Default UIBarButtonItems with protocols
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.