Search
Filters
Blog options
Close

A cleaner SWRevealViewController implementation using Swift extensions

A cleaner SWRevealViewController implementation using Swift extensions

Extensions allow us to extend the functionality of the native Swift language with our own functionality.

I'd like to provide a real world example where extending a specific type really reduces clutter in my applications. I often use the popular SWRevealViewController library to add a neat sidebar menu to my applications. Once I have the application setup, I have to run the following block of code in my viewDidLoad:


override func viewDidLoad() {
    super.viewDidLoad()
        
    if self.revealViewController() != nil {
        self.menuButton.target = self.revealViewController()
        self.menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
        self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
        self.revealViewController().rearViewRevealWidth = 200
    }
}

I have to add this exact block of code in every single ViewController or TableViewController that implements SWRevealViewController. This leads to unnecessary clutter and repetition...

Rule of thumb

In programming, when you find yourself repeating something consistently, there's probably a better way to do it.

So we write an extension to UIViewController, and since UITableViewController inherits from UIViewController, we cover our table view's as well.


extension UIViewController {
    func setupRevealMenu(controller : UIViewController) {
        if self.revealViewController() != nil {
            controller.menuButton.target = self.revealViewController()
            controller.menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
         self.view.addGestureRecognizer(controller.revealViewController().panGestureRecognizer())
            self.revealViewController().rearViewRevealWidth = 200
        }
    }
}

This actually gives us an error, because not every View Controller and Table View Controller use an SWRevealViewController, so that menuButton doesn't always exist! We need to constrain our extension to just those that actually derive from SWRevealViewController. In order to make this constraint possible, we need to define a protocol that will act as a contract or guarantee that our menuButton will indeed exist.


protocol Revealable {
    weak var menuButton: UIBarButtonItem! { get set }
}

Now that we have that guarantee, we just need to add this Revealable protocol to every single Controller that uses an SWRevealViewController.

class HomeTableViewController: UITableViewController, Revealable {/* ... */ }

Now, we just update our extension with our new constraint.


extension UIViewController { 
func setupRevealMenu<T : UIViewController where T : Revealable>(controller : T) {
if self.revealViewController() != nil {
controller.menuButton.target = self.revealViewController()
controller.menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(controller.revealViewController().panGestureRecognizer())
self.revealViewController().rearViewRevealWidth = 200
}
}
}

If you have any questions, please feel free to ask in the comments!


Based on @Stack Help's question in the comments:

 

So here's something that I did in a recent app that I think will put you on the right track:


protocol Revealable : class {
    
}

extension Revealable where Self: UIViewController {
    func setupRevealMenu() {
        
        if self.revealViewController() != nil {
            let button = UIButton.init(type: .custom)
            button.setImage(UIImage.init(named: "box-trans.png"), for: UIControlState.normal)
            button.addTarget(self.revealViewController(), action:#selector(SWRevealViewController.revealToggle(_:)), for: UIControlEvents.touchUpInside)
            button.frame = CGRect.init(x: 0, y: 0, width: 30, height: 30)
            let barButton = UIBarButtonItem.init(customView: button)
            
            self.navigationItem.leftBarButtonItem = barButton
            self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
            self.revealViewController().rearViewRevealWidth = 200
            self.revealViewController().panGestureRecognizer().isEnabled = false
        }
    }
}

The code above adds the button programmatically.
 
Resources