Default UIBarButtonItems with protocols

Roland Leth
2 min readJun 17, 2016

--

Having to create the same set of buttons over and over can become cumbersome. We’ll try to make use of protocols and implement some default ones. Let’s start with that:

protocol CanGoBack {   func back() } extension CanGoBack where Self: UIViewController {    func back() { 
if presentingViewController != nil
&& navigationController?.childViewControllers.first == self {
navigationController?.dismissViewControllerAnimated(true, completion: nil)
}
else {
navigationController?.popViewControllerAnimated(true)
}
}
func setupBackButton() {
guard
navigationController?.childViewControllers.first != self
else { return }
let backButton = UIButton.myBackButton()
backButton.addTarget(self, action: #selector(back),
forControlEvents: .touchUpInside)

navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton)
}
}

But here we stumble upon the problem: back has to be marked as @objc, to be able to create the #selector; but to do that, we’d actually have to mark the whole protocol as @objc; but if we did that, we’d need to implement the back method in all places where we conform to CanGoBack, meaning the only thing we’d be saving, would be the creation of the button, but not the action as well.

Sadly, there’s no way to work around this without some subclassing, but the good news is it’s rather straightforward:

class Button: UIButton { 

// `action` is the internal variable of type `Selector`.
private let buttonAction: () -> Void

@objc
private func performButtonAction() {
buttonAction()
}
init(action: @escaping () -> Void) {
buttonAction = action

let image = UIImage(named: "back")

super.init(frame: CGRect(origin: .zero,
size: image?.size ?? .zero))
setImage(image, forState: .normal) addTarget(self, action: #selector(performButtonAction),
forControlEvents: .touchUpInside)
}
}

We then modify our protocol, accordingly:

func setupBackButton() { 
guard
navigationController?.childViewControllers.first != self
else { return }

let backButton = Button(action: { [weak self] in self?.back() } )

navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton)
}

And from now on, all we have to do for an UIViewController to have a back button and action is:

class SecondMainViewController: UIViewController, CanGoBack {    override viewDidLoad() {
super.viewDidLoad()
setupBackButton()
}
}

We could go a step further and subclass UIBarButtonItem, but since the logic is exactly the same, there’s no reason to include it here.

Originally published at rolandleth.com.

--

--

Roland Leth
Roland Leth

Written by Roland Leth

iOS & web developer. Blogger about life and tech at https://rolandleth.com. Founder at https://runtimesharks.com.

No responses yet