Create a Cool 3D Sidebar Menu Animation

In this tutorial, you’ll learn how to manipulate CALayer properties on views in order to create a cool 3D sidebar animation. By Warren Burton.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Implementing the MenuDelegate Protocol

You can now implement the protocol you created to send the selection change to DetailViewController.

Open RootViewController.swift and add this extension to the end of the file:

extension RootViewController: MenuDelegate {
  func didSelectMenuItem(_ item: MenuItem) {
    detailViewController?.menuItem = item
  }
}

This code declares that RootViewController adopts MenuDelegate. When you select a menu item, RootViewController tells DetailViewController about that change by passing the selected MenuItem to the instance.

Finally, insert this line at end of viewDidLoad():

menuViewController?.delegate = self

That tells MenuViewController that RootViewController is the delegate.

Build and run the app. Your menu selections will now change the contents of DetailViewController. Signal thumbs up. :]

3D sidebar animation

Controlling the Scroll View

So far so good. Your menu works and the app looks a lot nicer.

However, you’ll also notice that manually scrolling the menu away doesn’t last very long. The menu always bounces back into view.

The scroll view property isPagingEnabled causes that effect because you have set it to true. You’ll fix that now.

Still working inside RootViewController, add this line below let menuWidth: CGFloat = 80.0:

lazy var threshold = menuWidth/2.0

Here, you pick an arbitrary point where the menu will choose to hide or show itself. You use lazy because you’re calculating a value relative to menuWidth.

Locate extension RootViewController: UIScrollViewDelegate in RootViewController and insert this code inside the extension:

//1
func scrollViewDidScroll(_ scrollView: UIScrollView) {
  let offset = scrollView.contentOffset
  scrollView.isPagingEnabled = offset.x < threshold
}
//2
func scrollViewDidEndDragging(_ scrollView: UIScrollView,
                              willDecelerate decelerate: Bool) {
  let offset = scrollView.contentOffset
  if offset.x > threshold {
    hideMenu()
  }
}
//3
func moveMenu(nextPosition: CGFloat) {
  let nextOffset = CGPoint(x: nextPosition, y: 0)
  scroller.setContentOffset(nextOffset, animated: true)
}
func hideMenu() {
  moveMenu(nextPosition: menuWidth)
}
func showMenu() {
  moveMenu(nextPosition: 0)
}
func toggleMenu() {
  let menuIsHidden = scroller.contentOffset.x > threshold
  if menuIsHidden {
    showMenu()
  } else {
    hideMenu()
  }
}

Take a look at what this code does:

  1. The first UIScrollViewDelegate method, scrollViewDidScroll(_:), is super useful. It always tells you when something has changed the contentOffset of the scroll view. You set isPagingEnabled based on whether the horizontal offset is above the threshold value.
  2. Next, you implement scrollViewDidEndDragging(_:willDecelerate:) to detect a raised touch on the scroll view. As long as the content offset is greater than the threshold, you hide the menu; otherwise the paging effect takes hold and reveals the menu.
  3. The last methods are helpers to animate the menu into position: show, hide and toggle.

Build and run your app. Now, try dragging the scroll view and see what happens. When you cross the threshold, the menu springs open or closed:

3D sidebar animation

Looks like it’s time for burgers. :]

Adding a Menu Button

In this section, you’re going to add a burger button to the navigation bar so your users don’t have to drag to show and hide the menu.

Because you want to animate this button later, this needs to be a UIView rather than an image-based UIBarButton.

Creating a Hamburger View

Select the Views folder in the Project navigator, then add a new Swift file.

  1. Select iOS ▸ Cocoa Touch Class. Click Next.
  2. Name the class HamburgerView.
  3. Ensure that HamburgerView is a subclass of UIView.
  4. The language should be Swift.

Open HamburgerView.swift and replace everything inside the class HamburgerView with this code:

//1
let imageView: UIImageView = {
  let view = UIImageView(image: UIImage(imageLiteralResourceName: "Hamburger"))
  view.contentMode = .center
  return view
}()
//2
required override init(frame: CGRect) {
  super.init(frame: frame)
  configure()
}
required init?(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
  configure()
}
private func configure() {
  addSubview(imageView)
}

Here’s what you’re doing here:

  1. First, you create an UIImageView using an asset from the library.
  2. You then add the that image view, creating a path for both possible init methods.

Installing the Hamburger View

Now you have a view, you can install it in the navigation bar that belongs to DetailViewController.

Open RootViewController.swift again and insert this property at the top of the main RootViewController class:

var hamburgerView: HamburgerView?

Next append this extension to the end of the file:

extension RootViewController {
  func installBurger(in viewController: UIViewController) {
    let action = #selector(burgerTapped(_:))
    let tapGestureRecognizer = UITapGestureRecognizer(target: self,
                                                      action: action)
    let burger = HamburgerView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
    burger.addGestureRecognizer(tapGestureRecognizer)
    viewController.navigationItem.leftBarButtonItem
      = UIBarButtonItem(customView: burger)
    hamburgerView = burger
  }
  @objc func burgerTapped(_ sender: Any) {
    toggleMenu()
  }
}

Finally add this statement into the bottom of the viewDidLoad():

if let detailViewController = detailViewController {
  installBurger(in: detailViewController)
}

This set of code provides an instance variable for the burger button, since you’ll want to animate it soon. You then create a method to install the burger in the navigation bar of any view controller.

The method installBurger(in:) creates a tap gesture in the view that calls out to the method burgerTapped(_:).

Note that you must annotate burgerTapped(_:) with @objc because you are using the Objective-C runtime here. This method toggles the menu in or out depending on the current state.

You then use this method to install the button in the UINavigationBar that belongs to DetailViewController. From an architecture perspective, DetailViewController doesn’t know about this button and doesn’t need to deal with any menu state operations. You maintain separation of responsibilities.

That’s it. The steps left to bring your 3D sidebar animation to life are getting fewer as you build up the stack of objects.

Build and run your app. You’ll see that you now have a burger button that toggles the menu in and out.

3D sidebar animation

Adding Perspective to the Menu

To review what you’ve done so far, you’ve refactored a Master-Detail app into a viable side menu-type app, where the user can either drag or use a button to reveal and hide the menu.

Now, for your next step: The animated version of your menu should look like a panel opening and closing. The menu button will rotate smoothly clockwise as the menu opens and counter-clockwise as the menu closes.

To do this, you’ll calculate the fraction of the menu view that’s visible, then use this to calculate the menu’s angle of rotation.