In this post, we will breakdown, analyze the Slide Menu mechanism of Twitter app, and try to replicate it using Auto Layout and Container View. This post assume you have some experience working with Auto Layout. No third party library is used in this tutorial.
Main screen of Twitter app consist of a tab view controller. If you swipe right, a side menu will appear.
When the side menu is open, both side menu view and tab bar view appear in the same screen. The screen (which is a view controller) contain both of the side menu view controller and tab bar view controller :
We can achieve this using two container view in the Main View Controller.
You can download the starter project containing tab bar controller and preset side menu here : https://drive.google.com/open?id=1p4pfLTnIZd2Qokh03bH_Lpqs0xNQ7h58
If you are planning to start the project from scratch, the steps below assume you already have tab bar controller and a view controller for side menu prepared.
Setup Container View for Tab bar controller
To accomodate the side menu and tab bar controllers, we will create a new view controller in storyboard named MainViewController , and drag a container view to it.
After dragging, you will see that Xcode auto link a new view controller to the Container view. We don't need this new controller, select the link (segue) and press delete to remove the link, then delete the attached view controller.
Then we will link the container view to the tab bar view controller. Select the Container view, then hold control and drag it to the tab bar controller. Select "Embed".
You might see the tab bar controller shrinked to match the container view size 😱, no worries, we will add some constraint to the container view so that it will match the screen size.
Let's create a leading space 0 , top space 0, equal width and equal height constraints for the container view (to its parent view) like this :
Select the container view, Hold control and drag to its parent view. Then once the list of constraints appear, hold shift to select multiple constraints, then click 'Add Constraints' once you are done.
As by default Xcode align the top / bottom constraint to Safe Area, the container view will have some margin on top when viewed in iPhone X / XR / XS :
We want the container view to align to the screen top, not safe area, hence we will need to edit the constraint.
Select the container view and double click its Align Top constraint :
Change the "Safe Area" in First/Second Item to "Superview", and then set the constant to 0 .
After adjusting the top constraint, remember to set the constant of leading constraint to 0 as well.
Set the MainViewController as the initial view controller.
Build and run the app, you now should have a container view containing the tab bar controller 🙌.
As we will add another container view for side menu later on, we should give a label to the tab bar container view so we can identify it later. Let's label it as ContentView.
Setup Container View for Side Menu View Controller
Similar to previous section, drag another container view to MainViewController, link it to the side menu view controller, setup constraints (top 0, leading 0, equal width and equal height) and change the safe area to superview.
Add a label to this container view, we will label it as SideMenuView.
As we want the width of side menu to be 80% of screen width (so the side menu won't cover the whole screen when opened), we will change its equal width constraint to equal 80% of its parent view's width.
Select SideMenuView , then click on 'Edit' on its Equal width constraint, change the multipler to 0.8 . This will make the side menu view width to be 80% of its parent view.
Build and run the app, you should see the side menu is overlapping the tab bar controller like this :
Now we have two container view on the main view controller 🙌!
As we are going to setup the top left profile button in the next step, let's move the side menu to the left so the tab bar controller is visible. We will set the leading constraint value of the side menu container to -350 for now.
Later we will create outlet for the leading constraints of these two container view so we can manipulate their horizontal position.
Setup top left profile button
Notice that for every tab in the tab bar controller, they have the same top left profile button (the rounded avatar on top left) like this :
To reduce the repeating work of creating the top left button for each of the tab, we can create a View Controller class named 'ContentViewController', and then subclass view controller in each tab to it.
We will add the following code to the ContentViewController.swift to create the left top profile button :
class ContentViewController: UIViewController {
let profileButton = UIButton(type: .custom)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
profileButton.setImage(UIImage(named: "Avatar") , for: .normal)
profileButton.contentMode = .scaleAspectFit
profileButton.translatesAutoresizingMaskIntoConstraints = false
// function performed when the button is tapped
profileButton.addTarget(self, action: #selector(profileButtonTapped(_:)), for: .touchUpInside)
// Add the profile button as the left bar button of the navigation bar
let barbutton = UIBarButtonItem(customView: profileButton)
self.navigationItem.leftBarButtonItem = barbutton
// Set the width and height for the profile button
NSLayoutConstraint.activate([
profileButton.widthAnchor.constraint(equalToConstant: 35.0),
profileButton.heightAnchor.constraint(equalToConstant: 35.0)
])
// Make the profile button become circular
profileButton.layer.cornerRadius = 35.0 / 2
profileButton.clipsToBounds = true
}
@IBAction func profileButtonTapped(_ sender: Any){
}
}
And then we replace the UIViewController subclass to ContentViewController subclass for HomeViewController, SearchViewController, NotificationViewController and MessageViewController.
// HomeViewController.swift
// Change the UIViewController to ContentViewController
class HomeViewController: ContentViewController {
...
}
// SearchViewController.swift
// Change the UIViewController to ContentViewController
class SearchViewController: ContentViewController {
...
}
// NotificationViewController.swift
// Change the UIViewController to ContentViewController
class NotificationViewController: ContentViewController {
...
}
// MessageViewController.swift
// Change the UIViewController to ContentViewController
class MessageViewController: ContentViewController {
...
}
Build and run the app, we have a top left profile button for each tab now 👌!
Create outlet for constraints
To be able to modify the constraint value in code, we need to create and link outlet of constraint to the MainViewController.
Select the Side Menu Container View in storyboard, double click the leading constraint, then hold control + drag the highlighted constraint to the view controller. Name the outlet as sideMenuViewLeadingConstraint.
Repeat the same step for the leading constraint of Tab Bar Container View (ContentView), and name the outlet as contentViewLeadingConstraint.
Create an outlet for the Side Menu Container View, name it as sideMenuContainer .
Your MainViewController.swift should look like this now :
class MainViewController: UIViewController {
@IBOutlet weak var sideMenuContainer: UIView!
@IBOutlet weak var contentViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var sideMenuViewLeadingConstraint: NSLayoutConstraint!
...
}
Toggle side menu when top left profile button is tapped
When you tap on the top left avatar circle button on Twitter app, the side menu appears if it is not visible yet, or the side menu disappear if it is currently visible. Now we are going to replicate this functionality.
Add a boolean variable to keep track if the side menu is visible on screen.
class MainViewController: UIViewController {
@IBOutlet weak var sideMenuContainer: UIView!
@IBOutlet weak var contentViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var sideMenuViewLeadingConstraint: NSLayoutConstraint!
// if side menu is visible
var menuVisible = false
...
}
In viewDidLoad, we set the leading constraint of side menu container to negative width of the screen width. So that the side menu container will be invisible at first.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
sideMenuContainerLeadingConstraint.constant = 0 - UIScreen.main.bounds.width
}
Next, we will add a function to toggle the side menu :
class MainViewController: UIViewController {
@IBOutlet weak var sideMenuContainer: UIView!
@IBOutlet weak var contentViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var sideMenuViewLeadingConstraint: NSLayoutConstraint!
var menuVisible = false
...
@objc func toggleSideMenu(fromViewController: UIViewController) {
if(menuVisible){
UIView.animate(withDuration: 0.5, animations: {
// hide the side menu to the left
self.sideMenuViewLeadingConstraint.constant = 0 - self.sideMenuContainer.frame.size.width
// move the content view (tab bar controller) to original position
self.contentViewLeadingConstraint.constant = 0
self.view.layoutIfNeeded()
})
} else {
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.5, animations: {
// move the side menu to the right to show it
self.sideMenuViewLeadingConstraint.constant = 0
// move the content view (tab bar controller) to the right
self.contentViewLeadingConstraint.constant = self.sideMenuContainer.frame.size.width
self.view.layoutIfNeeded()
})
}
menuVisible = !menuVisible
}
Then in the ContentViewController, we toggle the side menu when the profile button is tapped :
class ContentViewController: UIViewController {
...
@IBAction func profileButtonTapped(_ sender: Any){
// current view controller (self) is embed in navigation controller which is embed in tab bar controller
// .parent means the view controller that has the container view (ie. MainViewController)
if let mainVC = self.navigationController?.tabBarController?.parent as? MainViewController {
mainVC.toggleSideMenu(fromViewController: self)
}
}
}
Build and run the app, now we have toggle-able side menu 🤘!
Cool right? In the next part, we will add slide gesture to show/hide menu, and segue to profile view controller when one of the table view row in side menu view controller is tapped.
Want to dive in the completed project and try it out?
Get the completed project