Kaspis245
Kaspis245

Reputation: 496

How to transfer the position and scale of an image to other view?

I have two views and an image in each view. In the first view I am able to scale, rotate and pan the image using gestures. I want to record each of these transformations and transfer them to the image in the second view, so that the second image would appear in exactly the same location as the first image in the first view, but would have an opposite scaling (if the first image is scaled up, then the second one needs to be scaled down by the same amount).

The code below works only if the image is panned or rotated, but not scaled. Scaling completely changes the location of the centre of the image and the code no longer works correctly. How can I fix it so that the location of the centre of the second image is the same as the first regardless of the scaling?

Here's the view controller of the first view:

import UIKit

class FirstViewController: UIViewController {

// MARK: Properties
@IBOutlet weak var photo: UIImageView!

// Variables for scaling
var max_scale: CGFloat = 3.0
var min_scale: CGFloat = 0.2
var current_scale: CGFloat = 1.0
var lastScale: CGFloat = 0.0

// Position variables
var translate_x: CGFloat = 0.0
var translate_y: CGFloat = 0.0
var scale: CGFloat = 1.0
var angle: CGFloat = 0.0

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

// MARK: Gesture actions

@IBAction func pinchGesture(_ gestureRecognizer: UIPinchGestureRecognizer) {

    // Move the achor point of the view's layer to the touch point
    // so that scaling the view and the layer becames simpler.
    self.adjustAnchorPoint(gestureRecognizer: gestureRecognizer)

    // Scale the view by the current scale factor.

    if(gestureRecognizer.state == .began) {
        // Reset the last scale, necessary if there are multiple objects with different scales
        lastScale = gestureRecognizer.scale
    }
    if (gestureRecognizer.state == .began || gestureRecognizer.state == .changed) {
        let currentScale = gestureRecognizer.view!.layer.value(forKeyPath:"transform.scale")! as! CGFloat
        // Constants to adjust the max/min values of zoom
        let kMaxScale:CGFloat = 15.0
        let kMinScale:CGFloat = 1.0
        var newScale = 1 -  (lastScale - gestureRecognizer.scale)
        newScale = min(newScale, kMaxScale / currentScale)
        newScale = max(newScale, kMinScale / currentScale)
        let transform = (gestureRecognizer.view?.transform)!.scaledBy(x: newScale, y: newScale);
        gestureRecognizer.view?.transform = transform
        lastScale = gestureRecognizer.scale  // Store the previous scale factor for the next pinch gesture call
        scale = currentScale
    }
}

@IBAction func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
    // Move the achor point of the view's layer to the touch point
    // so that movig the view becomes simpler.
    let piece = gestureRecognizer.view
    self.adjustAnchorPoint(gestureRecognizer: gestureRecognizer)

    // Apply the pan to the view's transform.
    if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
        //print((photo.frame.origin.x), (photo.frame.origin.y))
        // Get the distance moved since the last call to this method.
        let translation = gestureRecognizer.translation(in: piece?.superview)

        // Set the translation point to zero so that the translation distance
        // is only the change since the last call to this method.
        piece?.center = CGPoint(x: ((piece?.center.x)!+translation.x), y: ((piece?.center.y)!+translation.y))
        translate_x = (piece?.center.x)!
        translate_y = (piece?.center.y)!
        gestureRecognizer.setTranslation(CGPoint.zero, in: piece?.superview)
    }
}

@IBAction func rotationGesture(_ gestureRecognizer: UIRotationGestureRecognizer) {
    // Move the achor point of the view's layer to the center of the
    // user's two fingers. This creates a more natural looking rotation.
    self.adjustAnchorPoint(gestureRecognizer: gestureRecognizer)

    // Apply the rotation to the view's transform.
    if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
        gestureRecognizer.view?.transform = (gestureRecognizer.view?.transform.rotated(by: gestureRecognizer.rotation))!
        // Set the rotation to 0 to avoid compouding the
        // rotation in the view's transform.
        angle += gestureRecognizer.rotation
        gestureRecognizer.rotation = 0.0
    }
}

func adjustAnchorPoint(gestureRecognizer : UIGestureRecognizer) {
    if gestureRecognizer.state == .began {
        let view = gestureRecognizer.view
        let locationInView = gestureRecognizer.location(in: view)
        let locationInSuperview = gestureRecognizer.location(in: view?.superview)

        // Move the anchor point to the the touch point and change the position of the view
        view?.layer.anchorPoint = CGPoint(x: (locationInView.x / (view?.bounds.size.width)!), y: (locationInView.y / (view?.bounds.size.height)!))
        view?.center = locationInSuperview
    }
}

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let resultView = segue.destination as! SecondViewController
    resultView.finalImage = photo.image!

    resultView.translate_x_ = photo.frame.origin.x + UIScreen.main.bounds.width / 2
    resultView.translate_y_ = photo.frame.origin.y + UIScreen.main.bounds.height / 2
    resultView.scale_ = scale
    resultView.angle_ = angle
}
}

Location parameters are then transferred to the second view controller like this:

override func viewDidLoad() {
    super.viewDidLoad()

    editedPhoto.image = finalImage

    // Translate
    editedPhoto.center = CGPoint(x: (UIScreen.main.bounds.width / 2), y: (UIScreen.main.bounds.height / 2))
    editedPhoto.center = CGPoint(x: (translate_x_), y: (translate_y_))

    // Scale
    editedPhoto.transform = editedPhoto.transform.scaledBy(x: (1/scale_), y: (1/scale_))

    // Rotate
    editedPhoto.transform = editedPhoto.transform.rotated(by: angle_)
}

Upvotes: 0

Views: 465

Answers (1)

DonMag
DonMag

Reputation: 77423

Hmmm... I tried running your code as-is, but I must be missing a setup step. If I don't make any changes to the view (no drag, pinch or rotate), the view in the next viewer is out-of-position.

However....

I think you're running into trouble because you're not accounting for changing the .layer.anchorPoint

Try this -

In SecondViewController, add a new property (it will be set during prepare for segue):

var anchorPoint_: CGPoint = CGPoint.zero

then, in viewDidLoad():

override func viewDidLoad() {
    super.viewDidLoad()

    editedPhoto.image = finalImage

    // add this line
    editedPhoto.layer.anchorPoint = anchorPoint_

    // Translate
    editedPhoto.center = CGPoint(x: (translate_x_), y: (translate_y_))

    // Scale
    editedPhoto.transform = editedPhoto.transform.scaledBy(x: (1/scale_), y: (1/scale_))

    // Rotate
    editedPhoto.transform = editedPhoto.transform.rotated(by: angle_)

}

In FirstViewController, prepare for segue becomes:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let resultView = segue.destination as! SecondViewController
    resultView.finalImage = photo.image!

    resultView.anchorPoint_ = photo.layer.anchorPoint

    resultView.translate_x_ = photo.center.x
    resultView.translate_y_ = photo.center.y

    resultView.scale_ = scale
    resultView.angle_ = angle
}

I think that should fix the issue.

Upvotes: 1

Related Questions