I have been working at this for a while and searched SO thoroughly for a solution but to no avail. Here is what I am trying to do.
I have a UIScrollView on which the user can zoom and pan for 5 seconds. I have a separate CALayer which is not layered on top of the UIScrollView. I want to scale and translate this CALayer's contents to reflect the zoom and pans occurring on the UIScrollView. I want to achieve this via key frame animation CAKeyFrameAnimation
. When I put this into code, the zoom occurs as expected but the position of the content is offset incorrectly.
Here is how I do it in code. Assume that UIScrollView
delegate passes zoom scale and content offset to the following method:
// Remember all zoom params for late recreation via a CAKeyFrameAnimation
- (void) didZoomOrScroll:(float)zoomScale
contentOffset:(CGPoint)scrollViewContentOffset {
CATransform3D scale = CATransform3DMakeScale(zoomScale, zoomScale, 1);
CATransform3D translate = CATransform3DMakeTranslation(-scrollViewContentOffset.x,
-scrollViewContentOffset.y, 0);
CATransform3D concat = CATransform3DConcat(scale, translate);
// _zoomScrollTransforms and _zoomScrollTimes below are of type NSMutableArray
[_zoomScrollTransforms addObject:[NSValue valueWithCATransform3D:concat]];
// secondsElapsed below keeps track of time
[_zoomScrollTimes addObject:[NSNumber numberWithFloat:secondsElapsed]];
}
// Construct layer animation
- (void) constructLayerWithAnimation:(CALayer *)layer {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
animation.duration = 5.0;
animation.values = _zoomScrollTransforms;
// Adjust key frame times to contain fractional durations
NSMutableArray *keyTimes = [[NSMutableArray alloc] init];
for( int i = 0; i < [_zoomScrollTimes count]; i++ ) {
NSNumber *number = (NSNumber *)[_zoomScrollTimes objectAtIndex:i];
NSNumber *fractionalDuration = [NSNumber numberWithFloat:[number floatValue]/animation.duration];
[keyTimes addObject:fractionalDuration];
}
animation.keyTimes = keyTimes;
animation.removedOnCompletion = YES;
animation.beginTime = 0;
animation.calculationMode = kCAAnimationDiscrete;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:animation forKey:nil];
}
When the above layer is animated, the content is zoomed properly but it is positioned incorrectly. The content appears to be x
and y
shifted more than I expected and as a result doesn't exactly retrace the zoom/pans done by the user on the UIScrollView.
Any ideas what I may be doing wrong?
Answer
Ok, i figured out what I was doing wrong. It had to do with the anchor point for the CALayer. Transforms on CALayers are always applied to the anchor point. For CALayers, the anchor point is (0.5, 0.5) by default. So scaling and translations were being conducted along the center point. UIScrollViews, on the other hand, gives offsets from the top left corner of the view. Basically you can think of the anchor point for UIScrollView, for the purposes of thinking about the CGPoint offset value, as being (0.0, 0.0).
So the solution is to set the anchor point of CALayer to (0.0, 0.0). And then everything works as expected.
There are other resources that present this info in a nicer way. See this other question on Stackoverflow that is similar. Also see this article in Apple's documentation that discusses position, anchorpoint and general layer in geometry in great detail.