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.

side menu and tab bar

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 :

container view

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.

create new file

mainVC

Drag container view

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.

selectSegue

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".

controlDrag

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 :

Constraint gif

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 :

Safe Area

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 :

Double click top constraint

Change the "Safe Area" in First/Second Item to "Superview", and then set the constant to 0 .

Safe Area

constant zero

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.

initial view controller

Build and run the app, you now should have a container view containing the tab bar controller 🙌.

super view

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.

label content view

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.

container view

Add a label to this container view, we will label it as SideMenuView.

side menu label

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.

proportional width

Build and run the app, you should see the side menu is overlapping the tab bar controller like this :

side menu run

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.

side menu leading

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 :

top left profile button

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.

create new file

content VC

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 👌!

top left profile button

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.

double click constraint

control drag to view controller

side menu view constraint outlet

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
}

side menu leading explanation

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 🤘!

toggling

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.

Continue to read Part 2.



Want to dive in the completed project and try it out?

Get the completed project