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?
问题:
回答1:
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
.
回答2:
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);
}
}
回答3:
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: currentPoint.x + deltaWidth, height: currentPoint.y + 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.
回答4:
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
}
}
回答5:
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://imgur.com/a/CrkARLi
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