I need to translate and scale a UIView from the center of the screen to the upper right corner.
_____________________________
| |
| |
| _________ |
| | | | START
| | | |
| |_________| |
| |
| |
|____________________________|
_____________________________
| __ |
| |__| |
| |
| | END
| |
| |
| |
| |
|____________________________|
I verified that my individual calculations for scale and translation were accurate. But once they are put together, they conflict with each other. The following code makes the view end up too close to the center.
CGFloat finalPadding = 10.0f;
CGFloat finalScale = 46.0f / 170.0f;
CGFloat finalX = self.view.frame.size.width
- self.platformProgressView.frame.size.width * finalScale
- finalPadding;
CGFloat finalY = finalPadding;
CGFloat deltaX = finalX - self.platformProgressView.frame.origin.x;
CGFloat deltaY = finalY - self.platformProgressView.frame.origin.y;
[UIView
animateWithDuration:1.0
delay:1.0
options:UIViewAnimationOptionCurveEaseOut
animations:^(void){
self.platformProgressView.transform = CGAffineTransformConcat(
CGAffineTransformMakeTranslation(deltaX, deltaY),
CGAffineTransformMakeScale(finalScale, finalScale)
);
}
completion:^(BOOL finished) {
}
];
Final effect:
_____________________________
| |
| |
| __ |
| |__| |
| |
| |
| |
| |
|____________________________|
Changing the order of the multiplication causes the view to veer off the right edge of the screen.
self.platformProgressView.transform = CGAffineTransformConcat(
CGAffineTransformMakeScale(finalScale, finalScale),
CGAffineTransformMakeTranslation(deltaX, deltaY)
);
Final effect (projected):
_____________________________
| |
| | __
| | |__|
| |
| |
| |
| |
| |
|____________________________|
Applying them seperately causes an abrupt jump and the final position is even worse.
self.platformProgressView.transform = CGAffineTransformMakeTranslation(deltaX, deltaY);
self.platformProgressView.transform = CGAffineTransformMakeScale(finalScale, finalScale);
Final effect:
_____________________________
| |
| |
| |
| |
| __ |
| |__| |
| |
| |
|____________________________|
The main thing to realize is that the origin for transforms is the center point of the view rectangle. This is best shown with an example.
First we translate the view. v1 is the view at it's original position, v2 is the view at its translated position. p is the padding that you desire (finalPadding
in your code). c marks the center point of the view.
+--------------------------------+
| ^ |
| | p |
| v |
| +- v2 --------+ |
| | | |
| | c |<->|
| | | p |
| +-------------+ |
| |
| |
| +- v1 --------+ |
| | | |
| | c | |
| | | |
| +-------------+ |
| |
+--------------------------------+
Next we scale the view. v3 is the view at its scaled position. Note how the center point for v3 remains unchanged while the dimensions of the view around it shrink. Although the dimensions are correct, the positioning of the view and the resulting padding p' are wrong.
+--------------------------------+
| ^ |
| | p' |
| | |
| v |
| +- v3 --+ |
| | c |<---->|
| +-------+ p' |
| |
| |
| +- v1 --------+ |
| | | |
| | c | |
| | | |
| +-------------+ |
| |
+--------------------------------+
Now that we know how scaling works, we need to fix the code where you calculate the translation deltas. Here is how to do it right:
CGRect windowFrame = self.window.frame;
CGRect viewFrame = self.platformProgressView.frame;
CGPoint finalCenter = CGPointZero;
finalCenter.x = (windowFrame.size.width
- (viewFrame.size.width * finalScale) / 2.0f
- finalPadding);
finalCenter.y = (finalPadding
+ (viewFrame.size.height * finalScale) / 2.0f);
CGPoint viewCenter = self.platformProgressView.center;
CGFloat deltaX = finalCenter.x - viewCenter.x;
CGFloat deltaY = finalCenter.y - viewCenter.y;
Finally, as you have noted yourself, the order in which transformations are concatenated with CGAffineTransformConcat
matters. In your first attempt you have the sequence 1) transform + 2) scale. The result is that the scale transform - which comes later in the sequence - affects the deltas specified for the translate transform.
There are 2 solution here: Either your own second attempt, where you reverse the sequence so that it becomes 1) scale + 2) transform. Or you use the helper function which I suggested in the first revision of my answer. So this
self.platformProgressView.transform = CGAffineTransformConcat(
CGAffineTransformMakeScale(finalScale, finalScale),
CGAffineTransformMakeTranslation(deltaX, deltaY)
);
is equivalent to
CGAffineTransform CGAffineTransformMakeScaleTranslate(CGFloat sx, CGFloat sy, CGFloat dx, CGFloat dy)
{
return CGAffineTransformMake(sx, 0.0f, 0.0f, sy, dx, dy);
}
self.platformProgressView.transform = CGAffineTransformMakeScaleTranslate(finalScale, finalScale, deltaX, deltaY);
If you're unhappy about the center point being the origin for the view's transforms you can change this by setting the anchorPoint
property of the view's CALayer. The default anchor point is at 0.5/0.5, which represents the center of the view rectangle. Obviously, the anchor point is not a coordinate but a kind of multiplication factor.
Your original calculation for the translation deltas was correct if we assume that the anchor point is in the upper-left corner of the view. So if you do this
self.platformProgressView.layer.anchorPoint = CGPointMake(0.0f, 0.0f)
you can keep your original calculations.
There is probably a lot more of information material out there, but the WWDC 2011 video Understanding UIKit Rendering is something that I recently watched and that greatly helped me improve my understanding of the relationship between bounds, center, transform and frame.
Also, if you are going to change the CALayer anchorPoint
property, you should probably read the property documentation in the CALayer class reference first.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With