Unable to correctly perform a move-and-scale animation on an already translated UIView
.
When the view has not been priorly moved, in order to achieve the desired result, I apply the scale first and then the translation. I animate the resulting transformation using a UIViewPropertyAnimator
: the View scales up or down whilst moving at the same time accordingly.
However, if the view has been moved (translated) from its original position before applying this animated transformation, I failed to achieve the result consisting in scaling up or down whilst moving the View from its new position.
¡Although transform issues are well documented - and I have done my due diligence before submitting the question - I have failed to find a successful solution so far!
In order to facilitate the understanding as well as the resolution of the issue, the codes have been simplified.
extension UIView {
func zoomAndMove(vector: CGPoint, scale: CGPoint){
self.transform = self.transform.concatenating(CGAffineTransform(scaleX: scale.x, y: scale.y).concatenating(CGAffineTransform(translationX: vector.x, y: vector.y))
}
func animateZoomAndMove(from origin: CGPoint, for duration: TimeInterval, cameraZoom: CGPoint, timingFunction: UITimingCurveProvider, controlPoint2: CGPoint(x: 0.8, y: 0.7)) autoplay: Bool = false) -> UIViewPropertyAnimator {
let animator = UIViewPropertyAnimator(duration: duration, timingParameters: timingFunction)
let vector = CGPoint(x: self.frame.midX - origin.x, y: self.frame.midY - origin.y)
animator.addAnimations {
self.zoomAndMove(vector: vector, scale: cameraZoom)
}
if autoplay { animator.startAnimation()}
return animator
}
}
I have tried to modify my code to return a transform that takes into account a previous translation before the zoomAndMove
occurs:
extension UIView {
func zoomAndMove(vector: CGPoint, scale: CGPoint){
if self.transform == CGAffineTransform.identity {
self.transform = self.transform.concatenating(CGAffineTransform(scaleX: scale.x, y: scale.y).concatenating(CGAffineTransform(translationX: vector.x, y: vector.y))
} else {
let preTransform = self.transform
self.transform = CGAffineTransform(a: scale.x, b: 0, c: 0, d: scale.y, tx: vector.x + preTransform.tx, ty: vector.y + preTransform.ty)
}
}
This code does not result in the desired effect: the view jumps to a new location, scales correctly and moves "randomly".
I am definitely missing something - I might be aiming at the wrong end result matrix - but all in all I am currently stuck.
Should anyone have a clue on how to perform such a simple task as scaling and moving a UIView from an already translated UIView, I would be most grateful for their input!
Best,
A picture can be worth a 1,000 words, so here is what happens when I try to implement the various suggestions that have been kindly made so far (in particular the .scaledBy(:)
method):
You can notice the final transformation is right, but the animation is not.
First of all, thanks to @gmogames for taking the time to offer suggestions. It always helps to be able to exchange ideas!!!
The issue had indeed something to do with resetting the anchorPoint (or the center) of the view before applying the new transformation so that the animation runs correctly. Therefore, using a much simpler example of scaling a view after having moved it, here is what the new method looks like:
extension UIView {
func scaleView(scaleFactor: CGPoint, duration: TimeInterval) {
// store the view original center
let oCenter = self.center
// get the current transformation
let cTransform = self.transform
// set the new center of the view
self.center = CGPoint(x: self.frame.midX, y: self.frame.midY)
// clears the transform matrix from potential prior translation
// Note that you need to take into account potential prior scale of the view into the translation vector!!
self.transform = self.transform.translatedBy(x: -cTransform.tx / cTransform.a, y: -cTransform.ty / cTransform.d)
// Animates the transformation
let animator = UIViewPropertyAnimator(duration: duration, timingParameters: UICubicTimingParameters(controlPoint1: CGPoint(x: 0, y: 0), controlPoint2: CGPoint(x: 1, y: 1))
animator.addAnimations {
self.transform = self.transform.scaledBy(x: scaleFactor.x, y: scaleFactor.y)
}
// resets the View to its original center and apply the transformation so that the view stays in the right end position
animator.addCompletion { (position) in
if position == UIViewAnimatingPosition.end {
self.center = oCenter
self.transform = cTransform.scaledBy(x: scaleFactor.x, y: scaleFactor.y)
}
}
animator.startAnimation()
}
}
Here the result in animation: move + scale + scale + revert to original