iOS & Swift Tutorials

Learn iOS development in Swift. Over 2,000 high quality tutorials!

How to Create Your Own Slide-Out Panel Navigation

This easy tutorial will help you add the popular slide-out navigation panels to your apps using Swift 4, Xcode 10 and iOS 12.

4.6/5 19 Ratings

Version

  • Swift 4.2, iOS 12, Xcode 10
Update note: Brody Eller updated this tutorial for iOS 12, Xcode 10, and Swift 4. Tammy Coron wrote the original.

In this tutorial, you’ll build a slide-out panel navigation, which is a popular alternative to using a plain UINavigationController or a UITabBarController for app navigation. The slide-out navigation panel allows users to slide content on or off screen.

Use the Download Materials button found at the top or bottom of this tutorial to get what you’ll need for this tutorial. The following animation shows what you’re going to build in this tutorial.

The slide-out navigation panel design pattern lets developers add permanent navigation to their apps without taking up valuable screen real estate, since users can choose to reveal the navigation at any time, while still seeing their current context.

Getting Started

In this tutorial, you’ll take a less-is-more approach so you can apply the slide-out navigation panel technique to your own apps with relative ease.

Open the project from the SlideOutNavigation-Starter folder, called SlideOutNavigation.xcodeproj, and take a look at how it’s organized. In addition to the view controllers, there’s also an Asset Catalog called Assets.xcassets containing all the adorable kitten and puppy images you’ll use in the app.

Here’s the overall structure of this app:

  • ContainerViewController is where the magic happens! This is the view controller that handles things like animations and swiping between the center view controller and the left and right panels. It’s responsible for holding references to all the other necessary view controllers.
  • CenterViewController is the center panel view controller.
  • SidePanelViewController serves as both the left and right side panel view controllers.

You can find the views for the center, left and right view controllers in Main.storyboard. So, feel free to take a look at the whole project.

Now that you’re familiar with the structure of the app, it’s time to start at square one—the center panel.

Finding Your Center

First order of business is placing CenterViewController inside the ContainerViewController as a child view controller.

Open ContainerViewController.swift. Locate viewDidLoad() and add the following properties right above it:

var centerNavigationController: UINavigationController!
var centerViewController: CenterViewController!

These two properties will hold both the centerViewController and its parent navigation controller.

Note: These are implicitly-unwrapped optionals (as denoted by the !). They have to be optional because their values won’t be initialized until after init() completes, but they can be implicitly unwrapped because you know they’ll be initialized by the time you use them. If they aren’t, then it’s a programmer error and you want to know about it when you test the app.

At the bottom of the file, you’ll see a class extension for UIStoryboard containing a handful of static methods for more convenient loading of specific view controllers from the app’s storyboard. You’ll take advantage of these methods to populate the properties you just created.

Add the following code inside viewDidLoad(), beneath the call to super:

// 1
centerViewController = UIStoryboard.centerViewController()
// 2
centerViewController.delegate = self

// 3
centerNavigationController = UINavigationController(rootViewController: centerViewController)
view.addSubview(centerNavigationController.view)
addChild(centerNavigationController)

// 4
centerNavigationController.didMove(toParent: self)

Don’t worry about the compiler error on the second line, you’ll take care of that shortly.

There’s some fun stuff going on in this short method. Here’s what you’re doing:

  1. Get a centerViewController by pulling it from the storyboard.
  2. Set the current view controller as the center view controller’s delegate so the center view controller can notify its container when to show and hide the left and right side panels.
  3. Create a navigation controller to contain the center view controller so you can push views to it and display bar button items in the navigation bar. Then, add the navigation controller’s view to ContainerViewController‘s view.
  4. Set up the parent-child relationship using addChild(_:) and didMove(toParent:).

Awesome! Now to take care of the error, modify this class so it implements CenterViewControllerDelegate.

Add the following class extension to ContainerViewController below the UIStoryboard extension at the bottom of the file (this also includes a number of empty methods you’ll fill out later):

// MARK: CenterViewController delegate

