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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Create a Cool 3D Sidebar Menu Animation
30 mins
- Getting Started
- Restructuring Your Storyboard
- Deleting the Old Structure
- Adding a New Root Container
- Adding Identifiers to View Controllers
- Creating Contained View Controllers
- Creating a Scroll View
- Creating Containers
- Adding Contained View Controllers
- Reconnect Menu and Detail Views
- Creating a Delegate Protocol
- Implementing the MenuDelegate Protocol
- Controlling the Scroll View
- Adding a Menu Button
- Creating a Hamburger View
- Installing the Hamburger View
- Adding Perspective to the Menu
- Manipulating the Menu Layer
- Rotating the Burger Button
- Where to Go From Here?
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. :]
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:
- The first
UIScrollViewDelegate
method,scrollViewDidScroll(_:)
, is super useful. It always tells you when something has changed thecontentOffset
of the scroll view. You setisPagingEnabled
based on whether the horizontal offset is above the threshold value. - 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. - 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:
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.
- Select iOS ▸ Cocoa Touch Class. Click Next.
- Name the class HamburgerView.
- Ensure that
HamburgerView
is a subclass of UIView. - 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:
- First, you create an
UIImageView
using an asset from the library. - 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.
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.