I have a few views between which I want to swipe in an iOS program. Right now, I'm swiping between them using a modal style, with a cross dissolve animation. However, I want to have a swiping/sliding animation like you see on the home screen and such. I have no idea how to code such a transition, and the animation style isn't an available modal transition style. Can anyone give me an example of the code? It doesn't need to be a modal model or anything, I just found that easiest.
-
If you need a true custom transition, full tutorial for 2018 https://stackoverflow.com/a/48081504/294884 – Fattie Jan 24 '18 at 15:52
-
extremely easy with PAGING in iOS: http://stackoverflow.com/a/26024779/294884 – Fattie Mar 03 '21 at 15:48
-
If you need to actually connect/sync the animation to your finger, here's a good article on UIPercentDrivenInteractiveTransition https://theswiftdev.com/ios-custom-transition-tutorial-in-swift/ – Fattie Nov 07 '22 at 23:57
3 Answers
Since iOS 7, if you want to animate the transition between two view controllers, you would use custom transitions, as discussed in WWDC 2013 video Custom Transitions Using View Controllers. For example, to customize the presentation of a new view controller you would:
The destination view controller would specify the
self.modalPresentationStyle
andtransitioningDelegate
for the presentation animation:- (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { self.modalPresentationStyle = UIModalPresentationCustom; self.transitioningDelegate = self; } return self; }
This delegate (in this example, the view controller itself) would conform to
UIViewControllerTransitioningDelegate
and implement:- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [[PresentAnimator alloc] init]; } // in iOS 8 and later, you'd also specify a presentation controller - (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source { return [[PresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting]; }
You would implement an animator that would perform the desired animation:
@interface PresentAnimator : NSObject <UIViewControllerAnimatedTransitioning> @end @implementation PresentAnimator - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.5; } // do whatever animation you want below - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; [[transitionContext containerView] addSubview:toViewController.view]; CGFloat width = fromViewController.view.frame.size.width; CGRect originalFrame = fromViewController.view.frame; CGRect rightFrame = originalFrame; rightFrame.origin.x += width; CGRect leftFrame = originalFrame; leftFrame.origin.x -= width / 2.0; toViewController.view.frame = rightFrame; toViewController.view.layer.shadowColor = [[UIColor blackColor] CGColor]; toViewController.view.layer.shadowRadius = 10.0; toViewController.view.layer.shadowOpacity = 0.5; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ fromViewController.view.frame = leftFrame; toViewController.view.frame = originalFrame; toViewController.view.layer.shadowOpacity = 0.5; } completion:^(BOOL finished) { [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } @end
You'd also implement a presentation controller that takes care of cleaning up the view hierarchy for you. In this case, since we're completely overlaying the presenting view, we can remove it from the hierarchy when the transition is done:
@interface PresentationController: UIPresentationController @end @implementation PresentationController - (BOOL)shouldRemovePresentersView { return true; } @end
Optionally, if you wanted this gesture to be interactive, you would also:
Create an interaction controller (typically a
UIPercentDrivenInteractiveTransition
);Have your
UIViewControllerAnimatedTransitioning
also implementinteractionControllerForPresentation
, which obviously would return the aforementioned interaction controller;Have a gesture (or what have you) that updates the
interactionController
This is all described in the aforementioned Custom Transitions Using View Controllers.
For example of customizing navigation controller push/pop, see Navigation controller custom transition animation
Below, please find a copy of my original answer, which predates custom transitions.
@sooper's answer is correct, that CATransition can yield the effect you're looking for.
But, by the way, if your background isn't white, the kCATransitionPush
of CATransition
has an weird fade in and fade out at the end of the transition that can be distracting (when navigating between images, especially, it lends it a slightly flickering effect). If you suffer from this, I found this simple transition to be very graceful: You can prepare your "next view" to be just off screen to the right, and then animate the moving of the current view off screen to the left while you simultaneously animate the next view to move to where the current view was. Note, in my examples, I'm animating subviews in and out of the main view within a single view controller, but you probably get the idea:
float width = self.view.frame.size.width;
float height = self.view.frame.size.height;
// my nextView hasn't been added to the main view yet, so set the frame to be off-screen
[nextView setFrame:CGRectMake(width, 0.0, width, height)];
// then add it to the main view
[self.view addSubview:nextView];
// now animate moving the current view off to the left while the next view is moved into place
[UIView animateWithDuration:0.33f
delay:0.0f
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
[nextView setFrame:currView.frame];
[currView setFrame:CGRectMake(-width, 0.0, width, height)];
}
completion:^(BOOL finished){
// do whatever post processing you want (such as resetting what is "current" and what is "next")
}];
Clearly, you'd have to tweak this for how you've got your controls all set up, but this yields a very simple transition, no fading or anything like that, just a nice smooth transition.
A caveat: First, neither this example, nor the CATransition
example, are quite like the SpringBoard home screen animation (that you talked about), which is continuous (i.e. if you're half way through a swipe, you can stop and go back or whatever). These transitions are ones that once to initiate them, they just happen. If you need that realtime interaction, that can be done, too, but it's different.
Update:
If you want to use a continuous gesture that tracks the user's finger you can use UIPanGestureRecognizer
rather than UISwipeGestureRecognizer
, and I think animateWithDuration
is better than CATransition
in that case. I modified my handlePanGesture
to change the frame
coordinates to coordinate with the user's gesture, and then I modified the above code to just complete the animation when the user let go. Works pretty well. I don't think you can do that with CATransition
very easily.
For example, you might create a gesture handler on the controller's main view:
[self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]];
And the handler might look like:
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
// transform the three views by the amount of the x translation
CGPoint translate = [gesture translationInView:gesture.view];
translate.y = 0.0; // I'm just doing horizontal scrolling
prevView.frame = [self frameForPreviousViewWithTranslate:translate];
currView.frame = [self frameForCurrentViewWithTranslate:translate];
nextView.frame = [self frameForNextViewWithTranslate:translate];
// if we're done with gesture, animate frames to new locations
if (gesture.state == UIGestureRecognizerStateCancelled ||
gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateFailed)
{
// figure out if we've moved (or flicked) more than 50% the way across
CGPoint velocity = [gesture velocityInView:gesture.view];
if (translate.x > 0.0 && (translate.x + velocity.x * 0.25) > (gesture.view.bounds.size.width / 2.0) && prevView)
{
// moving right (and/or flicked right)
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
prevView.frame = [self frameForCurrentViewWithTranslate:CGPointZero];
currView.frame = [self frameForNextViewWithTranslate:CGPointZero];
}
completion:^(BOOL finished) {
// do whatever you want upon completion to reflect that everything has slid to the right
// this redefines "next" to be the old "current",
// "current" to be the old "previous", and recycles
// the old "next" to be the new "previous" (you'd presumably.
// want to update the content for the new "previous" to reflect whatever should be there
UIView *tempView = nextView;
nextView = currView;
currView = prevView;
prevView = tempView;
prevView.frame = [self frameForPreviousViewWithTranslate:CGPointZero];
}];
}
else if (translate.x < 0.0 && (translate.x + velocity.x * 0.25) < -(gesture.view.frame.size.width / 2.0) && nextView)
{
// moving left (and/or flicked left)
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
nextView.frame = [self frameForCurrentViewWithTranslate:CGPointZero];
currView.frame = [self frameForPreviousViewWithTranslate:CGPointZero];
}
completion:^(BOOL finished) {
// do whatever you want upon completion to reflect that everything has slid to the left
// this redefines "previous" to be the old "current",
// "current" to be the old "next", and recycles
// the old "previous" to be the new "next". (You'd presumably.
// want to update the content for the new "next" to reflect whatever should be there
UIView *tempView = prevView;
prevView = currView;
currView = nextView;
nextView = tempView;
nextView.frame = [self frameForNextViewWithTranslate:CGPointZero];
}];
}
else
{
// return to original location
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
prevView.frame = [self frameForPreviousViewWithTranslate:CGPointZero];
currView.frame = [self frameForCurrentViewWithTranslate:CGPointZero];
nextView.frame = [self frameForNextViewWithTranslate:CGPointZero];
}
completion:NULL];
}
}
}
That uses these simple frame
methods that you'd presumably define for your desired UX:
- (CGRect)frameForPreviousViewWithTranslate:(CGPoint)translate
{
return CGRectMake(-self.view.bounds.size.width + translate.x, translate.y, self.view.bounds.size.width, self.view.bounds.size.height);
}
- (CGRect)frameForCurrentViewWithTranslate:(CGPoint)translate
{
return CGRectMake(translate.x, translate.y, self.view.bounds.size.width, self.view.bounds.size.height);
}
- (CGRect)frameForNextViewWithTranslate:(CGPoint)translate
{
return CGRectMake(self.view.bounds.size.width + translate.x, translate.y, self.view.bounds.size.width, self.view.bounds.size.height);
}
Your particular implementation will undoubtedly vary, but hopefully this illustrates the idea.
Having illustrated all of this (supplementing and clarifying this old answer), I should point out that I don't use this technique any more. Nowadays, I generally use a UIScrollView
(with "paging" turned on) or (in iOS 6) a UIPageViewController
. This gets you out of the business of writing this sort of gesture handler (and enjoying extra functionality like scroll bars, bouncing, etc.). In the UIScrollView
implementation, I just respond to the scrollViewDidScroll
event to make sure that I'm lazily loading the necessary subview.
-
hey @Rob, nicely done, it's just as I have done. And I really wanted to get the UIPanGestureRecognizer working nicely. Any chance of a piece of code, specially with the animation? thks a lot! – StinkyCat Jul 09 '13 at 14:31
-
-
@StinkyCat When I first wrote this, I was manually adding subviews to a window moving them, etc. I've subsequently came to prefer a couple of simpler approaches solutions, namely `UIPageViewController` (iOS 6.0 and above, if you want the sliding views) or `UIScrollView` (in which you turn on paging, and then respond to the `scrollViewWillScroll` delegate method to figure out which views to add as subviews. These are nice approaches because it gets you out of the process of creating your own gesture recognizers, you get scrollbars or page counters and the like. – Rob Jul 11 '13 at 10:27
-
But, I've also done it with the three different views (without containers), creating these subviews and moving them around upon gestures, myself, and then when I let go, figure out whether you scrolled far enough to count as a scroll to the next view or not, and I animate from there to the desired resting place. You don't get UI elements like scrollbars, nor do you get the bouncing effect at the ends without a little work. – Rob Jul 11 '13 at 10:31
-
Ok thks @Rob. I've just used 2 views: one which is the actual view seen on the screen and the other, will appear whether from left or right depending on the swipe direction. Apart from not having bouncing effect and the view's movement not following the finger's, it's flawless :) – StinkyCat Jul 11 '13 at 10:43
-
1@StinkyCat I've expanded my answer as to how you could use pan gesture recognizer to drag subviews and then animate them when you let go. Having said that, I will often use `UIScrollView` to enjoy bounce effects and the like. Anyway, see expanded answer above. – Rob Jul 12 '13 at 18:30
-
excellent explanation! By now I've got no time to implement this, but I'm sure that if in the end of the project I'll have time, I'll be back to check this post, since from reading it I was able to clearly see how it would work :) nice job! +1 – StinkyCat Jul 14 '13 at 15:15
-
@Rob Would you please provide a little gif or sth. like this, to be able to understand the result of your code? – Ömer Faruk Almalı Apr 09 '14 at 18:05
-
@Rob Because I'm about to implement such animation and need to see the result if you can provide, that will be appreciated so much. – Ömer Faruk Almalı Apr 09 '14 at 18:13
-
I see "Variable is not assignable (missing __block type specifier)" for prevView = currView; currView = nextView; nextView = tempView;. What have I done wrong? – JZ. Jul 19 '14 at 16:40
-
I defined __block variables, such as this: __block UITableView *nextView = nextViewC.tableView but I do not see a table view to transition to? what considerations are there for tables? – JZ. Jul 19 '14 at 16:56
-
The only animation that is not black is, self.view. when I instantiate other views they are black. MyView *nextViewC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"JZ"]; __block UIView *nextView = nextViewC.view; – JZ. Jul 19 '14 at 18:27
-
-
Maybe a better question than all of the above. How does one define: prevView and nextView? and Where should this function, handlePan live? – JZ. Jul 19 '14 at 18:34
-
I had defined them as instance variables. Or, perhaps better, you could define properties and then set `self.prevView`, `self.nextView`, etc. – Rob Jul 19 '14 at 21:10
-
You can always refer to the revision history of a question to see what was added when. Bottom line, the custom interactive transitions for iOS 7 at the start is the most up-to-date portion of this answer. If I were to update this today, I might reference that iOS 8 has extended this approach with the introduction of presentation controllers. The idea is the very similar to the custom transitions for iOS 7, though. See about 20 min into the WWDC 2014 [View Controller Enhancements for iOS 8](https://developer.apple.com/videos/wwdc/2014/?id=214) video. If you only need iOS 8, then check that out. – Rob Mar 09 '15 at 13:00
You could create a CATransition
animation. Here's an example of how you can slide a second view (from left) into the screen whilst pushing the current view out:
UIView *theParentView = [self.view superview];
CATransition *animation = [CATransition animation];
[animation setDuration:0.3];
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromLeft];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[theParentView addSubview:yourSecondViewController.view];
[self.view removeFromSuperview];
[[theParentView layer] addAnimation:animation forKey:@"showSecondViewController"];

- 5,991
- 6
- 40
- 65
If you want the same page scroll/swipe effect as the springboard have when switching page, then why not just use a UIScrollView
?
CGFloat width = 320;
CGFloat height = 400;
NSInteger pages = 3;
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0,0,width,height)];
scrollView.contentSize = CGSizeMake(width*pages, height);
scrollView.pagingEnabled = YES;
And then use UIPageControl
to get these dots. :)

- 2,662
- 1
- 23
- 23