extension ContainerViewController: CenterViewControllerDelegate {
  func toggleLeftPanel() {
  }

  func toggleRightPanel() {
  }

  func collapseSidePanels() {
  }
}

Implementing these methods makes this class conform to CenterViewControllerDelegate.

Now is a good time to check your progress. Build and run the app. You should see something similar to the screen below:

Slide Out Navigation in Swift main screen

Yes, those buttons at the top will eventually bring you kitties and puppies. What better reason could there be for creating sliding navigation panels? But to get your cuteness fix, you’ve got to start sliding. First, to the left!

Kittens to the Left of Me…

You’ve created your center panel, but adding the left view controller requires a different set of steps.

To expand the left panel, the user will tap on the Kitties button in the navigation bar. Open CenterViewController.swift to get started on implementing that.

In the interests of keeping this tutorial focused on the important stuff, the IBActions and IBOutlets are pre-connected for you in the storyboard. However, to implement your DIY slide-out navigation panel, you need to understand how the buttons are configured.

Notice there are already two IBAction methods, one for each of the buttons. Find kittiesTapped(_:) and add the following implementation to it:

delegate?.toggleLeftPanel()

As previously mentioned, the method is already hooked up to the Kitties button. This uses optional chaining to call toggleLeftPanel() only if delegate has a value.

You can see the definition of the delegate protocol at the bottom. As you’ll see, there are methods called toggleLeftPanel(), toggleRightPanel() and collapseSidePanels(). If you remember, when you set up the center view controller instance earlier, you set its delegate as the container view controller. Time to go and implement toggleLeftPanel().

Note: For more information on delegate methods and how to implement them, please refer to Apple’s Developer Documentation. If you’re interested in the delegate pattern, and other code design patterns, then take a look at our book Design Patterns by Tutorials.

Open ContainerViewController.swift and add an enum to the top of ContainerViewController:

enum SlideOutState {
  case bothCollapsed
  case leftPanelExpanded
  case rightPanelExpanded
}

You’ll use this to keep track of the current state of the side panels, so you can tell whether neither panel is visible or one of the left or right panels is visible.

Next, add two more properties below your existing centerViewController property:

var currentState: SlideOutState = .bothCollapsed
var leftViewController: SidePanelViewController?

These will hold the current state of the side panels and the left side panel view controller itself:

You initialize currentState to .bothCollapsed — that is, neither of the side panels is visible when the app first loads. The leftViewController property is an optional, because you’ll be adding and removing the view controller at various times, so it might not always have a value.

Next, add the implementation for toggleLeftPanel():

let notAlreadyExpanded = (currentState != .leftPanelExpanded)

if notAlreadyExpanded {
  addLeftPanelViewController()
}

animateLeftPanel(shouldExpand: notAlreadyExpanded)

First, this method checks whether the left side panel is already expanded. If it’s not already visible, then you call a method which adds the panel to the view hierarchy. Then, it calls another method which animates it to its “open” position. If the panel is already visible, then it animates the panel to its “closed” position.

Next, you need to add the code to add the left panel to the view hierarchy. Add the following just below toggleLeftPanel():

func addLeftPanelViewController() {
  guard leftViewController == nil else { return }

  if let vc = UIStoryboard.leftViewController() {
    vc.animals = Animal.allCats()
    addChildSidePanelController(vc)
    leftViewController = vc
  }
}

This code first checks to see if the leftViewController property is nil. If it is, then it creates a new SidePanelViewController and sets its list of animals to display — in this case, cats!

Next, add the implementation for addChildSidePanelController(_:) at the bottom of the extension:

func addChildSidePanelController(_ sidePanelController: SidePanelViewController) {
  view.insertSubview(sidePanelController.view, at: 0)

  addChild(sidePanelController)
  sidePanelController.didMove(toParent: self)
}

This method adds the child view controller to the container view controller. This is the same as adding the center view controller earlier. It simply inserts its view — in this case it’s inserted at z-index 0, which means it will be below the center view controller — and adds it as a child view controller.

Add the following constant below your other properties at the top of ContainerViewController:

let centerPanelExpandedOffset: CGFloat = 90

