This leaves us developers with the burden of building this UI component from scratch, or going with a 3rd party library.

In a previous tutorial, I wrote about building a slide-out menu by nesting Container Views inside a Scroll View. At first, this seemed like a clever solution. But this approach had a few weaknesses:

The Scroll View pan gesture conflicts with horizontal swiping in the main content area.

The main content area is tappable while the menu is open, so it needs to be masked by a modal.

It’s difficult to customize the slide-out animation.

Nowadays, I usually recommend using a Tab Bar Controller instead of a slide-out menu. But if I had to build a slide-out, I would use Custom View Controller Transitions. My guess is that a number of slide-out implementations already do this, but I can’t tell for sure.

It takes a while to get used to Custom View Controller Transitions. If you need a refresher on using this API, check out my earlier modal pull-down tutorial.

By the end of this tutorial, you’ll end up with an interactive slide-out menu. Here’s the spec:

The Menu button opens the slide-out.

Tapping the main content area (blue) closes it.

Panning from the left edge opens the slide-out interactively.

Panning the main content area closes the slide-out interactively.

Interactive gestures either complete or roll back, depending on how far you pan.

Here’s a table of contents if you want to jump straight to a particular section:

This method calculates the progress in a particular direction. For example, if you specify the .Right direction, it only cares about movement along the positive-x direction. Likewise, .Left tracks progress in the negative-x direction.

Build and run. The closing animation should now slide the main content area back to its original position.

8. Add the Dismiss Interaction

The next step is to close the menu interactively.

You’ll use a Pan Gesture Recognizer to drive the transition. As the user drags horizontally, the dismiss animation will scrub to its corresponding progress point.

Open MenuViewController.swift and replace its contents with the following code:

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

importUIKit

classMenuViewController: UIViewController{

// 1

varinteractor:Interactor?=nil

// 2

@IBAction funchandleGesture(sender:UIPanGestureRecognizer){

// 3

lettranslation=sender.translationInView(view)

// 4

letprogress=MenuHelper.calculateProgress(

translation,

viewBounds:view.bounds,

direction:.Left

)

// 5

MenuHelper.mapGestureStateToInteractor(

sender.state,

progress:progress,

interactor:interactor){

// 6

self.dismissViewControllerAnimated(true,completion:nil)

}

}

@IBAction funccloseMenu(sender:AnyObject){

dismissViewControllerAnimated(true,completion:nil)

}

}

Here’s what’s going on:

Comment #1: The MainViewController passes the interactor object to the MenuViewController. This is how they share state.

Comment #2: You create an @IBAction for a pan gesture. You’ll wire this up in the Storyboard later.

Comment #3: You use translationInView() to get the pan gesture coordinates.

Comment #4: Using the MenuHelper‘s calculateProgress() method, you convert the coordinates into progress in a specific direction.

Comment #5: You pass all the information you have to the MenuHelper‘s mapGestureStateToInteractor() method. This does the hard work of syncing the gesture state with the interactive transition.

Comment #6: It’s important to note that this trailing closure is not a completion handler. Rather, you pass in the line of code that initiates the transition. In this case, it’s dismissViewControllerAnimated().

All of this code is essentially trying to update the transition status each time a pan gesture event is fired. Feel free to refer back to the MenuHelper methods in the add some helpful files section.

9. Add a Pan Gesture Recognizer

You just added the code for an @IBAction for a pan gesture recognizer to the MenuViewController. Now it’s time to wire this up in the Storyboard.

a. In the Storyboard, search for a Pan Gesture Recognizer in the Object Library.

b. Drag it onto the Close button.

c. Hold down Control and drag from the gray Pan Gesture Recognizer icon to the yellow MenuViewController Scene icon.

d. Select the handleGesture: action from the popover menu.

Thanks to a reader named Aly, I learned it’s better to attach the Pan Gesture Recognizer to the Close button rather than the background view. This makes the snapshot feel like a real object. Also, you avoid gesture conflicts with the menu’s table should you decide to implement row swipe actions.

10. Wire up the Dismiss Interaction

There’s one more step before any of this works. You have to inform the MainViewController that you’ll take responsibility for the interactive portion of the dismiss transition.

Replace the contents of MainViewController.swift with the following code:

Build and run. You should now be able to close the menu by dragging the snapshot.

11. Add the Present Interaction

You’re almost done! You can actually call it quits now, and still end up with a pretty decent slide-out menu. But wouldn’t it be nice to open the menu interactively as well?

