Nick Harris

Archive for February 2012

iOS Slide-out Navigation Code

Ken Yarmosh has a great post entitled New iOS Design Pattern: Slide-out Navigation in which he does a great job describing this new design pattern and how different applications are using it.

I personally like the approach so I decided to figure out how I would implement it.  I have no idea if any of the apps mentioned in Ken’s post actually do it this way, but it works.

Slide-out Navigation

The idea here is use the Application Delegate as the controller between the current content view of the application and the menu view.  When a view in the application needs to show the navigation menu, it can call a method on the App Delegate that handles all the work of showing and restoring the menu and content views.

The “illusion” of the content view sliding off to reveal the menu view is done by first grabbing a screenshot of the current content view and passing that off to the MenuViewController.  The MenuViewController has a UIImageView that it populates with that screenshot.  Its this UIImageView that acts as the content view overlay. It makes animation smooth using a single flat image rather then trying to animate an actual view back and forth.


The AppDelegate acts as the central controller whenever any views need to show the menu view. It gets two new properties:

@property (strong, nonatomic) ContentViewController *contentViewController;

@property (strong, nonatomic) MenuViewController *menuViewController;


The contentViewController gets used as a temp holding place while the menuViewController is visible.  Having the two of them in the app delegate makes switching the window.rootViewController easier.  I do this in two new methods on the AppDelegate:



// before swaping the views, we'll take a "screenshot" of the current view

// by rendering its CALayer into the an ImageContext then saving that off to a UIImage

CGSize viewSize = self.contentViewController.view.bounds.size;

UIGraphicsBeginImageContextWithOptions(viewSize, NO, 1.0);

[self.contentViewController.view.layer renderInContext:UIGraphicsGetCurrentContext()];


// Read the UIImage object

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();



// pass this image off to the MenuViewController then swap it in as the rootViewController

self.menuViewController.screenShotImage = image;

self.window.rootViewController = self.menuViewController;





// all animation takes place elsewhere. When this gets called just swap the contentViewController in

self.window.rootViewController = self.contentViewController;




The MenuViewController handles all the animation and touch gestures that make the slide-out navigation feel real.  It has two important properties:

@property (strong, nonatomic) IBOutlet UIImageView *screenShotImageView;

@property (strong, nonatomic) UIImage *screenShotImage;


Using the viewWillAppear:animated method we can reset the menu view, covering the entire view with the screenShotImageView whose image has been set using the screenshot taken by the app delegate of the currentContentViewController.  It’s then just animated to the right.



[super viewWillAppear:animated];


// when the menu view appears, it will create the illusion that the other view has slide to the side

// what its actually doing is sliding the screenShotImage passed in off to the side

// to start this, we always want the image to be the entire screen, so set it there

[screenShotImageView setImage:self.screenShotImage];

[screenShotImageView setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];


// now we'll animate it across to the right over 0.2 seconds with an Ease In and Out curve

// this uses blocks to do the animation. Inside the block the frame of the UIImageView has its

// x value changed to where it will end up with the animation is complete.

// this animation doesn't require any action when completed so the block is left empty

[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{

[screenShotImageView setFrame:CGRectMake(265, 0, self.view.frame.size.width, self.view.frame.size.height)];


completion:^(BOOL finished){  }];



The easy part of the MenuViewController is if the user selects a new view.  Simply set the currentViewController on the AppDelegate with the new view, then call the slideThenHide method which animates the screenshot back over the entire screen completing the illusion.



// this sets the currentViewController on the app_delegate to the expanding view controller

// then slides the screenshot back over

[app_delegate setContentViewController:[[LogoExpandingViewController alloc] initWithNibName:@"LogoExpandingViewController" bundle:nil]];

[self slideThenHide];



-(void) slideThenHide


// this animates the screenshot back to the left before telling the app delegate to swap out the MenuViewController

// it tells the app delegate using the completion block of the animation

[UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{

[screenShotImageView setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];


completion:^(BOOL finished){ [app_delegate hideSideMenu]; }];



The more difficult part is detecting when the user interacts with the screenshot.  If they touch it, the end result should be the menu hiding and the content view becoming active again.  So it needs a UITapGestureRecognizer.

It also needs to be user interactive.  If they touch and drag the screenshot it should respond by moving. So it also needs a UIPanGestureRecognizer.

I wasn’t up to speed with dragging using Gestures so after a little searching I found this post by Soo How Ng which made it pretty simple:

- (void)viewDidLoad


[super viewDidLoad];


// create a UITapGestureRecognizer to detect when the screenshot recieves a single tap

tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTapScreenShot:)];

[screenShotImageView addGestureRecognizer:tapGesture];


// create a UIPanGestureRecognizer to detect when the screenshot is touched and dragged

panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureMoveAround:)];

[panGesture setMaximumNumberOfTouches:2];

[panGesture setDelegate:self];

[screenShotImageView addGestureRecognizer:panGesture];



- (void)viewDidUnload


[super viewDidUnload];


// remove the gesture recognizers

[self.screenShotImageView removeGestureRecognizer:self.tapGesture];

[self.screenShotImageView removeGestureRecognizer:self.panGesture];



– (void)singleTapScreenShot:(UITapGestureRecognizer *)gestureRecognizer


// on a single tap of the screenshot, assume the user is done viewing the menu

// and call the slideThenHide function

[self slideThenHide];



/* The following is from */

-(void)panGestureMoveAround:(UIPanGestureRecognizer *)gesture;


UIView *piece = [gesture view];

[self adjustAnchorPointForGestureRecognizer:gesture];


if ([gesture state] == UIGestureRecognizerStateBegan || [gesture state] == UIGestureRecognizerStateChanged) {


CGPoint translation = [gesture translationInView:[piece superview]];


// I edited this line so that the image view cannont move vertically

[piece setCenter:CGPointMake([piece center].x + translation.x, [piece center].y)];

[gesture setTranslation:CGPointZero inView:[piece superview]];


else if ([gesture state] == UIGestureRecognizerStateEnded)

[self slideThenHide];



- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {

if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {

UIView *piece = gestureRecognizer.view;

CGPoint locationInView = [gestureRecognizer locationInView:piece];

CGPoint locationInSuperview = [gestureRecognizer locationInView:piece.superview];


piece.layer.anchorPoint = CGPointMake(locationInView.x / piece.bounds.size.width, locationInView.y / piece.bounds.size.height); = locationInSuperview;






Source Code


Written by Nick Harris

February 5, 2012 at 3:53 am

Posted in Uncategorized