Search code examples
iosuiimageviewdraguipangesturerecognizeruiswipegesturerecognizer

'Adapt' my current UISwipeGestureRecognizer code for flicking through images to an iPhoto style utilising UIPanGestureRecognizer


I use the following code to swipe left and right with a UISwipeGestureRecognizer to show photos in my app in a similar way to iPhoto.

    CATransition *animation = [CATransition animation];
    [animation setDuration:0.5];
    [animation setType:kCATransitionPush];
    [animation setSubtype:kCATransitionFromRight];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    [[self.imageHolder layer] addAnimation:animation forKey:@"SwitchToView"];

This slides the next image in and the old image out like shown in the pictures below:

Image One:

Image One

Transition:

Transitional View

Image Two:

Image Two

I was wondering how to make this like iPhoto when it dawned on me I should use a UIPanGestureRecognizer instead.

I need to 'control' the animation with my finger in the way iPhoto does, namely by allowing me to start dragging to the next image and then reverse the drag direction. Also, in a similar way to iPhoto I'd like the next image to slide in if I release my finger when more of the next image is showing than the first image (this HASN'T happened in the transition picture above and if I released my finger at this point in iPhoto it would slide back to the first image.

I've never used a UIPanGestureRecognizer before so if anybody can help me out that'd be great.

EDIT:

Using the code provided by Levi, I have created a solution that works nicely with 3 sample images. For anybody else who is interested in using a UIPanGestureRecognizer, the code is:

Interface:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIGestureRecognizerDelegate>
{
int imageIndex;
}

@property (nonatomic, strong) UIImageView* imageView;
@property (nonatomic, strong) UIImageView* leftImageView;
@property (nonatomic, strong) UIImageView* rightImageView;

@end

Implementation:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
[super viewDidLoad];

self->imageIndex = 0;

float xFactor;

UIInterfaceOrientation currentOrientation = self.interfaceOrientation;
if(UIInterfaceOrientationIsLandscape(currentOrientation)){

    xFactor = 256;

}
else{

    xFactor = 0;

}

self.imageView = [[UIImageView alloc] initWithFrame:self.view.frame];
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
self.imageView.image = [UIImage imageNamed:@"69B4356B-1CB2-4A2F-867E-9C086251DF11-12668-0000036A534E9B6D"];
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

CGRect leftFrame = self.view.frame;
leftFrame.origin.x -= leftFrame.size.width+xFactor;
self.leftImageView = [[UIImageView alloc] initWithFrame:leftFrame];
self.leftImageView.contentMode = UIViewContentModeScaleAspectFit;
self.leftImageView.image = [UIImage imageNamed:@"42517D93-F8BF-42E7-BB44-53B099A482AA-12668-0000036A49BCECAA"];
self.leftImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

CGRect rightFrame = self.view.frame;
rightFrame.origin.x += rightFrame.size.width+xFactor;
self.rightImageView = [[UIImageView alloc] initWithFrame:rightFrame];
self.rightImageView.contentMode = UIViewContentModeScaleAspectFit;
self.rightImageView.image = [UIImage imageNamed:@"C6AC2508-243B-464B-A71F-96DD7F18673D-12668-00000369F3AFD3FC"];
self.rightImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

[self.view addSubview:self.imageView];
[self.view addSubview:self.leftImageView];
[self.view addSubview:self.rightImageView];

UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[self.view addGestureRecognizer:recognizer];
}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];

}

- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded) {
    CGRect leftFrame = self.leftImageView.frame;
    CGRect currentFrame = self.imageView.frame;
    CGRect rightFrame = self.rightImageView.frame;

    float duration = 0.0;

    float factor;

    UIInterfaceOrientation currentOrientation = self.interfaceOrientation;
    if(UIInterfaceOrientationIsPortrait(currentOrientation)){

        factor = 768;
    }
    else{
        factor = 1024;
    }

    if (self.imageView.center.x < 0) { // Present the right image
        duration = 0.3 * ABS(self.rightImageView.frame.origin.x / factor);
        leftFrame.origin.x = -2 * factor;
        currentFrame.origin.x = -1 * factor;
        rightFrame.origin.x = 0;
        self->imageIndex = 1;

    } else if (self.imageView.center.x > factor) { // Present the left image
        duration = 0.3 * ABS(self.leftImageView.frame.origin.x / factor);
        leftFrame.origin.x = 0;
        currentFrame.origin.x = factor;
        rightFrame.origin.x = 2 * factor;
        self->imageIndex = -1;

    } else { // leave the middle image
        duration = 0.3 * ABS(self.imageView.frame.origin.x / factor);
        leftFrame.origin.x = -1 * factor;
        currentFrame.origin.x = 0;
        rightFrame.origin.x = factor;
        self->imageIndex = 0;

    }

    [UIView animateWithDuration:duration
                          delay:0
                        options:UIViewAnimationOptionCurveEaseInOut
                     animations:^{
                         self.leftImageView.frame = leftFrame;
                         self.imageView.frame = currentFrame;
                         self.rightImageView.frame = rightFrame;
                     } completion:^(BOOL finished) {
                     }];

} else {
    CGPoint translation = [recognizer translationInView:recognizer.view];
    CGPoint leftCenter = self.leftImageView.center;
    CGPoint currentCenter = self.imageView.center;        
    CGPoint rightCenter = self.rightImageView.center;

    leftCenter.x += translation.x;
    currentCenter.x += translation.x;
    rightCenter.x += translation.x;
    self.leftImageView.center = leftCenter;
    self.imageView.center = currentCenter;
    self.rightImageView.center = rightCenter;

    [recognizer setTranslation:CGPointMake(0, 0) inView:self.imageView];
}

}

- (void)willAnimateRotationToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
                                     duration:(NSTimeInterval)duration
{ 
CGPoint leftCenter = self.leftImageView.center;
CGPoint currentCenter = self.imageView.center;
CGPoint rightCenter = self.rightImageView.center;

if(UIInterfaceOrientationIsPortrait(toInterfaceOrientation)){

    leftCenter.x = 384+(((-self->imageIndex)-1)*768);
    currentCenter.x = 384+((-self->imageIndex)*768);
    rightCenter.x = 384+(((-self->imageIndex)+1)*768);

}
else{

    leftCenter.x = 512+(((-self->imageIndex)-1)*1024);
    currentCenter.x = 512+((-self->imageIndex)*1024);
    rightCenter.x = 512+(((-self->imageIndex)+1)*1024);

}

self.leftImageView.center = leftCenter;
self.imageView.center = currentCenter;
self.rightImageView.center = rightCenter;

}

@end