Rather than use a normal pan gesture recognizer, you’re going to use a Screen Edge Pan Gesture Recognizer instead. It doesn’t interfere as badly with horizontal gestures in the main content area.

As with any gesture, you’ll still need to onboard your users so that they’re aware of this feature. But hopefully this gesture will be somewhat familiar with users who are accustomed with swiping-to-go-back.

Build and run. You should now be able to swipe from the left edge to open the menu.

Conclusion

You can check out the completed project on GitHub. If you’re pulling from the latest commit, you might see some sample table view cells that that open detail pages.

I think using Custom View Controller Transitions is a decent approach for making interactive slide-out menus. It looks like you have two View Controllers on the screen, but you’re actually just using a snapshot. Also, you can customize the animation to support pretty much any slide-out implementation.

If people are interested, I might consider follow-up blog posts on customizing different slide-out animations. But in the meantime, you can check out what others have done here and here.

Got any tips for working with custom transitions? Have any suggestions for future tutorial topics? Feel free to add your thoughts to the comments.

Like this post? Please share it using the share buttons to the left. Then join our mailing list below and follow us on Twitter – @thorntech – for future updates.

49 Comments

Yo
on March 31, 2016 at 5:01 pm

Great tutorial! I struggled a bit in the pan gesture section and turnt out it was because I was running on iOS 8.1. As soon as I switched to iOS 9 device it got going. Not too sure what caused that tho..

Other than that I think I’m able to create my own drawer menu now. Thanks again!

The tutorial was really helpful 🙂
I really like that you include visuals along the way!

It would be great to have a little example on how to retain the menu construct if a menu item was opened.
Right now as soon as you open a menu item you get into a totally different state and are not able to access the menu anymore until you go back.

Another reader asked how to use the Menu to replace the Main Content VC, rather than push a detail page. I created a GitHub branch called Riddhi. The Extra Page menu item replaces the Main contents with a yellow page. It uses a Tab Bar Controller with hidden tabs to perform the switching, which does seem kind of hackish. And due to the nested hierarchy, NSNotifications are used to figure out which combination of tabs should be selected. Is this on track with what you had in mind?

You could use a custom table view cell, add a few image views, and wire them up with IBOutlets. Then you can specify which images you want to see in cellForRowAtIndexPath. Is this what you had in mind?

I noticed when you tap the menu button, it turns grey when the view slides right. But if you use gesture to slide it, the menu button stays blue. Any way to match functionality on both to either turn grey or stay blue on the button?

Hi Vlad, you can do this with a custom button. I pushed up a commit with the fix. Here’s what I ended up doing:

Deleted the BarButtonItem and used a regular Button instead. In the view hierarchy, you end up with a UIButton nested inside a Bar Button Item.

Wired up the button to the openMenu IBAction

Changed the Button Type from System to Custom

Set the text alignment to left instead of center

The UIBarButtonItem API is locked down pretty tight, and it doesn’t give you any access to its internal Button object. But if you drag a Button instead of BarButtonItem, you end up with a child button object you have access to.

I’ve done as per the GIFs, even replaced my code with your source and whenever I run and try to gesture close the menu by dragging left on the blue section, the whole thing animation completes instantly. There is no gradual movement of the main VC as I gesture right to left. As soon as the gesture is made the DismissAnimation completes and shows the full blue VC.

PS – when I tap on the Close button area behind the snapshot, the menu animates down over the time specified as expected. Its just when I try the gesture method that it completes instantly with no duration.

Hi John,
Thanks for trying out the tutorial. I think I’ve run a similar issue in the past, where starting the pan gesture causes the transition to jump to a weird state with no animation. If you email me a zip of your progress so far robert.chen@thorntech.com, I can try to help troubleshoot the issue.

4. In the Storyboard, select the ViewController scene. In the Identity Inspector, change the class to MainViewController
5. While the MainViewController scene is still selected, go to Editor > Embed In > Navigation Controller
6. Select the View within MainViewController, and in the Attributes Inspector, set the color to blue
7. From the Object Library, drag a Bar Button Item to the top left corner of the nav bar
8. Change the Bar Button Item’s title from “Item” to “Menu”
9. From the Object Library, drag a View Controller scene onto the Storyboard
10. Set the new View Controller > View background to green
11. From the Object Library, drag a Button and place it in the top right corner of the green View Controller
12. Set the Button’s text to “Close”
13. Right-click-drag-up from the Close button, and let go. From the pop-up menu, select “Vertical spacing to top layout guide”
14. Right-click-drag-right from the Close button, and choose “Trailing Space to container margin”.
15. In the Project Navigator, Right-click > New File > Swift File, and call it MenuViewController. And replace its contents with the following:

