1

I'm trying to make an app based on image editing and I'm founding some problems coding a resizing by corners dragging crop area.

I have croppingView.swift where the crop rectangle is drawn when launched:

override func viewDidAppear(_ animated: Bool) {
        let imagePoint = CGPoint(x: contentView.center.x, y: contentView.center.y)

        let imageSize = CGSize(width: cropImageView.image!.size.width, height: cropImageView.image!.size.height)
        let widthScale = cropImageView.frame.width / imageSize.width
        let heightScale = cropImageView.frame.height / imageSize.height
        let scale = min(widthScale, heightScale)
        let width = imageSize.width * scale
        let height = imageSize.height * scale
        let size = CGSize(width: width, height: height)

        let originViewFrame = CGPoint(x: imagePoint.x - width/2.0, y: imagePoint.y - height/2.0)

        let cropSize = CGSize(width: width * 0.7, height: height * 0.7)
        let cropViewFrame = CGRect(origin: originViewFrame, size: cropSize)

        cropView = ShapeView(frame: cropViewFrame)
        cropView.backgroundColor = UIColor(red: 224.0/255.0, green: 224.0/255.0, blue: 224.0/255.0, alpha: 0.3)
        cropView.layer.borderColor = UIColor(red: 97.0/255.0, green: 97.0/255.0, blue: 97.0/255.0, alpha: 1.0).cgColor
        cropView.layer.borderWidth = 1.0
        cropView.center = imagePoint
        self.view.addSubview(cropView)
}

The ShapeView.swift to draw the cropping area is as follows:

class ShapeView: UIView {

    var previousLocation = CGPoint.zero

    var topLeft = DragHandle(fillColor:UIColor(red: 0.0/255.0, green: 150.0/255.0, blue: 136.0/255.0, alpha: 1.0),
                             strokeColor: UIColor.white)
    var topRight = DragHandle(fillColor:UIColor(red: 0.0/255.0, green: 150.0/255.0, blue: 136.0/255.0, alpha: 1.0),
                              strokeColor: UIColor.white)
    var bottomLeft = DragHandle(fillColor:UIColor(red: 0.0/255.0, green: 150.0/255.0, blue: 136.0/255.0, alpha: 1.0),
                                strokeColor: UIColor.white)
    var bottomRight = DragHandle(fillColor:UIColor(red: 0.0/255.0, green: 150.0/255.0, blue: 136.0/255.0, alpha: 1.0),
                                 strokeColor: UIColor.white)

    override func didMoveToSuperview() {
        superview?.addSubview(topLeft)
        superview?.addSubview(topRight)
        superview?.addSubview(bottomLeft)
        superview?.addSubview(bottomRight)

        var pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
        topLeft.addGestureRecognizer(pan)
        pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
        topRight.addGestureRecognizer(pan)
        pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
        bottomLeft.addGestureRecognizer(pan)
        pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
        bottomRight.addGestureRecognizer(pan)
        pan = UIPanGestureRecognizer(target: self, action: #selector(handleMove))
        self.addGestureRecognizer(pan)

        self.updateDragHandles()
    }

    func updateDragHandles() {
        topLeft.center = self.transformedTopLeft()
        topRight.center = self.transformedTopRight()
        bottomLeft.center = self.transformedBottomLeft()
        bottomRight.center = self.transformedBottomRight()
    }

    //Gesture Methods
    @IBAction func handleMove(gesture:UIPanGestureRecognizer) {
        let translation = gesture.translation(in: self.superview!)
        var center = self.center
        center.x += translation.x
        center.y += translation.y
        self.center = center
        gesture.setTranslation(CGPoint.zero, in: self.superview!)
        updateDragHandles()
    }

    @IBAction func handlePan(gesture:UIPanGestureRecognizer) {
        let translation = gesture.translation(in: self)
        switch gesture.view! {
        case topLeft:
            if gesture.state == .began {
                self.setAnchorPoint(anchorPoint: CGPoint(x: 1, y: 1))
            }
            self.bounds.size.width -= translation.x
            self.bounds.size.height -= translation.y
        case topRight:
            if gesture.state == .began {
                self.setAnchorPoint(anchorPoint: CGPoint(x: 0, y: 1))
            }
            self.bounds.size.width += translation.x
            self.bounds.size.height -= translation.y

        case bottomLeft:
            if gesture.state == .began {
                self.setAnchorPoint(anchorPoint: CGPoint(x: 1, y: 0))
            }
            self.bounds.size.width -= translation.x
            self.bounds.size.height += translation.y
        case bottomRight:
            if gesture.state == .began {
                self.setAnchorPoint(anchorPoint: CGPoint.zero)
            }
            self.bounds.size.width += translation.x
            self.bounds.size.height += translation.y
        default:()
        }

        gesture.setTranslation(CGPoint.zero, in: self)
        updateDragHandles()
        if gesture.state == .ended {
            self.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
        }
    }
}

As you can see I'm adding one circle in each corner of the rectangle. These circles are used to resize the cropping area by dragging. Here the key is the updateDragHandles()function which updates the positions of all other circle keeping them on the corners.

So when launching the cropping area appears like:

enter image description here

And when user moves for example the bottom-right corner by dragging everything is working fine:

enter image description here

Now the problem is when I click on the portrait button to change cropping area orientation. With the next code into croppingView.swift the grey rectangle correctly changes the size but the circles remain on the their position:

@IBAction func portButton(_ sender: Any) {
        portButton.tintColor = UIColor.systemBlue
        landButton.tintColor = UIColor.systemGray
        isPortait = 1
        let inWidth = cropView.frame.size.width
        let inHeight = cropView.frame.size.height
        if inWidth > inHeight {
            let scaleW = cropView.frame.size.height / cropView.frame.size.width
            let scaleH = cropView.frame.size.width / cropView.frame.size.height
            let fixPoint = CGPoint(x: 0.5, y: 0.5)
            cropView.setAnchorPoint(anchorPoint: fixPoint)
            cropView.transform = cropView.transform.scaledBy(x: scaleW, y: scaleH)
            let vc = ShapeView()
            vc.updateDragHandles()
        }
    }

It's like updateDragHandles is not working from this ViewController...

enter image description here

Probably it's a basic mistake but I can't find out the solution.

Any suggestion?

Thank you in advance!

DragHandle.swift

let diameter:CGFloat = 30

import UIKit

class DragHandle: UIView {

