Search code examples
swiftuiviewuiimageviewimage-resizing

Resize ImageView by pressing on a circle on it's corner and dragging it to make it smaller/bigger


I've been doing this small app that contains a UIImageView. I can tap on it and 4 circles on each corner of the image should appear. I have to be able to drag my finger from the corner to resize the image. However the resizing doesn't work. From what I understood - I have to update the constraints of the imageView in the touchesMoved method.

I was using this post as a reference: How to resize UIView by dragging from its edges?

Setup imageView, scrollView and buttons

 struct ResizeRect{
        var topTouch = false
        var leftTouch = false
        var rightTouch = false
        var bottomTouch = false
        var middelTouch = false
    }

This is for my circles on the corners

   private var topLeftCircleLayer: CAShapeLayer!
   private var topRightCircleLayer: CAShapeLayer!
   private var bottomLeftCircleLayer: CAShapeLayer!
   private var bottomRightCircleLayer: CAShapeLayer!

Constraints for imageView

    private var imageViewTopConstraint: NSLayoutConstraint!
    private var imageViewBottomConstraint: NSLayoutConstraint!
    private var imageViewLeadingConstraint: NSLayoutConstraint!
    private var imageViewTrailingConstraint: NSLayoutConstraint!
    private var originalImageFrame: CGRect = .zero
    private var resizeRect = ResizeRect()

Setting up my views

 override func viewDidLoad() {
        super.viewDidLoad()

        scrollView.delegate = self

        setupView()

        addTapGestureRecognizer()
        addPinchGestureRecognizer()
        addRotateButton()
        addDeletePhotoButton()
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        borderLayer.path = UIBezierPath(rect: imageView.bounds).cgPath
        addConstraintsForItems()
        createCircles()
    }

    private func addConstraintsForItems() {
        imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 180)
        imageViewBottomConstraint = imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -180)
        imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 70)
        imageViewTrailingConstraint = imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -70)

        NSLayoutConstraint.activate([imageViewTopConstraint, imageViewBottomConstraint, imageViewLeadingConstraint, imageViewTrailingConstraint])
   }

    private func setupView() {
        view.addSubview(scrollView)
        view.addSubview(rotateButton)
        view.addSubview(deleteButton)

        scrollView.addSubview(imageView)

        borderLayer.fillColor = UIColor.clear.cgColor
        borderLayer.strokeColor = UIColor.black.cgColor
        borderLayer.lineWidth = 2
        borderLayer.isHidden = true
        imageView.layer.addSublayer(borderLayer)
    }

Circle creation

     private func updateCircles() {
        let topLeft = CGPoint(x: imageView.frame.minX, y: imageView.frame.minY)
        topLeftCircleLayer.position = topLeft
        let topRight = CGPoint(x: imageView.frame.maxX, y: imageView.frame.minY)
        topRightCircleLayer.position = topRight
        let bottomLeft = CGPoint(x: imageView.frame.minX, y: imageView.frame.maxY)
        bottomLeftCircleLayer.position = bottomLeft
        let bottomRight = CGPoint(x: imageView.frame.maxX, y: imageView.frame.maxY)
        bottomRightCircleLayer.position = bottomRight
        imageView.layer.insertSublayer(topLeftCircleLayer, at: 0)
        imageView.layer.insertSublayer(topRightCircleLayer, at: 1)
        imageView.layer.insertSublayer(bottomLeftCircleLayer, at: 2)
        imageView.layer.insertSublayer(bottomRightCircleLayer, at: 3)
    }

    private func createCircles() {
        topLeftCircleLayer = createCircle(at: CGPoint(x: imageView.frame.minX, y: imageView.frame.minY))
        topRightCircleLayer = createCircle(at: CGPoint(x: imageView.frame.maxX, y: imageView.frame.minY))
        bottomLeftCircleLayer = createCircle(at: CGPoint(x: imageView.frame.minX, y: imageView.frame.maxY))
        bottomRightCircleLayer = createCircle(at: CGPoint(x: imageView.frame.maxX, y: imageView.frame.maxY))
    }

    private func createCircle(at position: CGPoint) -> CAShapeLayer {
        let circle = CAShapeLayer()
        circle.path = UIBezierPath(arcCenter: position, radius: 10, startAngle: 0, endAngle: .pi * 2, clockwise: true).cgPath
        circle.fillColor = UIColor.systemPink.cgColor
        circle.strokeColor = UIColor.white.cgColor
        circle.lineWidth = 6
        circle.isHidden = !isCirclesVisible
        imageView.layer.addSublayer(circle)
        return circle
    }

