So I want to create a slide out menu. I want the menu to slide out when a tab bar item/button is touched. So far, I have created a tab view controller with 4 different tab bar buttons. Each button leads to a different view controller and each view controller has been separated into their own storyboard.
I made an attempt of adding a UIViewController for the side menu to the class that is already established as a UITabBarController and I got the error:
Multiple inheritance from classes 'UITabBarController' and 'UIViewController'.
Is there a way around this?
I appreciate all the help
Apps tend to have a top level container like a Tab Bar Controller
that cannot be embedded inside a View. The approach here is to wrap the main body and the left menu each inside a Container View
. Now both of these elements can be arranged inside a wrapper View Controller
.
A Scroll View
is used to simulate opening and closing the menu by shifting the left menu on/off-screen.
Drop a View Controller
onto the Storyboard. This is your entry point into the app.
Check the box for Is Initial View Controller
.
Change the Simulated Size
from Fixed
to Freeform
. Set the Width to 568 so we can fit the menu and Tab Bar side-by-side.
Create a new Swift file and set this View Controller's class to ContainerVC.
import UIKit
class ContainerVC : UIViewController {
}
Inside Container View Controller, drop in a Scroll View
and add constraints in all directions.
Check the box for Scrolling Enabled
. This allows you to pan the screen to slide the menu. You might need to disable this if your app uses horizontally scrolling elements.
Check the box for Paging Enabled
. This snaps the menu to either the open or closed state.
Uncheck the box for Bounces
. You don't really want to scroll past the right edge of the Tab Bar Controller.
Wire the IBOutlet to ContainerVC:
@IBOutlet weak var scrollView: UIScrollView!
The left container holds the menu, and is not quite the full width of the screen.
Drag a Container View
into the left side of the Scroll View
.
Add constraints for top, left, and right to the containing Scroll View
.
Hard-code the width to 260.
Add a constraint for Equal height
with the ContainerVC's embedded View. Note: don't constrain the height to the Scroll View.
Delete the embedded View Controller
that came with the Container View
.
Drop a new Table View Controller
(Any view according to your need) onto the Storyboard, and connect using an embed segue.
The right container holds the main body of your app, namely the Tab Bar Controller
.
Drag a second Container View
into the right side of the Scroll View
.
Add constraints to the containing Scroll View
for top, right, and bottom. Connect horizontally to the left Container View you created earlier.
Constraint both Equal height
and Equal width
to the ContainerVC's embedded View.
Note: do not constrain these to the Scroll View.
Again, delete the embedded View Controller that came free with the Container View
. Instead, create an embed
segue to the Tab Bar Controller
.
To create a little visual separation between the two containers, add a Runtime Attribute
to the Right Container. Add layer.shadowOpacity
with a number of 0.8
.
Embed each tab inside a Navigation Controller
. Doing so gives you a free Navigation Bar
.
Drag a Bar Button Item
into the top left corner of each Navigation Bar
.
Wire an IBAction
to each controller. These will fire off a Notification
to the great-grandfather ContainerVC
to toggle the menu.
@IBAction func toggleMenu(sender: AnyObject) {
NotificationCenter.default().post(name: Notification.Name("toggleMenu"), object: nil)
}
Finally add the below code into ContainerVC:
class ContainerVC : UIViewController {
// This value matches the left menu's width in the Storyboard
let leftMenuWidth:CGFloat = 260
// Need a handle to the scrollView to open and close the menu
@IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
// Initially close menu programmatically. This needs to be done on the main thread initially in order to work.
DispatchQueue.main.async() {
self.closeMenu(animated: false)
}
// Tab bar controller's child pages have a top-left button toggles the menu
NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.toggleMenu), name: NSNotification.Name(rawValue: "toggleMenu"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.closeMenuViaNotification), name: NSNotification.Name(rawValue: "closeMenuViaNotification"), object: nil)
// Close the menu when the device rotates
NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
// LeftMenu sends openModalWindow
NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.openModalWindow), name: NSNotification.Name(rawValue: "openModalWindow"), object: nil)
}
// Cleanup notifications added in viewDidLoad
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc func openModalWindow() {
performSegue(withIdentifier: "openModalWindow", sender: nil)
}
@objc func toggleMenu() {
scrollView.contentOffset.x == 0 ? closeMenu() : openMenu()
}
// This wrapper function is necessary because closeMenu params do not match up with Notification
@objc func closeMenuViaNotification(){
closeMenu()
}
// Use scrollview content offset-x to slide the menu.
func closeMenu(animated:Bool = true){
scrollView.setContentOffset(CGPoint(x: leftMenuWidth, y: 0), animated: animated)
}
// Open is the natural state of the menu because of how the storyboard is setup.
func openMenu(){
print("opening menu")
scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}
@objc func rotated(){
if UIDeviceOrientationIsLandscape(UIDevice.current.orientation) {
DispatchQueue.main.async() {
print("closing menu on rotate")
self.closeMenu()
}
}
}
}
extension ContainerVC : UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrollView.contentOffset.x:: \(scrollView.contentOffset.x)")
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
scrollView.isPagingEnabled = true
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollView.isPagingEnabled = false
}
}
The code accomplishes the following:
Hopefully you have a simple slideout left menu on which to build the rest of your app.