Search code examples
cocoa-touchuiviewcgaffinetransformcgrect

How do I transfer the frame *and* transform from one UIView to another without distortion?


I have a UIView that may have scale and/or rotation transforms applied to it. My controller creates a new controller and passes the view to it. The new controller creates a new view and tries to place it in the same location and rotation as the passed view. It sets the location and size by converting the original view's frame:

CGRect frame = [self.view convertRect:fromView.frame fromView:fromView.superview];
ImageScrollView *isv = [[ImageScrollView alloc]initWithFrame:frame image:image];

This works great, with the scaled size and location copied perfectly. However, if there is a rotation transform applied to fromView, it does not transfer.

So I added this line:

isv.transform = fromView.transform;

That nicely handles transfers the rotation, but also the scale transform. The result is that the scale transform is effectively applied twice, so the resulting view is much too large.

So how do I go about transferring the location (origin), scale, and rotation from one view to another, without doubling the scale?


Edit

Here is a more complete code example, where the original UIImageView (fromView) is being used to size and position a UIScrollView (the ImageScrollView subclass):

CGRect frame = [self.view convertRect:fromView.frame fromView:fromView.superview];
frame.origin.y += pagingScrollView.frame.origin.y;
ImageScrollView *isv = [[ImageScrollView alloc]initWithFrame:frame image:image];
isv.layer.anchorPoint = fromView.layer.anchorPoint;
isv.transform = fromView.transform;
isv.bounds = fromView.bounds;
isv.center = [self.view convertPoint:fromView.center fromView:fromView.superview];
[self.view insertSubview:isv belowSubview:captionView];

And here is the entirety of the configuration in ImageScrollView:

- (id)initWithFrame:(CGRect)frame image:(UIImage *)image {
    if (self = [self initWithFrame:frame]) {
        CGRect rect = CGRectMake(0, 0, frame.size.width, frame.size.height);
        imageLoaded = YES;
        imageView = [[UIImageView alloc] initWithImage:image];
        imageView.frame = rect;
        imageView.contentMode   = UIViewContentModeScaleAspectFill;
        imageView.clipsToBounds = YES;
        [self addSubview:imageView];
    }
    return self;
}

It looks as though the transform causes the imageView to scale up too large, as you can see in this ugly video.


Solution

  • Copy the first view's bounds, center, and transform to the second view.

    Your code doesn't work because frame is a value that is derived from the bounds, center, and transform. The setter for frame tries to do the right thing by reversing the process, but it can't always work correctly when a non-identity transform is set.

    The documentation is pretty clear on this point:

    If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.

    ...

    If the transform property contains a non-identity transform, the value of the frame property is undefined and should not be modified. In that case, you can reposition the view using the center property and adjust the size using the bounds property instead.