And this is the most important part where I try to drag the corner

 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

            if touchStart.y > imageView.frame.maxY - proxyFactor &&  touchStart.y < imageView.frame.maxY + proxyFactor {
                resizeRect.bottomTouch = true
                print("bottom")
            }

            if touchStart.x > imageView.frame.maxX - proxyFactor && touchStart.x < imageView.frame.maxX + proxyFactor {
                resizeRect.rightTouch = true
                print("right")
            }

            if touchStart.x > imageView.frame.minX - proxyFactor &&  touchStart.x < imageView.frame.minX + proxyFactor {
                resizeRect.leftTouch = true
                print("left")
            }

            if touchStart.y > imageView.frame.minY - proxyFactor &&  touchStart.y < imageView.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.topTouch && resizeRect.leftTouch {
                if imageViewTopConstraint.constant + deltaY > 0 && imageViewLeadingConstraint.constant + deltaX > 0 {
                    imageViewTopConstraint.constant += deltaY
                    imageViewLeadingConstraint.constant += deltaX

                }
            }
            if resizeRect.topTouch && resizeRect.rightTouch {
                if imageViewTopConstraint.constant + deltaY > 0 && imageViewTrailingConstraint.constant - deltaX > 0 {
                    imageViewTopConstraint.constant += deltaY
                    imageViewTrailingConstraint.constant -= deltaX
                }
            }
            if resizeRect.bottomTouch && resizeRect.leftTouch {
                if imageViewBottomConstraint.constant - deltaY > 0 && imageViewLeadingConstraint.constant + deltaX > 0 {
                    imageViewLeadingConstraint.constant += deltaX
                    imageViewBottomConstraint.constant -= deltaY
                }
            }
            if resizeRect.bottomTouch && resizeRect.rightTouch {
                if imageViewBottomConstraint.constant - deltaY > 0 && imageViewTrailingConstraint.constant - deltaX > 0 {
                    imageViewTrailingConstraint.constant -= deltaX
                    imageViewBottomConstraint.constant -= deltaY
                }
            }

            UIView.animate(withDuration: 0.25, delay: 0, options: UIView.AnimationOptions.curveEaseIn) {
                self.view.layoutIfNeeded()
            }
        }
    } 

Edit 1: I've updated my code a bit. My app finally understands when I touch the imageView, when I touch a corner it can say which corner was touched but resizing doesn't work properly. Sometimes it works but it does the resizing very slowly. In the console it says that I have this error:

Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x600000cf8910 H:|-(65)-[UIImageView:0x13a707300]   (active, names: '|':UIView:0x13c20f220 )>",
    "<NSLayoutConstraint:0x600000ce0640 H:|-(70)-[UIImageView:0x13a707300]   (active, names: '|':UIView:0x13c20f220 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600000ce0640 H:|-(70)-[UIImageView:0x13a707300]   (active, names: '|':UIView:0x13c20f220 )>

I try to update the constraints but for some reason it won't let me. I've cloned the github project from a person that wrote a solution. He made IBOutlets for constraints and from what I understand - constraints created from IBOutlets are some kind different from those that I have. How do I fix the constraint issue? And I would be very grateful if someone could notice what is wrong with my circle creation. Right now I only see 1 below the middle of the image...


