How to resize UIView by dragging from its edges?

2020-01-25 04:37发布

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?

5条回答
再贱就再见
2楼-- · 2020-01-25 04:57

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.

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

查看更多
够拽才男人
3楼-- · 2020-01-25 05:06

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.

查看更多
爷、活的狠高调
4楼-- · 2020-01-25 05:06

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);
    }
} 
查看更多
淡お忘
5楼-- · 2020-01-25 05:14

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

        }
    }
查看更多
太酷不给撩
6楼-- · 2020-01-25 05:15

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.

查看更多
登录 后发表回答