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:
@objc protocol CanGoBack { // Sounds better than Backable :)
func back()
}
extension CanGoBack where Self: UIViewController {
func back() {
if presentingViewController != nil, navigationController?.childViewControllers.first == self {
navigationController?.dismiss(animated: true)
}
else {
navigationController?.popViewController(animated: true)
}
}
func setupBackButton() {
guard navigationController?.childViewControllers.first != self else { return }
let backButton = UIButton.myBackButton()
backButton.addTarget(self, action: #selector(back), for: .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 that's not possible; @objc
is to be used for members of classes, @objc protocols, and concrete extensions of classes.
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: () -> 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, by replacing the UIButton
creation:
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 need to include it here.