Also in the Storyboard, set the new View Controller’s class to MenuViewController.
17. Go back to the Storyboard, and find the MainViewController’s Menu button
18. In the Project Navigator, Option-click on MainViewController.swift, so the windows are side-by-side
19. Control-drag from the Menu button to a spot inside the MainViewController class.
20. In the pop-up menu, change Connection to “Action”, and use the name “openMenu”. Click Connect
21. It should create an @IBAction method for you. Modify this by adding a performSegue() call within the method, like this:

22. To create the segue, return to the Storyboard. Select the MainViewController scene, and control-drag from it to the MenuViewController. From the pop-up menu, select “Present Modally”.
23. Select the segue you just created. In the Attributes Inspector, change the Identifier to “openMenu”
24. To make the “Close” button work, repeat the process of creating an IBAction (i.e. open the Storyboard and MenuViewController side-by-side, control-drag to create the IBAction, etc). This time, name the IBAction to “closeButton”. And for the method contents, you want to dismiss the view controller, like this:

the following code in the PresentMenuAnimator class gives an error: let containerView = transitionContext.containerView ; the error is “initializer for conditional binding must have Optional type, not UIVIew” How do you resolve this? thanks!

Hi Robert. Thanks so much for publishing this tutorial. I really like the approach you took! However, try as I might, when I complete step 4 (Wire up the Present Menu Animator) and run the project, the menu still transitions as a normal segue (i.e., from the bottom). I’m assuming I did something wrong in updating the code to the current Swift version. Any suggestions as to where I might have gone wrong? Thanks.

Thinking about the problem, it was apparent that the Present Menu Animator simply wasn’t wired up. I changed the syntax of the animationControllerForPresentedController function (in the extension to MainViewController) to the following and it worked:

Great tutorial! However, I have a question. The cells in the side menu takes my app to another viewcontroller. So when I exit from that viewcontroller back to the side menu, the snapshot and the slide animation state are gone. How do i fix that?

That’s a great question. Just off the top of my head, I think the approach I use doesn’t really work well with keeping the side menu open, since it’s a modal that ought to be closed before doing a push segue. I wonder if there’s a way to retain that snapshot, and override the pop animation (although this is starting to sound kind of messy). I probably won’t get a chance to sit down and figure this out, so if anyone has ideas, feel free to chime in.

This is a great and helpful tutorial. However, I would like to ask you if I can build the side menu without using storyboard. I build my apps all programmatically, and I want to use your source codes to build my side menu. But it didn’t work because I don’t know how to call the menuController without using perform segue. Any suggestions? Really appreciated your time. Thanks

thank you so much for this. I just have one question. If you use the edge swipe gesture and partially open the menu, then close it without lifting your finger, it creates a memory leak by creating a strong reference to the menu view controller. How do you go about removing it from memory in this case?

Excellent Tutorial, the code from git as of May 30 2017, needs some minor tweaks on Xcode, thanks to Apple’s ever Swift moving target.
Also this code can be more re-usable if one uses StoryBoardIDs and some plain code for the segues.

Out of all the tutorials i researched, this one was the cleanest and the simplest to integrate with my new app.
It is 99% plug and play.

True but I still didn’t find a way to have the gesture pan (when closing the menu) working. But I cannot override func interactionControllerForDismissal and I didn’t find any substitude for the NavigationControllerDelegate.

I have a road block with usage if segues. I am not using storyboard entirely and am coding for an iPad, is it possible for you to give me an example with xib’s and not using storyboard? If it’s too much to ask can you please guide me on how to proceed with implementing slide out menu with out storyboard?

And also one more thing, when flip my device to landscape there is a gap between two views.

Funny thing is that if I open the menu when the phone is upside down (and thanks to the override still showing correctly) the app suddenly jumps back to original right-way-up orientation and thus looks upside down!

Ah yes, answered that myself. Also override inside the menu view controller of course. But other lesson learned: Do not rotate the app while the menu is open, it completely screws things up. For instance I ended up with a landscape oriented app on a portrait-oriented phone… (and thus half the screen black and half the app sticking out of the device.) Looks like some more advanced tweaking is necessary, or some way to block the app from rotating while the menu is open.

Great tutorial and helped a lot.
I wanted to take it a bit further and make the Menu accessible from the two new views you added in the final project. Does someone know how to make it efficiently, without getting messed up with the views. I always get a lot of views stacked up in memory where I get problems…
Thanks in advance.