This value is the width, in points, of the center view controller left visible once it has animated off screen. 90 points should do it.

Next, back in the CenterViewControllerDelegate extension, add the following below addLeftPanelViewController():

func animateLeftPanel(shouldExpand: Bool) {
  if shouldExpand {
    currentState = .leftPanelExpanded
    animateCenterPanelXPosition(
      targetPosition: centerNavigationController.view.frame.width 
        - centerPanelExpandedOffset)
  } else {
    animateCenterPanelXPosition(targetPosition: 0) { _ in
      self.currentState = .bothCollapsed
      self.leftViewController?.view.removeFromSuperview()
      self.leftViewController = nil
    }
  }
}

This method first checks whether it’s been told to expand or collapse the side panel. If it should expand, then it sets the current state to indicate the left panel is expanded and calls a method to animate the center panel so it’s open. Otherwise, it animates the center panel closed, removes its view and sets the current state to indicate it’s closed.

Now add this method above addChildSidePanelController(_:):

func animateCenterPanelXPosition(
    targetPosition: CGFloat, 
    completion: ((Bool) -> Void)? = nil) {
  UIView.animate(
    withDuration: 0.5,
    delay: 0,
    usingSpringWithDamping: 0.8
    initialSpringVelocity: 0,
    options: .curveEaseInOut,
    animations: {
      self.centerNavigationController.view.frame.origin.x = targetPosition
    }, 
    completion: completion)
}

This is where the actual animation happens. It moves the center view controller’s view to the specified position with a nice spring animation. The method also takes an optional completion closure, which it passes on to the UIView animation. You can try tweaking the duration and spring damping parameters if you want to change the appearance of the animation.

Build and run the app.

Tap on Kitties in the navigation bar. The center view controller should slide over — whoosh! — and reveal the Kitties menu underneath. D’aww, look how cute they all are.

Slide Out Navigation in Swift - Kitties

But too much cuteness can be a dangerous thing! Tap the Kitties button again to hide them!

Me and My Shadow

When the left panel is open, notice how it’s right up against the center view controller. It would be nice if there were a bit more of a distinction between them. How about adding a shadow?

Still in ContainerViewController.swift, add the following method to the end of the extension:

func showShadowForCenterViewController(_ shouldShowShadow: Bool) {
  if shouldShowShadow {
    centerNavigationController.view.layer.shadowOpacity = 0.8
  } else {
    centerNavigationController.view.layer.shadowOpacity = 0.0
  }
}

This adjusts the opacity of the navigation controller’s shadow to make it visible or hidden. You’ll implement a didSet observer to add or remove the shadow whenever the currentState property changes.

Scroll to the top of ContainerViewController.swift and change the currentState declaration to:

var currentState: SlideOutState = .bothCollapsed {
  didSet {
    let shouldShowShadow = currentState != .bothCollapsed
    showShadowForCenterViewController(shouldShowShadow)
  }
}

The didSet closure executes whenever the property’s value changes. If either of the panels is visible, it shows the shadow.

Build and run the app again. This time when you tap Kitties, check out the sweet new shadow! Looks better, huh?

Slide Out Navigation in Swift - Kitties with shadows

Up next, adding the same functionality but for the right side, which means… puppies!

Puppies to the Right…

To add the right panel view controller, simply repeat the steps for adding the left view controller.

Open ContainerViewController.swift and add the following property below leftViewController:

var rightViewController: SidePanelViewController?

Next, locate toggleRightPanel() and add the following implementation:

let notAlreadyExpanded = (currentState != .rightPanelExpanded)

if notAlreadyExpanded {
  addRightPanelViewController()
}

animateRightPanel(shouldExpand: notAlreadyExpanded)

Next, add the following below toggleRightPanel():

func addRightPanelViewController() {
  guard rightViewController == nil else { return }

  if let vc = UIStoryboard.rightViewController() {
    vc.animals = Animal.allDogs()
    addChildSidePanelController(vc)
    rightViewController = vc
  }
}