Solution

  • Several issues with your approach...

    First, none of this:

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        borderLayer.path = UIBezierPath(rect: imageView.bounds).cgPath
        addConstraintsForItems()
        createCircles()
    }
    

    should be there. viewDidLayoutSubviews() is called more than once, and if you are changing views/constraints it will be called many times.

    Second, the you may have had trouble getting that GitHub project working with code setup (as opposed to Storyboard setup with @IBOutlet connections) is because the order of the constraints is reversed.

    In your code, you're setting all 4 constraints on your imageView going from imageView -> view. The Storyboard setup can be replicated like this:

    // top and leading constraints need to be imageView -> view
    imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 180)
    imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 70)
        
    // trailing and bottom constraints need to be view -> imageView
    imageViewTrailingConstraint = view.trailingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 70)
    imageViewBottomConstraint = view.bottomAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 180)
        
    

    Third, you are using .frame in some places where you need to use .bounds.

    Here's a modified version of your code. I don't know how you are deciding when to show/hide the corner circles, so I added a Double-Tap gesture to activate / deactivate "resizing" mode:

    class ResizeViewViewController: UIViewController {
    
        private var topLeftCircleLayer: CAShapeLayer!
        private var topRightCircleLayer: CAShapeLayer!
        private var bottomLeftCircleLayer: CAShapeLayer!
        private var bottomRightCircleLayer: CAShapeLayer!
    
        private var borderLayer: CAShapeLayer!
    
        private var imageViewTopConstraint: NSLayoutConstraint!
        private var imageViewBottomConstraint: NSLayoutConstraint!
        private var imageViewLeadingConstraint: NSLayoutConstraint!
        private var imageViewTrailingConstraint: NSLayoutConstraint!
    
        private var originalImageFrame: CGRect = .zero
        private var resizeRect = ResizeRect()
    
        private var proxyFactor: CGFloat = 10.0
    
        private var imageView = UIImageView()
        
        private var isResizing: Bool = false {
            didSet {
                // update circle positions and
                //  show or hide the circles and border
                updateCircles()
                topLeftCircleLayer.isHidden = !isResizing
                topRightCircleLayer.isHidden = !isResizing
                bottomLeftCircleLayer.isHidden = !isResizing
                bottomRightCircleLayer.isHidden = !isResizing
                borderLayer.isHidden = !isResizing
            }
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
            
            setupView()
    
            addConstraintsForItems()
            
            createCircles()
            
            isResizing = false
            
            // let's use double-tap on the image view to
            //  toggle resizing
            let t = UITapGestureRecognizer(target: self, action: #selector(gotDoubleTap(_:)))
            t.numberOfTapsRequired = 2
            imageView.addGestureRecognizer(t)
            imageView.isUserInteractionEnabled = true
    
            // an instructions label at the top
            let v = UILabel()
            v.textAlignment = .center
            v.text = "Double-Tap image to toggle resizing mode."
            v.translatesAutoresizingMaskIntoConstraints = false
            // put it under the image view so it doesn't interfere
            view.insertSubview(v, at: 0)
            NSLayoutConstraint.activate([
                v.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
                v.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
            ])
            
        }
        
        @objc func gotDoubleTap(_ sender: Any?) {
            isResizing.toggle()
        }
    
        private func addConstraintsForItems() {
    
            // top and leading constraints need to be imageView -> view
            imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 180)
            imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 70)
    
            // trailing and bottom constraints need to be view -> imageView
            imageViewTrailingConstraint = view.trailingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 70)
            imageViewBottomConstraint = view.bottomAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 180)
            
            NSLayoutConstraint.activate([
                imageViewTopConstraint, imageViewBottomConstraint, imageViewLeadingConstraint, imageViewTrailingConstraint
            ])
        }
    
        private func setupView() {
    
            // add the image view
            imageView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(imageView)
    
            imageView.backgroundColor = .systemYellow
    
            // let's use the "swift" SF Symbol image
            if let img = UIImage(systemName: "swift") {
                imageView.image = img
                imageView.tintColor = .systemBlue
            }
    
            borderLayer = CAShapeLayer()
            borderLayer.fillColor = UIColor.clear.cgColor
            borderLayer.strokeColor = UIColor.black.cgColor
            borderLayer.lineWidth = 2
            borderLayer.isHidden = true
            imageView.layer.addSublayer(borderLayer)
            
        }
    
        private func updateCircles() {
            
            // we need to disable CALayer internal animations when
            //  changing the positions of the layers
            CATransaction.begin()
            CATransaction.setDisableActions(true)
    
            let topLeft = CGPoint(x: imageView.bounds.minX, y: imageView.bounds.minY)
            topLeftCircleLayer.position = topLeft
            let topRight = CGPoint(x: imageView.bounds.maxX, y: imageView.bounds.minY)
            topRightCircleLayer.position = topRight
            let bottomLeft = CGPoint(x: imageView.bounds.minX, y: imageView.bounds.maxY)
            bottomLeftCircleLayer.position = bottomLeft
            let bottomRight = CGPoint(x: imageView.bounds.maxX, y: imageView.bounds.maxY)
            bottomRightCircleLayer.position = bottomRight
            
            borderLayer.path = UIBezierPath(rect: imageView.bounds).cgPath
            
            CATransaction.commit()
            
        }
    
        private func createCircles() {
            // no need to pass "positions" here... they will be set when we show / update them
            topLeftCircleLayer = createCircle()
            topRightCircleLayer = createCircle()
            bottomLeftCircleLayer = createCircle()
            bottomRightCircleLayer = createCircle()
    
            // add the layers here
            imageView.layer.addSublayer(topLeftCircleLayer)
            imageView.layer.addSublayer(topRightCircleLayer)
            imageView.layer.addSublayer(bottomLeftCircleLayer)
            imageView.layer.addSublayer(bottomRightCircleLayer)
        }
    
        private func createCircle() -> CAShapeLayer {
            let circle = CAShapeLayer()
            circle.path = UIBezierPath(arcCenter: .zero, radius: 10, startAngle: 0, endAngle: .pi * 2, clockwise: true).cgPath
            circle.fillColor = UIColor.systemPink.cgColor
            circle.strokeColor = UIColor.white.cgColor
            circle.lineWidth = 6
            circle.isHidden = true
            return circle
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            
            if !isResizing { return() }
            
            if let touch = touches.first{
                
                let touchStart = touch.location(in: self.view)
                print(touchStart, imageView.frame)
                
                resizeRect.topTouch = false
                resizeRect.leftTouch = false
                resizeRect.rightTouch = false
                resizeRect.bottomTouch = false
                
                if touchStart.y > imageView.frame.maxY - proxyFactor &&  touchStart.y < imageView.frame.maxY + proxyFactor {
                    resizeRect.bottomTouch = true
                    print("bottom")
                }
                
                if touchStart.x > imageView.frame.maxX - proxyFactor && touchStart.x < imageView.frame.maxX + proxyFactor {
                    resizeRect.rightTouch = true
                    print("right")
                }
                
                if touchStart.x > imageView.frame.minX - proxyFactor &&  touchStart.x < imageView.frame.minX + proxyFactor {
                    resizeRect.leftTouch = true
                    print("left")
                }
                
                if touchStart.y > imageView.frame.minY - proxyFactor &&  touchStart.y < imageView.frame.minY + proxyFactor {
                    resizeRect.topTouch = true
                    print("top")
                }
    
            }
        }
        
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    
            if !isResizing { return() }
    
            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.topTouch {
                    imageViewTopConstraint.constant += deltaY
                }
                if resizeRect.leftTouch {
                    imageViewLeadingConstraint.constant += deltaX
                }
                if resizeRect.rightTouch {
                    imageViewTrailingConstraint.constant -= deltaX
                }
                if resizeRect.bottomTouch {
                    imageViewBottomConstraint.constant -= deltaY
                }
    
                // don't know why you would want to animate this?
                //UIView.animate(withDuration: 0.25, delay: 0, options: UIView.AnimationOptions.curveEaseIn, animations: {
                //  self.view.layoutIfNeeded()
                //}, completion: { (ended) in
                //
                //})
    
                // note: this can "lag" and not precisely match the image view frame
                
                self.updateCircles()
                
                //  we can dispatch it async to allow UIKit to set the
                //  image view's frame before we move the layers
                //  but we still get a little "lag"
                DispatchQueue.main.async {
                    self.updateCircles()
                }
    
            }
    
        }
    
    }
    
    struct ResizeRect{
        var topTouch = false
        var leftTouch = false
        var rightTouch = false
        var bottomTouch = false
        var middelTouch = false
    }
    

    When you run that, you'll notice that there is some "lag" when dragging and resizing - see the comments in the code.

    A similar approach that might find it more responsive, as well as easier to manage...

    Instead of adding "circle layers" we'll add "circle views" as subviews of the image view. We can then set constraints to the image view corners, and let auto-layout handle all of the positioning.

    Less code... takes advantage of auto-layout... more responsive...

    Run the above version first, then give this one a try:

    class CircleView: UIView {
        
        // this allows us to use the "base" layer as a shape layer
        //  instead of adding a sublayer
        lazy var shapeLayer: CAShapeLayer = self.layer as! CAShapeLayer
        override class var layerClass: AnyClass {
            return CAShapeLayer.self
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() {
            shapeLayer.fillColor = UIColor.systemPink.cgColor
            shapeLayer.strokeColor = UIColor.white.cgColor
            shapeLayer.lineWidth = 6
        }
        override func layoutSubviews() {
            super.layoutSubviews()
            shapeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
        }
    
    }
    
    class ResizeViewViewController: UIViewController {
        
        private var topLeftCircleView = CircleView()
        private var topRightCircleView = CircleView()
        private var bottomLeftCircleView = CircleView()
        private var bottomRightCircleView = CircleView()
        
        private var borderView = UIView()
        
        private var imageViewTopConstraint: NSLayoutConstraint!
        private var imageViewBottomConstraint: NSLayoutConstraint!
        private var imageViewLeadingConstraint: NSLayoutConstraint!
        private var imageViewTrailingConstraint: NSLayoutConstraint!
        
        private var originalImageFrame: CGRect = .zero
        private var resizeRect = ResizeRect()
        
        private var proxyFactor: CGFloat = 10.0
        
        private var imageView = UIImageView()
        
        private var isResizing: Bool = false {
            didSet {
                // show or hide the circles and image view border
                [topLeftCircleView, topRightCircleView, bottomLeftCircleView, bottomRightCircleView].forEach { v in
                    v.isHidden = !isResizing
                }
                borderView.layer.borderWidth = isResizing ? 2.0 : 0.0
            }
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
            
            setupView()
            
            addConstraintsForItems()
            
            createCircles()
            
            isResizing = false
    
            // let's use double-tap on the image view to
            //  toggle resizing
            let t = UITapGestureRecognizer(target: self, action: #selector(gotDoubleTap(_:)))
            t.numberOfTapsRequired = 2
            imageView.addGestureRecognizer(t)
            imageView.isUserInteractionEnabled = true
    
            // an instructions label at the top
            let v = UILabel()
            v.textAlignment = .center
            v.text = "Double-Tap image to toggle resizing mode."
            v.font = .systemFont(ofSize: 15.0, weight: .light)
            v.translatesAutoresizingMaskIntoConstraints = false
            // put it under the image view so it doesn't interfere
            view.insertSubview(v, at: 0)
            NSLayoutConstraint.activate([
                v.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
                v.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
            ])
            
        }
        
        @objc func gotDoubleTap(_ sender: Any?) {
            isResizing.toggle()
        }
        
        private func addConstraintsForItems() {
            
            // top and leading constraints need to be imageView -> view
            imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 180)
            imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 70)
            
            // trailing and bottom constraints need to be view -> imageView
            imageViewTrailingConstraint = view.trailingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 70)
            imageViewBottomConstraint = view.bottomAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 180)
            
            NSLayoutConstraint.activate([
                imageViewTopConstraint, imageViewBottomConstraint, imageViewLeadingConstraint, imageViewTrailingConstraint,
    
                // constrain all 4 sides of the borderView to the imageView
                borderView.topAnchor.constraint(equalTo: imageView.topAnchor),
                borderView.leadingAnchor.constraint(equalTo: imageView.leadingAnchor),
                borderView.trailingAnchor.constraint(equalTo: imageView.trailingAnchor),
                borderView.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
            ])
        }
        
        private func setupView() {
    
            // add the image view
            imageView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(imageView)
    
            imageView.backgroundColor = .systemYellow
    
            // add the borderView to the image view
            //  we'd like to just use the image view's layer border, but
            //  setting its border width draws on top of the circle subviews
            borderView.translatesAutoresizingMaskIntoConstraints = false
            imageView.addSubview(borderView)
            
            // let's use the "swift" SF Symbol image
            if let img = UIImage(systemName: "swift") {
                imageView.image = img
                imageView.tintColor = .systemBlue
            }
            
            // border view layer border starts at width: 0 (not showing)
            borderView.layer.borderColor = UIColor.black.cgColor
            borderView.layer.borderWidth = 0
            
        }
        
    
        private func createCircles() {
    
            // add the circle views to the image view
            //  constraining width: 20 and height = width
            [topLeftCircleView, topRightCircleView, bottomLeftCircleView, bottomRightCircleView].forEach { v in
                v.translatesAutoresizingMaskIntoConstraints = false
                v.widthAnchor.constraint(equalToConstant: 20.0).isActive = true
                v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
                // they start hidden
                v.isHidden = true
                imageView.addSubview(v)
            }
            
            // constrain circleViews to the image view corners
            NSLayoutConstraint.activate([
                topLeftCircleView.centerXAnchor.constraint(equalTo: imageView.leadingAnchor),
                topLeftCircleView.centerYAnchor.constraint(equalTo: imageView.topAnchor),
                
                topRightCircleView.centerXAnchor.constraint(equalTo: imageView.trailingAnchor),
                topRightCircleView.centerYAnchor.constraint(equalTo: imageView.topAnchor),
                
                bottomLeftCircleView.centerXAnchor.constraint(equalTo: imageView.leadingAnchor),
                bottomLeftCircleView.centerYAnchor.constraint(equalTo: imageView.bottomAnchor),
                
                bottomRightCircleView.centerXAnchor.constraint(equalTo: imageView.trailingAnchor),
                bottomRightCircleView.centerYAnchor.constraint(equalTo: imageView.bottomAnchor),
            ])
    
        }
        
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            
            // only want to drag-resize if the corner circles are showing
            if !isResizing { return() }
            
            if let touch = touches.first{
                
                let touchStart = touch.location(in: self.view)
                print(touchStart, imageView.frame)
                
                resizeRect.topTouch = false
                resizeRect.leftTouch = false
                resizeRect.rightTouch = false
                resizeRect.bottomTouch = false
                
                if touchStart.y > imageView.frame.maxY - proxyFactor &&  touchStart.y < imageView.frame.maxY + proxyFactor {
                    resizeRect.bottomTouch = true
                    print("bottom")
                }
                
                if touchStart.x > imageView.frame.maxX - proxyFactor && touchStart.x < imageView.frame.maxX + proxyFactor {
                    resizeRect.rightTouch = true
                    print("right")
                }
                
                if touchStart.x > imageView.frame.minX - proxyFactor &&  touchStart.x < imageView.frame.minX + proxyFactor {
                    resizeRect.leftTouch = true
                    print("left")
                }
                
                if touchStart.y > imageView.frame.minY - proxyFactor &&  touchStart.y < imageView.frame.minY + proxyFactor {
                    resizeRect.topTouch = true
                    print("top")
                }
                
            }
        }
        
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            
            // only want to drag-resize if the corner circles are showing
            if !isResizing { return() }
            
            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.topTouch {
                    imageViewTopConstraint.constant += deltaY
                }
                if resizeRect.leftTouch {
                    imageViewLeadingConstraint.constant += deltaX
                }
                if resizeRect.rightTouch {
                    imageViewTrailingConstraint.constant -= deltaX
                }
                if resizeRect.bottomTouch {
                    imageViewBottomConstraint.constant -= deltaY
                }
                
                // don't know why you would want to animate this?
                //UIView.animate(withDuration: 0.25, delay: 0, options: UIView.AnimationOptions.curveEaseIn, animations: {
                //  self.view.layoutIfNeeded()
                //}, completion: { (ended) in
                //
                //})
    
            }
            
        }
        
    }
    
    struct ResizeRect{
        var topTouch = false
        var leftTouch = false
        var rightTouch = false
        var bottomTouch = false
        var middelTouch = false
    }