UIImageView – Tear animation

In mid 2013 we developed the native profile picture upload for our iOS application. Since iOS6 was current, we loved to have a skeumorphic animation, if a user cancelled to upload the picture or deletes it from his own profile.

It seemed obvious to us, to tear the image in this very situation. As we found no Open Source code, that would’ve helped us to get the thing done, we built this animation on our own.

That’s, how it looked in the end:

Reusability is key

When we craft our software, a core focus is the reusability of our code. So, we wrote an Objective-c category, that can be easily dropped in and which enables everyone to tear any UIImageView with just one call:

- (void)tearWithDuration:(NSTimeInterval)duration
              animations:(void (^)(void))animations
              completion:(void (^)(BOOL finished))completion;

How does the category UIImageView+tear work?

To explain the animation it is best to break it into 3 parts:

  1. Random tear path creation
  2. Creating masked copies of the original image and delete the original image reference
  3. Animate the masked images

Let’s tear an image of me, Sascha, the writer of this article:

iostear-image

1. Random tear path creation

The starting point for the path is the center top of the imageView. Starting there, we are going down 1 pixel and randomly between 0 and 3 pixels left or right, since we reach the bottom of the image:

float frameWidth = self.frame.size.width;
float midX = frameWidth / 2.0;

CGPathRef tear = [self randomTear:3 startAtX:midX];

Example for CGPathRef tear, after creation with randomTear:startAtX: method
Example CGPath tear

- (CGPathRef)randomTear:(int)maxDistance startAtX:(float)x
{
    CGMutablePathRef tear = CGPathCreateMutable();

    CGPathMoveToPoint(tear, NULL, 0, 0);
    
    for (NSUInteger y = 0; y < self.frame.size.height; ++y)
    {
        CGPathAddLineToPoint(tear, NULL, x, y);
        
        int randomDirection = [self randomSign];
        int randomDistance = arc4random_uniform(maxDistance);
        x += (randomDirection * randomDistance);
    }

    return tear;
}

2. Creating masked copies of the original image and delete the original image reference

We decided, that it would be easier to handle, if we keep the original size of the image and create masked copies, so positioning becomes no issue.

CALayer *leftMaskPath = [self shapeWithTear:tear cornerX:0 tearStartAtX:midX];
CALayer *rightMaskPath = [self shapeWithTear:tear cornerX:frameWidth tearStartAtX:midX];

Example for CALayer *leftMaskPath and CALayer *rightMaskPath after applying shapeWithTear:cornerX:tearStartAtX: method

Example CALayer leftMaskPath and Example CALayer rightMaskPath

UIImageView *leftImageHalf = [self copyViewWithMask:leftMaskPath];
[self addSubview:leftImageHalf];
    
UIImageView *rightImageHalf = [self copyViewWithMask:rightMaskPath];
[self addSubview:rightImageHalf];
    
self.image = nil;

Example for UIImageView *leftImageHalf and UIImageView *rightImageHalf after applying copyViewWithMask: method

Example UIImageView leftImageHalf and Example UIImageView rightImageHalf

Helper methods for creating masked copies


- (UIImageView *)copyViewWithMask:(CALayer *)mask
{
    UIImageView *imageViewCopy = [[UIImageView alloc] initWithImage:self.image];
    imageViewCopy.frame = self.bounds;
    imageViewCopy.layer.masksToBounds = YES;
    imageViewCopy.contentMode = self.contentMode;
    imageViewCopy.layer.mask = mask;

    return imageViewCopy;
}


- (CAShapeLayer *)shapeWithTear:(CGPathRef)tear
                     cornerX:(float)cornerX
                   tearStartAtX:(float)midX
{
    CGRect rect = CGPathGetBoundingBox(tear);
    
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, cornerX, 0);
    CGPathAddLineToPoint(path, NULL, midX, 0);
    CGPathAddPath(path, NULL, tear);
    CGPathAddLineToPoint(path, NULL, cornerX, rect.size.height);
    CGPathAddLineToPoint(path, NULL, cornerX, 0);
    
    CAShapeLayer *shapeLayer = [CAShapeLayer new];
    shapeLayer.borderColor = [UIColor blackColor].CGColor;
    shapeLayer.borderWidth = 3;
    shapeLayer.path = path;

    CGPathRelease(path);
    
    return shapeLayer;
}

3. Animate the masked images

The first idea was, that in the moment both sides get torn apart an animation starts that shows both sides of the image moving away from each other. During implementation though, we saw the basic animation, that just animates and rotates the two views outside the viewport and really liked it. After only smaller refinements we decided to stick with this more basic version.

[UIView animateWithDuration:duration
                 animations:^{
                     float targetX = [UIScreen mainScreen].bounds.size.width + midX;

                     leftImageHalf.frame = CGRectOffset(leftImageHalf.frame, -targetX, 0);
                     leftImageHalf.transform = CGAffineTransformMakeRotation(-M_PI/2);

                     rightImageHalf.frame = CGRectOffset(rightImageHalf.frame, targetX, 0);
                     rightImageHalf.transform = CGAffineTransformMakeRotation(M_PI/2);
                     
                     animations();
                 }
                 completion:^(BOOL finished)
 {
     [leftImageHalf removeFromSuperview];
     [rightImageHalf removeFromSuperview];
     
     completion(finished);
 }];

Behavior of the animation

iostear-destruction

We like to share our experience with you

There should be no need to reinvent the wheel. So we decided to give the UIImageView tear animation as is it Open Source to use and modify in commercial and non-commercial products. Surely, we would love to see you using the category. We would be even happier, if you leave a comment and tell us where you used it.

Download UIImageView+tear on Github

Ein Gedanke zu “UIImageView – Tear animation

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s