func animateRightPanel(shouldExpand: Bool) {
  if shouldExpand {
    currentState = .rightPanelExpanded
    animateCenterPanelXPosition(
      targetPosition: -centerNavigationController.view.frame.width 
        + centerPanelExpandedOffset)
  } else {
    animateCenterPanelXPosition(targetPosition: 0) { _ in
      self.currentState = .bothCollapsed
      self.rightViewController?.view.removeFromSuperview()
      self.rightViewController = nil
    }
  }
}

This code is almost an exact duplicate of the code for the left panel except, of course, for the differences in method and property names and the direction. If you have any questions about it, you can always review the explanation from the previous section.

Just as before, the IBActions and IBOutlets are already connected in the storyboard for you. Similar to Kitties, Puppies is hooked up to an IBAction method named puppiesTapped(_:). This button controls the sliding of the center panel to reveal the right side panel.

Switch to CenterViewController.swift and add the following line to puppiesTapped(_:):

delegate?.toggleRightPanel()

Again, this is the same as kittiesTapped(_:), except it’s toggling the right panel instead of the left.

Time to see some puppies!

Build and run the app again to make sure everything is working. Tap Puppies. Your screen should look like this:

slide-out navigation panel

Looking good, right? But remember, you don’t want to expose yourself to the cuteness of puppies for too long, so tap that button again to hide them away.

You can now view both kitties and puppies, but it would be great to be able to view a bigger picture of each one, wouldn’t it? MORE CUTENESS :]

Pick an Animal, Any Animal!

The kitties and puppies are listed within the left and right panels. These are both instances of SidePanelViewController, which simply contains a table view.

Open SidePanelViewController.swift to take a look at the SidePanelViewControllerDelegate protocol. A side panel’s delegate can be notified via this method whenever an animal is tapped. Time to use it!

In SidePanelViewController.swift, add the following property at the top of the class, underneath the table view IBOutlet:

var delegate: SidePanelViewControllerDelegate?

Then, fill in the implementation for tableView(_:didSelectRowAt:) within the UITableViewDelegate extension:

let animal = animals[indexPath.row]
delegate?.didSelectAnimal(animal)

If there’s a delegate set, this will tell it the user has selected an animal. Currently, there’s no delegate! It would make sense for CenterViewController to be the side panel’s delegate as it can then display the selected animal photo and title.

Open CenterViewController.swift to implement the delegate protocol. Add the following extension beneath the existing class definition:

extension CenterViewController: SidePanelViewControllerDelegate {
  func didSelectAnimal(_ animal: Animal) {
    imageView.image = animal.image
    titleLabel.text = animal.title
    creatorLabel.text = animal.creator
    delegate?.collapseSidePanels()
  }
}

This method simply populates the image view and labels in the center view controller with the animal’s image, title and creator. Then, if the center view controller has a delegate of its own, you tell it to collapse the side panel so you can focus on the selected item.

collapseSidePanels() doesn’t do anything, though! Open, ContainerViewController.swift and add the following implementation to collapseSidePanels():

switch currentState {
case .rightPanelExpanded:
  toggleRightPanel()
case .leftPanelExpanded:
  toggleLeftPanel()
case .bothCollapsed:
  break
}

The switch statement in this method simply checks the current state of the side panels and collapses whichever one is open.

Now, locate addChildSidePanelController(_:) and add the following to the bottom of the method:

sidePanelController.delegate = centerViewController

In addition to what it was doing previously, the method will now set the center view controller as the side panels’ delegate.

That should do it!

Build and run the app. View kitties or puppies and tap on one of the cute little critters. The side panel should collapse itself again and you should see the details of the animal you chose.

Slide Out Navigation in Swift - Puppy Details

Move Your Hands Back and Forth

The navigation bar buttons are great, but most apps also allow you to swipe to open the side panels. Adding gestures to your app is surprisingly simple. Don’t be intimated; you’ll do fine!

Open ContainerViewController.swift again. First, make this class conform to UIGestureRecognizerDelegate by adding the following extension above the UIStoryboard extension:

// MARK: Gesture recognizer

