Reputation: 496
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
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