22

Say, I have two CGRects, CGRect A and CGRect B. My UIView's frame is the same as CGRect B, but I want to create an animation showing the UIView transitioning from frame A to B.

I'm trying to do this by changing the transform property of the UIView, so I don't have to mess around with its frame too much. However, I need the CGAffineTransform to make this possible. What is the best way to calculate this transform?

SpacyRicochet
  • 2,269
  • 2
  • 24
  • 39
  • Is there any pressing reason for not simply animating frame property from CGRect A to CGRect B? – Rok Jarc May 23 '12 at 15:37
  • 2
    Not pressing. But the reason is that keeping the view's frame static and using transforms to move it around significantly simplifies keeping track of it during more complex state changes (such as auto-rotation). – SpacyRicochet May 24 '12 at 07:34

6 Answers6

44

The previous answers didn't work for me. This should work:

Swift 5 (extension)

extension CGAffineTransform {
    init(from source: CGRect, to destination: CGRect) {
        self = CGAffineTransform.identity
            .translatedBy(x: destination.midX - source.midX, y: destination.midY - source.midY)
            .scaledBy(x: destination.width / source.width, y: destination.height / source.height)
    }
}

Swift 4

func transformFromRect(from source: CGRect, toRect destination: CGRect) -> CGAffineTransform {
    return CGAffineTransform.identity
        .translatedBy(x: destination.midX - source.midX, y: destination.midY - source.midY)
        .scaledBy(x: destination.width / source.width, y: destination.height / source.height)
}

Swift

func transformFromRect(from: CGRect, toRect to: CGRect) -> CGAffineTransform {
    let transform = CGAffineTransformMakeTranslation(CGRectGetMidX(to)-CGRectGetMidX(from), CGRectGetMidY(to)-CGRectGetMidY(from))
    return CGAffineTransformScale(transform, to.width/from.width, to.height/from.height)
}

Objective-C

+ (CGAffineTransform) transformFromRect:(CGRect)sourceRect toRect:(CGRect)finalRect {
    CGAffineTransform transform = CGAffineTransformIdentity;
    transform = CGAffineTransformTranslate(transform, -(CGRectGetMidX(sourceRect)-CGRectGetMidX(finalRect)), -(CGRectGetMidY(sourceRect)-CGRectGetMidY(finalRect)));
    transform = CGAffineTransformScale(transform, finalRect.size.width/sourceRect.size.width, finalRect.size.height/sourceRect.size.height);

    return transform;
}
José Manuel Sánchez
  • 5,215
  • 2
  • 31
  • 24
8

I haven't been able to find a convenience method of any kind for this, so I resorted to tried and true matrix calculations to achieve this.

Given a CGRect A and CGRect B, to calculate the transformation needed to go from A to B, do the following:

CGAffineTransform transform = CGAffineTransformTranslate(CGAffineTransformIdentity, -A.origin.x, -A.origin.y);
transform = CGAffineTransformScale(transform, 1/A.size.width, 1/A.size.height);
transform = CGAffineTransformScale(transform, B.size.width, B.size.height);
transform = CGAffineTransformTranslate(transform, B.origin.x, B.origin.y);
SpacyRicochet
  • 2,269
  • 2
  • 24
  • 39
4

Here's a variation on josema.vitaminew answer that also considers aspect ratio (useful if you are working with videos or images):

+ (CGAffineTransform)transformFromRect:(CGRect)sourceRect toRect:(CGRect)finalRect keepingAspectRatio:(BOOL)keepingAspectRatio {
    CGAffineTransform transform = CGAffineTransformIdentity;

    transform = CGAffineTransformTranslate(transform, -(CGRectGetMidX(sourceRect)-CGRectGetMidX(finalRect)), -(CGRectGetMidY(sourceRect)-CGRectGetMidY(finalRect)));

    if (keepingAspectRatio) {
        CGFloat sourceAspectRatio = sourceRect.size.width/sourceRect.size.height;
        CGFloat finalAspectRatio = finalRect.size.width/finalRect.size.height;

        if (sourceAspectRatio > finalAspectRatio) {
            transform = CGAffineTransformScale(transform, finalRect.size.height/sourceRect.size.height, finalRect.size.height/sourceRect.size.height);
        } else {
            transform = CGAffineTransformScale(transform, finalRect.size.width/sourceRect.size.width, finalRect.size.width/sourceRect.size.width);
        }
    } else {
        transform = CGAffineTransformScale(transform, finalRect.size.width/sourceRect.size.width, finalRect.size.height/sourceRect.size.height);
    }

    return transform;
}
Community
  • 1
  • 1
kanobius
  • 756
  • 9
  • 12
4

A nice Swift 3 solution:

extension CGAffineTransform {
    init(from: CGRect, toRect to: CGRect) {
        self.init(translationX: to.midX-from.midX, y: to.midY-from.midY)
        self = self.scaledBy(x: to.width/from.width, y: to.height/from.height)
    }
}
Paul Olson
  • 87
  • 3
1

The last line in the above code snippet needs to be changed as follows:

transform = CGAffineTransformTranslate (transform, B.origin.x*A.size.width/B.size.width, B.origin.y*A.size.height/B.size.height);

HarryS
  • 323
  • 3
  • 13
0

I started to use Kanobius answer, but either I'm misunderstanding his use of the term "aspect ratio" or he is, because it didn't perform as I expected. Here's my version that keeps the aspect ratio of the original view (by applying the scaling to both H and W) to achieve an "aspect fit" behavior

+ (CGAffineTransform)transformFromRect:(CGRect)sourceRect toRect:(CGRect)finalRect keepingAspectRatio:(BOOL)keepingAspectRatio
{
    CGAffineTransform transform = CGAffineTransformIdentity;

    // since the scale transform works around the midpoints, align them before doing the transform.
    transform = CGAffineTransformTranslate(transform, -(CGRectGetMidX(sourceRect)-CGRectGetMidX(finalRect)), -(CGRectGetMidY(sourceRect)-CGRectGetMidY(finalRect)));

    CGFloat originalHeight = sourceRect.size.height;
    CGFloat scaledHeight = finalRect.size.height;
    CGFloat hScalePercentage = scaledHeight / originalHeight;

    CGFloat originalWidth = sourceRect.size.width;
    CGFloat scaledWidth = finalRect.size.width;
    CGFloat wScalePercentage = scaledWidth / originalWidth;

    if (keepingAspectRatio)  
    {
        CGFloat scalePercentage = MIN(hScalePercentage, wScalePercentage);   // the most amount of scaling == smallest float number
        transform =  CGAffineTransformScale(transform, scalePercentage, scalePercentage);
    }
    else
    {
        transform = CGAffineTransformScale(transform, wScalePercentage, hScalePercentage);
    }

    return transform;
}
software evolved
  • 4,314
  • 35
  • 45