extension ContainerViewController: UIGestureRecognizerDelegate {
  @objc func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
  }
}

Next, locate viewDidLoad(). Add the following to the end of the method:

let panGestureRecognizer = UIPanGestureRecognizer(
  target: self, 
  action: #selector(handlePanGesture(_:)))
centerNavigationController.view.addGestureRecognizer(panGestureRecognizer)

This creates a UIPanGestureRecognizer, assigns self as the target and handlePanGesture(_:) as the selector to handle any detected pan gestures.

By default, a pan gesture recognizer detects a single touch with a single finger, so it doesn’t need any extra configuration. You just need to add the newly created gesture recognizer to centerNavigationController view.

Note: Refer to our UIGestureRecognizer with Swift Tutorial for more information about gesture recognizers in iOS.

Didn’t I tell you it’d be simple? There’s only one move remaining in your slide-out navigation panel routine. The gesture recognizer calls handlePanGesture(_:) when it detects a gesture. So your last task for this tutorial is to implement the method.

Add the following block of code to handlePanGesture(_:) (it’s a big one!):

// 1
let gestureIsDraggingFromLeftToRight = (recognizer.velocity(in: view).x > 0)

// 2
switch recognizer.state {
// 3
case .began:
  if currentState == .bothCollapsed {
    if gestureIsDraggingFromLeftToRight {
      addLeftPanelViewController()
    } else {
      addRightPanelViewController()
    }
    showShadowForCenterViewController(true)
  }

// 4
case .changed:
  if let rview = recognizer.view {
    rview.center.x = rview.center.x + recognizer.translation(in: view).x
    recognizer.setTranslation(CGPoint.zero, in: view)
  }

// 5
case .ended:
  if let _ = leftViewController,
    let rview = recognizer.view {
    // animate the side panel open or closed based on whether the view
    // has moved more or less than halfway
    let hasMovedGreaterThanHalfway = rview.center.x > view.bounds.size.width
    animateLeftPanel(shouldExpand: hasMovedGreaterThanHalfway)
  } else if let _ = rightViewController,
    let rview = recognizer.view {
    let hasMovedGreaterThanHalfway = rview.center.x < 0
    animateRightPanel(shouldExpand: hasMovedGreaterThanHalfway)
  }

default:
  break
}

Here's what this does:

  1. The pan gesture recognizer detects pans in any direction, but you're only interested in horizontal movement. First, you set up the gestureIsDraggingFromLeftToRight Boolean to check for this using the x component of the gesture velocity.
  2. There are three states that need to be tracked: UIGestureRecognizerState.began, UIGestureRecognizerState.changed and UIGestureRecognizerState.ended. Use a switch to handle each accordingly.
  3. .began: If the user starts panning and neither panel is visible, show the correct panel based on the pan direction and make the shadow visible.
  4. .changed: If the user is already panning, move the center view controller's view by the amount the user has panned
  5. .ended: When the pan ends, check whether the left or right view controller is visible. Depending on which one is visible and how far the pan has gone, perform the animation.

You can move the center view around, show and hide the left and right views using a combination of these three states, as well as the location, velocity and direction of the pan gesture.

For example, if the gesture direction is right, then show the left panel. If the direction is left, then show the right panel.

Build and run the app again. At this point, you should be able to slide the center panel left and right, revealing the panels underneath.

Where to Go From Here?

Congratulations! You're now a slide-out navigation panel ninja!

I hope you enjoyed this tutorial. You can download the completed project files by using the Download Materials button found at the top or bottom of this tutorial. I'm sure you'll enjoy being stuck in the middle of kitties and puppies!

If you want to try a pre-built library over the DIY solution, be sure to check out SideMenu. For an in-depth discussion of the origins of this UI control (and a trip down memory lane), check out iOS developer and designer Ken Yarmosh's post New iOS Design Pattern: Slide-Out Navigation. He does a great job of explaining the benefits of using this design pattern and showing common uses in the wild.

Leave a comment in the forums below to share your slide-out moves and grooves!

Average Rating

4.6/5

Add a rating for this content

19 ratings

Contributors

Comments