In my iPad app, I want the users to be able to resize a UIView
by dragging the view from its edges. I'll be using iOS 5 SDK, so what's the cleanest approach to do this? Are there any alternatives to achieving this without dealing with touchesBegan, touchesMoved,... etc?

- 8,740
- 10
- 59
- 80
6 Answers
You can do this by checking the touch-start point. If it hits one of your four corners you can resize based on the distance between that touch-start point and the current-touch point. (If the touch-start point didn't hit a corner, we just move the view instead of resizing.)
Define the size of your draggable corners.
CGFloat kResizeThumbSize = 45.0f;
Add these instance variables to your class to keep track of touch state and which way we're resizing.
@interface MY_CLASS_NAME : UIView {
BOOL isResizingLR;
BOOL isResizingUL;
BOOL isResizingUR;
BOOL isResizingLL;
CGPoint touchStart;
}
Handle the touch start / change events.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
touchStart = [[touches anyObject] locationInView:self];
isResizingLR = (self.bounds.size.width - touchStart.x < kResizeThumbSize && self.bounds.size.height - touchStart.y < kResizeThumbSize);
isResizingUL = (touchStart.x <kResizeThumbSize && touchStart.y <kResizeThumbSize);
isResizingUR = (self.bounds.size.width-touchStart.x < kResizeThumbSize && touchStart.y<kResizeThumbSize);
isResizingLL = (touchStart.x <kResizeThumbSize && self.bounds.size.height -touchStart.y <kResizeThumbSize);
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInView:self];
CGPoint previous = [[touches anyObject] previousLocationInView:self];
CGFloat deltaWidth = touchPoint.x - previous.x;
CGFloat deltaHeight = touchPoint.y - previous.y;
// get the frame values so we can calculate changes below
CGFloat x = self.frame.origin.x;
CGFloat y = self.frame.origin.y;
CGFloat width = self.frame.size.width;
CGFloat height = self.frame.size.height;
if (isResizingLR) {
self.frame = CGRectMake(x, y, touchPoint.x+deltaWidth, touchPoint.y+deltaWidth);
} else if (isResizingUL) {
self.frame = CGRectMake(x+deltaWidth, y+deltaHeight, width-deltaWidth, height-deltaHeight);
} else if (isResizingUR) {
self.frame = CGRectMake(x, y+deltaHeight, width+deltaWidth, height-deltaHeight);
} else if (isResizingLL) {
self.frame = CGRectMake(x+deltaWidth, y, width-deltaWidth, height+deltaHeight);
} else {
// not dragging from a corner -- move the view
self.center = CGPointMake(self.center.x + touchPoint.x - touchStart.x,
self.center.y + touchPoint.y - touchStart.y);
}
}

- 6,298
- 1
- 26
- 41