Solution

  • You can add a UIPanGestureRecognizer to your image view. You would have a method assigned to it where you do something like:

    - (void)handlePan:(UIPanGestureRecognizer *)recognizer {
        if (recognizer.state == UIGestureRecognizerStateEnded) {
    
        } else {
            CGPoint translation = [recognizer translationInView:recognizer.view];
            CGPoint center = recognizer.view.center;
            UIImageView *imageViewToPresent = nil;
            if (translation.x > 0) {
                imageViewToPresent = [self leftImageView];
            } else {
                imageViewToPresent = [self leftImageView];
            }
            CGPoint actualCenter = recognizer.view.center;
            CGPoint nextCenter = imageViewToPresent.center;
            actualCenter.x += translation.x;
            nextCenter.x += translation.x;
            recognizer.view.center = actualCenter;
            imageViewToPresent.view.center = nextCenter;
            [recognizer setTranslation:CGPointMake(0, 0) inView:self.imageView];
        }
    }
    

    In the UIGestureRecognizerStateEnded part, you can decide which picture to show.

    Or just use a ScrollView with paging enabled. It should have the same effect. However, it would require to have all the images loaded at the same time. Using Pan Recognizer, you need 3 image views tops (left, right, current).

    Hope this helps!

    EDIT:

    In the header file you should add 2 more Image Views:

    #import <UIKit/UIKit.h>
    
    @interface ViewController : UIViewController <UIGestureRecognizerDelegate>
    
    @property (nonatomic, strong) UIImageView* imageView;
    @property (nonatomic, strong) UIImageView* leftImageView;
    @property (nonatomic, strong) UIImageView* rightImageView;
    
    @end
    

    The implementation would be:

    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        self.imageView = [[UIImageView alloc] initWithFrame:self.view.frame];
        self.imageView.contentMode = UIViewContentModeScaleAspectFit;
        self.imageView.image = [UIImage imageNamed:@"69B4356B-1CB2-4A2F-867E-9C086251DF11-12668-0000036A534E9B6D"];
        self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
        CGRect leftFrame = self.view.frame;
        leftFrame.origin.x -= leftFrame.size.width;
        self.leftImageView = [[UIImageView alloc] initWithFrame:leftFrame];
        self.leftImageView.contentMode = UIViewContentModeScaleAspectFit;
        self.leftImageView.image = [UIImage imageNamed:@"42517D93-F8BF-42E7-BB44-53B099A482AA-12668-0000036A49BCECAA"];
        self.leftImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
        CGRect rightFrame = self.view.frame;
        rightFrame.origin.x += rightFrame.size.width;
        self.rightImageView = [[UIImageView alloc] initWithFrame:rightFrame];
        self.rightImageView.contentMode = UIViewContentModeScaleAspectFit;
        self.rightImageView.image = [UIImage imageNamed:@"C6AC2508-243B-464B-A71F-96DD7F18673D-12668-00000369F3AFD3FC"];
        self.rightImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
        [self.view addSubview:self.imageView];
        [self.view addSubview:self.leftImageView];
        [self.view addSubview:self.rightImageView];
    
        UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
        [self.view addGestureRecognizer:recognizer];
    }
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
    
    }
    
    - (void)handlePan:(UIPanGestureRecognizer *)recognizer {
        if (recognizer.state == UIGestureRecognizerStateEnded) {
            CGRect leftFrame = self.leftImageView.frame;
            CGRect currentFrame = self.imageView.frame;
            CGRect rightFrame = self.rightImageView.frame;
            float duration = 0.0;
            if (self.imageView.center.x < 0) { // Present the right image
                duration = 0.3 * ABS(self.rightImageView.frame.origin.x / self.view.frame.size.width);
                leftFrame.origin.x = -2 * self.view.frame.size.width;
                currentFrame.origin.x = -1 * self.view.frame.size.width;
                rightFrame.origin.x = 0;
            } else if (self.imageView.center.x > self.view.frame.size.width) { // Present the left image
                duration = 0.3 * ABS(self.leftImageView.frame.origin.x / self.view.frame.size.width);
                leftFrame.origin.x = 0;
                currentFrame.origin.x = self.view.frame.size.width;
                rightFrame.origin.x = 2 * self.view.frame.size.width;
            } else { // leave the middle image
                duration = 0.3 * ABS(self.imageView.frame.origin.x / self.view.frame.size.width);
                leftFrame.origin.x = -1 * self.view.frame.size.width;
                currentFrame.origin.x = 0;
                rightFrame.origin.x = self.view.frame.size.width;
            }
    
            [UIView animateWithDuration:duration
                                  delay:0
                                options:UIViewAnimationOptionCurveEaseInOut
                             animations:^{
                self.leftImageView.frame = leftFrame;
                self.imageView.frame = currentFrame;
                self.rightImageView.frame = rightFrame;
            } completion:^(BOOL finished) {
            }];
        } else {
            CGPoint translation = [recognizer translationInView:recognizer.view];
            CGPoint currnetCenter = self.imageView.center;
            CGPoint leftCenter = self.leftImageView.center;
            CGPoint rightCenter = self.rightImageView.center;
    
            currnetCenter.x += translation.x;
            leftCenter.x += translation.x;
            rightCenter.x += translation.x;
            self.imageView.center = currnetCenter;
            self.leftImageView.center = leftCenter;
            self.rightImageView.center = rightCenter;
    
    
            [recognizer setTranslation:CGPointMake(0, 0) inView:self.imageView];
        }
    
    }
    
    @end
    

    Of course it still need work (e.g. to support more than 3 hardcoded images). It may help you with the rotation issue if you would use IBOutlets, and set the auto resizing masks from the Interface Builder.

    Have fun!