  var fillColor = UIColor.darkGray
  var strokeColor = UIColor.lightGray
  var strokeWidth: CGFloat = 1.0

  required init(coder aDecoder: NSCoder) {
    fatalError("Use init(fillColor:, strokeColor:)")
  }

  init(fillColor: UIColor, strokeColor: UIColor, strokeWidth width: CGFloat = 1.0) {
    super.init(frame: CGRect(x: 0, y: 0, width: diameter, height: diameter))
    self.fillColor = fillColor
    self.strokeColor = strokeColor
    self.strokeWidth = width
    self.backgroundColor = UIColor.clear
  }

    override func draw(_ rect: CGRect)
    {
        super.draw(rect)
        let handlePath = UIBezierPath(ovalIn: rect.insetBy(dx: 10 + strokeWidth, dy: 10 + strokeWidth))
        fillColor.setFill()
        handlePath.fill()
        strokeColor.setStroke()
        handlePath.lineWidth = strokeWidth
        handlePath.stroke()
    }
}

UIViewExtensions.swift

import Foundation
import UIKit

extension UIView {
    func offsetPointToParentCoordinates(point: CGPoint) -> CGPoint {
      return CGPoint(x: point.x + self.center.x, y: point.y + self.center.y)
    }

    func pointInViewCenterTerms(point:CGPoint) -> CGPoint {
      return CGPoint(x: point.x - self.center.x, y: point.y - self.center.y)
    }

    func pointInTransformedView(point: CGPoint) -> CGPoint {
      let offsetItem = self.pointInViewCenterTerms(point: point)
      let updatedItem = offsetItem.applying(self.transform)
      let finalItem = self.offsetPointToParentCoordinates(point: updatedItem)
      return finalItem
    }

    func originalFrame() -> CGRect {
      let currentTransform = self.transform
      self.transform = .identity
      let originalFrame = self.frame
      self.transform = currentTransform
      return originalFrame
    }

    func transformedTopLeft() -> CGPoint {
      let frame = self.originalFrame()
      let point = frame.origin
      return self.pointInTransformedView(point: point)
    }

    func transformedTopRight() -> CGPoint {
      let frame = self.originalFrame()
      var point = frame.origin
      point.x += frame.size.width
      return self.pointInTransformedView(point: point)
    }

    func transformedBottomRight() -> CGPoint {
      let frame = self.originalFrame()
      var point = frame.origin
      point.x += frame.size.width
      point.y += frame.size.height
      return self.pointInTransformedView(point: point)
    }

    func transformedBottomLeft() -> CGPoint {
      let frame = self.originalFrame()
      var point = frame.origin
      point.y += frame.size.height
      return self.pointInTransformedView(point: point)
    }

    func setAnchorPoint(anchorPoint: CGPoint) {
      var newPoint = CGPoint(x: self.bounds.size.width * anchorPoint.x, y: self.bounds.size.height * anchorPoint.y)
      var oldPoint = CGPoint(x: self.bounds.size.width * self.layer.anchorPoint.x, y: self.bounds.size.height * self.layer.anchorPoint.y)

      newPoint = newPoint.applying(self.transform)
      oldPoint = oldPoint.applying(self.transform)

      var position = self.layer.position
      position.x -= oldPoint.x
      position.x += newPoint.x
      position.y -= oldPoint.y
      position.y += newPoint.y

      self.layer.position = position
      self.layer.anchorPoint = anchorPoint
    }
}
Tsilaicos
  • 435
  • 2
  • 13
  • You might want to read this: https://developer.apple.com/documentation/uikit/uiview/1622621-frame especially the warning part. Maybe https://stackoverflow.com/questions/7624755/accessing-the-current-position-of-uiview-during-animation might help, but not sure with transformations – Larme May 28 '20 at 12:44
  • What does the `DragHandle` class look like? Is their position setup in their own `didMoveToSuperview()`? – clawesome May 28 '20 at 13:25
  • Hi @clawesome thank you for your answer. I've edited my question adding the DragHandle class. – Tsilaicos May 28 '20 at 13:38
  • I think what we really need to see is what happens in the transform methods in `updateDragHandles()` – clawesome May 28 '20 at 14:13
  • Sure @clawesome! I've added the extensions class that I'm using into `updateDragHandles()` – Tsilaicos May 28 '20 at 15:10
  • In `func originalFrame()` that is essentially removing all the transforms to get the original frame right? And `updateDragHandles()` uses that original frame to update their locations, so when your button is pressed you add a transform to change it from landscape/portrait but when the drag handles are updated after that, they are done so by the original frames position which is calculated by removing the transform for the new orientation. – clawesome May 28 '20 at 21:53

0 Answers0