- 3,179
- 7
- 35
- 77
-
1Awesome thank You this made so much more sense to me than anything else!! However i think the first one if (isResizingLR) { needs to be y + deltaHeight NOT delta width - please let me know if this is right??!! Thx – devjme Jun 18 '18 at 16:07
I updated above code using enum
.
class ResizableView: UIView {
enum Edge {
case topLeft, topRight, bottomLeft, bottomRight, none
}
static var edgeSize: CGFloat = 44.0
private typealias `Self` = ResizableView
var currentEdge: Edge = .none
var touchStart = CGPoint.zero
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
touchStart = touch.location(in: self)
currentEdge = {
if self.bounds.size.width - touchStart.x < Self.edgeSize && self.bounds.size.height - touchStart.y < Self.edgeSize {
return .bottomRight
} else if touchStart.x < Self.edgeSize && touchStart.y < Self.edgeSize {
return .topLeft
} else if self.bounds.size.width-touchStart.x < Self.edgeSize && touchStart.y < Self.edgeSize {
return .topRight
} else if touchStart.x < Self.edgeSize && self.bounds.size.height - touchStart.y < Self.edgeSize {
return .bottomLeft
}
return .none
}()
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let currentPoint = touch.location(in: self)
let previous = touch.previousLocation(in: self)
let originX = self.frame.origin.x
let originY = self.frame.origin.y
let width = self.frame.size.width
let height = self.frame.size.height
let deltaWidth = currentPoint.x - previous.x
let deltaHeight = currentPoint.y - previous.y
switch currentEdge {
case .topLeft:
self.frame = CGRect(x: originX + deltaWidth, y: originY + deltaHeight, width: width - deltaWidth, height: height - deltaHeight)
case .topRight:
self.frame = CGRect(x: originX, y: originY + deltaHeight, width: width + deltaWidth, height: height - deltaHeight)
case .bottomRight:
self.frame = CGRect(x: originX, y: originY, width: width + deltaWidth, height: height + deltaWidth)
case .bottomLeft:
self.frame = CGRect(x: originX + deltaWidth, y: originY, width: width - deltaWidth, height: height + deltaHeight)
default:
// Moving
self.center = CGPoint(x: self.center.x + currentPoint.x - touchStart.x,
y: self.center.y + currentPoint.y - touchStart.y)
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
currentEdge = .none
}
}
currentEdge
saves state of touch position of user.

- 5
- 3

- 1,669
- 18
- 29
-
can your please update your answer by placing boolean check to maintain aspect ratio while dragging from any of the edge? That will be a great addition. – Rashesh Bosamiya Feb 10 '23 at 17:25
I'm guessing your UI involves some kind of handles on the sides of the view, and attaching a simple UIPanGestureRecognizer
to those handle controls makes the whole problem pretty easy.
In the action method from the gesture recognizer, just get the -translationInView:
relative to the view you're resizing, save off the original frame when the gesture recognizer's state is UIGestureRecognizerStateBegan
, and adjust the view's frame continually while the state is UIGestureRecognizerStateChanged
.

- 79,175
- 10
- 73
- 78
-
1I don't have any handles on the sides (cos I need to follow our designer's UI design) but I guess I can place invisible views or sthg like that to attach the gesture recognizers. – aslı Dec 10 '11 at 22:05
This is a Swift 4.2 Solution which works with AutoLayout & Constraint-Animation.
Since it is recommended to animate the constraints rather than the actual size of a rectangle when working with AutoLayout, this solutions does exactly that.
Additional features:
- Resize only one side when not on the edge.
- Move the whole rectangle on touch in the middle of the it.
Checkout a video of it here: https://i.stack.imgur.com/hlAUn.jpg
Import is to connect the constraints as outlets, to animate them.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var topConstraint: NSLayoutConstraint!
@IBOutlet weak var rightConstraint: NSLayoutConstraint!
@IBOutlet weak var leftConstraint: NSLayoutConstraint!
@IBOutlet weak var bottomConstraint: NSLayoutConstraint!
@IBOutlet weak var rect: UIView!
struct ResizeRect{
var topTouch = false
var leftTouch = false
var rightTouch = false
var bottomTouch = false
var middelTouch = false
}
var touchStart = CGPoint.zero
var proxyFactor = CGFloat(10)
var resizeRect = ResizeRect()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let touchStart = touch.location(in: self.view)
print(touchStart)
resizeRect.topTouch = false
resizeRect.leftTouch = false
resizeRect.rightTouch = false
resizeRect.bottomTouch = false
resizeRect.middelTouch = false
if touchStart.y > rect.frame.minY + (proxyFactor*2) && touchStart.y < rect.frame.maxY - (proxyFactor*2) && touchStart.x > rect.frame.minX + (proxyFactor*2) && touchStart.x < rect.frame.maxX - (proxyFactor*2){
resizeRect.middelTouch = true
print("middle")
return
}
if touchStart.y > rect.frame.maxY - proxyFactor && touchStart.y < rect.frame.maxY + proxyFactor {
resizeRect.bottomTouch = true
print("bottom")
}
if touchStart.x > rect.frame.maxX - proxyFactor && touchStart.x < rect.frame.maxX + proxyFactor {
resizeRect.rightTouch = true
print("right")
}
if touchStart.x > rect.frame.minX - proxyFactor && touchStart.x < rect.frame.minX + proxyFactor {
resizeRect.leftTouch = true
print("left")
}
if touchStart.y > rect.frame.minY - proxyFactor && touchStart.y < rect.frame.minY + proxyFactor {
resizeRect.topTouch = true
print("top")
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let currentTouchPoint = touch.location(in: self.view)
let previousTouchPoint = touch.previousLocation(in: self.view)
let deltaX = currentTouchPoint.x - previousTouchPoint.x
let deltaY = currentTouchPoint.y - previousTouchPoint.y
if resizeRect.middelTouch{
topConstraint.constant += deltaY
leftConstraint.constant += deltaX
rightConstraint.constant -= deltaX
bottomConstraint.constant -= deltaY
}
if resizeRect.topTouch {
topConstraint.constant += deltaY
}
if resizeRect.leftTouch {
leftConstraint.constant += deltaX
}
if resizeRect.rightTouch {
rightConstraint.constant -= deltaX
}
if resizeRect.bottomTouch {
bottomConstraint.constant -= deltaY
}
UIView.animate(withDuration: 0.25, delay: 0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.view.layoutIfNeeded()
}, completion: { (ended) in
})
}
}
}
I put this as working project on Github too: https://github.com/ppoh71/resizeRectangleOnTouchDrag

