Usually we have a predefined set of UIBarButtonItem
in the project that can be used in the project and multiple times like a left menu button to open a side menu, it can be used in different UIViewController
s also a close button that dismiss the presented view controller.
The classic way is to add these buttons as needed, but this introduce a code duplication and we all want to avoid that.
My come up with an approach, but it's far from being perfect :
enum BarButtonItemType {
case menu, close, notification
}
enum BarButtonItemPosition{
case right, left
}
extension UIViewController {
func add(barButtons:[BarButtonItemType], position: BarButtonItemPosition) {
let barButtonItems = barButtons.map { rightBarButtonType -> UIBarButtonItem in
switch rightBarButtonType {
case .menu:
return UIBarButtonItem(image: UIImage(named:"menu"),
style: .plain,
target: self,
action: #selector(presentLeftMenu(_:)))
case .notification:
return UIBarButtonItem(image: UIImage(named:"notification"),
style: .plain,
target: self,
action: #selector(showNotification(_:)))
case .close:
return UIBarButtonItem(image: UIImage(named:"close"),
style: .plain,
target: self,
action: #selector(dismissController(_:)))
}
}
switch position {
case .right:
self.navigationItem.rightBarButtonItems = barButtonItems
case .left:
self.navigationItem.leftBarButtonItems = barButtonItems
}
}
// MARK: Actions
@objc fileprivate func presentLeftMenu(_ sender:AnyObject) {
self.parent?.presentLeftMenuViewController(sender)
}
@objc fileprivate func dismissController(_ sender:AnyObject) {
self.dismiss(animated: true, completion: nil)
}
@objc fileprivate func showNotification(_ sender:AnyObject) {
let notificationViewController = UINavigationController(rootViewController:NotificationViewController())
self.present(notificationViewController, animated: true, completion: nil)
}
}
and then the usage:
override func viewDidLoad() {
super.viewDidLoad()
self.add(barButtons: [.close], position: .right)
self.add(barButtons: [.menu], position: .left)
}
The limitations of my approach are:
The extension needs to know how to instantiate new view controller (case of notification for example) and what if viewController must be inited with parameters
It assumes that you only want to present a UIViewController
Not elegant.
I am sure that there is better way with Swift language and protocol oriented programming that can achieve the intended result with more flexibility, any thoughts ?
Drop a button on the right-hand side of the navigation bar, then switch to the assistant editor so we can connect it to some code. Ctrl-drag from your new bar button item into your source code, and when you release your mouse button change Connection from “Outlet” to “Action”.
Go to the ViewController. swift file and add the ViewDidAppear method. a nav helper variable which saves typing. the Navigation Bar Style is set to black and the tint color is set to yellow, this will change the bar button items to yellow.
It seems that you're after having a default bar button configuration but specific (to subclass of UIViewController) bar button action implementations. You mentioned 1. "The extension needs to know how to instantiate new view controller" and your second point 2. "It assumes that you only want to present a UIViewController", thats a good sign that your extension should delegate that job to a subclass that knows what to do with those actions. Here I've done a sample implementation:
enum BarButtonItemPosition {
case right, left
}
enum BarButtonItemType {
case menu(BarButtonItemPosition)
case close(BarButtonItemPosition)
case notification(BarButtonItemPosition)
}
/// Has default implementation on UIViewControllers that conform to BarButtonActions.
protocol BarButtonItemConfiguration: class {
func addBarButtonItem(ofType type: BarButtonItemType)
}
/// Hate that we're forced to expose button targets to objc runtime :(
/// but I don't know any other way for the time being, maybe in Swift 6 :)
@objc protocol BarButtonActions {
@objc func presentLeftMenu(_ sender:AnyObject)
@objc func dismissController(_ sender:AnyObject)
@objc func showNotification(_ sender:AnyObject)
}
extension BarButtonItemConfiguration where Self: UIViewController, Self: BarButtonActions {
func addBarButtonItem(ofType type: BarButtonItemType) {
func newButton(imageName: String, position: BarButtonItemPosition, action: Selector?) {
let button = UIBarButtonItem(image: UIImage(named: imageName), style: .plain, target: self, action: action)
switch position {
case .left: self.navigationItem.leftBarButtonItem = button
case .right: self.navigationItem.rightBarButtonItem = button
}
}
switch type {
case .menu(let p): newButton(imageName: "", position: p, action: #selector(Self.presentLeftMenu(_:)))
case .notification(let p): newButton(imageName: "", position: p, action: #selector(Self.showNotification(_:)))
case .close(let p): newButton(imageName: "", position: p, action: #selector(Self.dismissController(_:)))
}
}
}
/// Conform to this in subclasses of UIViewController and implement BarButtonActions (its impl. differs from vc to vc).
protocol BarButtonConfigarable: BarButtonItemConfiguration, BarButtonActions {}
/// example
class SampleVC: UIViewController, BarButtonConfigarable {
override func viewDidLoad() {
super.viewDidLoad()
addBarButtonItem(ofType: .menu(.right))
addBarButtonItem(ofType: .menu(.left))
}
@objc func presentLeftMenu(_ sender:AnyObject) {
// TODO:
}
@objc func dismissController(_ sender:AnyObject) {
// TODO:
}
@objc func showNotification(_ sender:AnyObject) {
// TODO:
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With