- 1,478
- 15
- 30
Swift Version of @Prerna Chavan solution , Prerna solution does not detect if user touch on edges, it is detecting only corners, however below code detects all
class OverlayView: UIView {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
static var kResizeThumbSize:CGFloat = 44.0
private typealias `Self` = OverlayView
var imageView = UIImageView()
var isResizingLeftEdge:Bool = false
var isResizingRightEdge:Bool = false
var isResizingTopEdge:Bool = false
var isResizingBottomEdge:Bool = false
var isResizingBottomRightCorner:Bool = false
var isResizingLeftCorner:Bool = false
var isResizingRightCorner:Bool = false
var isResizingBottomLeftCorner:Bool = false
//Define your initialisers here
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let currentPoint = touch.location(in: self)
isResizingBottomRightCorner = (self.bounds.size.width - currentPoint.x < Self.kResizeThumbSize && self.bounds.size.height - currentPoint.y < Self.kResizeThumbSize);
isResizingLeftCorner = (currentPoint.x < Self.kResizeThumbSize && currentPoint.y < Self.kResizeThumbSize);
isResizingRightCorner = (self.bounds.size.width-currentPoint.x < Self.kResizeThumbSize && currentPoint.y < Self.kResizeThumbSize);
isResizingBottomLeftCorner = (currentPoint.x < Self.kResizeThumbSize && self.bounds.size.height - currentPoint.y < Self.kResizeThumbSize);
isResizingLeftEdge = (currentPoint.x < Self.kResizeThumbSize)
isResizingTopEdge = (currentPoint.y < Self.kResizeThumbSize)
isResizingRightEdge = (self.bounds.size.width - currentPoint.x < Self.kResizeThumbSize)
isResizingBottomEdge = (self.bounds.size.height - currentPoint.y < Self.kResizeThumbSize)
// do something with your currentPoint
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let currentPoint = touch.location(in: self)
// do something with your currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let currentPoint = touch.location(in: self)
// do something with your currentPoint
isResizingLeftEdge = false
isResizingRightEdge = false
isResizingTopEdge = false
isResizingBottomEdge = false
isResizingBottomRightCorner = false
isResizingLeftCorner = false
isResizingRightCorner = false
isResizingBottomLeftCorner = false
}
}

- 1,538
- 14
- 16
Just a small fixed
added by me to @Peter Pohlmann answer, that doesn't allow dragging or moving the view outside the view's frame
The only bit is in override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let currentTouchPoint = touch.location(in: self.view)
let previousTouchPoint = touch.previousLocation(in: self.view)
let deltaX = currentTouchPoint.x - previousTouchPoint.x
let deltaY = currentTouchPoint.y - previousTouchPoint.y
if resizeRect.middelTouch {
if topConstraint.constant + deltaY > 0 && leftConstraint.constant + deltaX > 0 && rightConstraint.constant - deltaX > 0 && bottomConstraint.constant - deltaY > 40 {
topConstraint.constant += deltaY
leftConstraint.constant += deltaX
rightConstraint.constant -= deltaX
bottomConstraint.constant -= deltaY
}
}
if resizeRect.topTouch {
if topConstraint.constant + deltaY > 0 {
topConstraint.constant += deltaY
}
}
if resizeRect.leftTouch {
if leftConstraint.constant + deltaX > 0 {
leftConstraint.constant += deltaX
}
}
if resizeRect.rightTouch {
if rightConstraint.constant - deltaX > 0 {
rightConstraint.constant -= deltaX
}
}
if resizeRect.bottomTouch {
if bottomConstraint.constant - deltaY > 0 {
bottomConstraint.constant -= deltaY
}
}
UIView.animate(withDuration: 0.25, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.view.layoutIfNeeded()
}, completion: { (ended) in
})
}
}

- 593
- 7
- 20