I'm trying to set in almost each page a custom back button and I'm repeating the same code in each page like this;
let buttonItem = UIBarButtonItem(image: UIImage(named: "arrow_back"), style: UIBarButtonItemStyle.Plain, target: self, action:"popBack")
buttonItem.tintColor=UIColor.blackColor()
navigationItem.leftBarButtonItem = backButtonItem()
And I think this is the wrong way for it. So please could you tell what is the best way for this?
I've done this by creating a category on UIViewController. In that file I created a method called addBackButton, where you can put your code in once, then expose the method in your .h file. Then in any of your view controller subclasses you can import your category and call [self addBackButton];
You have various options here.
1) UIViewController extension
extension UIViewController {
func brandedBackButton() {
let buttonItem = UIBarButtonItem(image: UIImage(named: "back"), style: UIBarButtonItemStyle.Plain, target: self, action:"popBack")
buttonItem.tintColor=UIColor.blackColor()
navigationItem.leftBarButtonItem = buttonItem
}
}
and then just call in your view controller
override func viewDidLoad() {
super.viewDidLoad()
brandedBackButton()
}
2) Base View Controller Class
You would basically put a class in between your controllers and the UIViewController.
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let buttonItem = UIBarButtonItem(image: UIImage(named: "back"), style: UIBarButtonItemStyle.Plain, target: self, action:"popBack")
buttonItem.tintColor=UIColor.blackColor()
navigationItem.leftBarButtonItem = buttonItem
}
}
and then you would inherit from this BaseViewController instead of standard UIViewController.
class YourWhateverViewController: BaseViewController {
//implementation here....
}
Both the category and the base class can also accommodate the popBack custom method so you would end up really with literally 10 characters to get this behaviour anywhere...
I suggest you create a custom UIButton class and reuse it every time you want it.
class backButtonItem: UIBarButtonItem {
convenience init(target: AnyObject?) {
self.init(image: UIImage(named: "arrow_back"), style: .Plain, target: target, action: "popBack")
self.tintColor = UIColor.blackColor()
}}
just type the following script when you use it:
navigationItem.leftBarButtonItem = backButtonItem(target: self)
Related
When I create a UIBarButton programatically in the viewDidLoad() method it does not show up when I run the program. I am not sure what is happening. Sorry I am new to xcode and do not understand how everything works.
var menuButton: UIBarButtonItem = UIBarButtonItem()
override func viewDidLoad() {
super.viewDidLoad()
// creating the refresh control object
menuButton = UIBarButtonItem.init(title: "Menu", style: .plain, target: self, action: nil)
}
It should create a UIBarButton in the navigation controller, instead there is nothing there.
You need to set navigationItem.rightBarButtonItem = menuButton
I added an extension to UIViewController to add a close button
extension UIViewController {
func addCloseButton() {
let button = UIBarButtonItem(image: #imageLiteral(resourceName: "bar_close"),
landscapeImagePhone: nil,
style: .done,
target: self,
action: #selector(UIViewController.dismiss(animated:completion:)))
navigationItem.leftBarButtonItem = button
}
}
When i tap the barbutton i get a crash directly to AppDelegate.
Any hints? Seems related to the selector.
You can't use dismiss(animated:completion:) as selector here because it takes two arguments bool and closure and bar button action pass args as UIBarButtonItem which cause app crash.
so change your code like this.
extension UIViewController {
func addCloseButton() {
let button = UIBarButtonItem(image: #imageLiteral(resourceName: "rightgreen"),
landscapeImagePhone: nil,
style: .done,
target: self,
action: #selector(onClose))
navigationItem.leftBarButtonItem = button
}
#objc func onClose(){
self.dismiss(animated: true, completion: nil)
}
}
However this question has accepted answer which load extra one method addCloseButton in each and every viewcontroller still posting a answer will going to help someone
NOTE : This example for adding barbutton item automatically and also handle action for pop view controller.
As Protocol extension doesn't provide a to implement selector methods so to get the rid of it I have created this solution.
First thing you need is BaseVC which is subclass of UIViewController and all of your view controller going to be inherited by BaseVC like your class LoginVC:BaseVC ...
now declare protocol
protocol PopableClass {
func popSelf (animated:Bool)
}
extension PopableClass where Self : UIViewController {
func popSelf (animated:Bool) {
self.navigationController?.popViewController(animated: animated)
}
}
In your Base VC add two methods and call setupNavigationBar from viewDidLoad
func setupNavigationBar () {
if self is PopableClass {
let barbuttonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "back"), landscapeImagePhone: #imageLiteral(resourceName: "back"), style: .plain, target: self, action: #selector(popViewController))
self.navigationItem.leftBarButtonItem = barbuttonItem
}
}
//--------------------------------------------------------------------------------
#objc func popViewController () {
if self is PopableClass {
(self as! PopableClass).popSelf(animated: true)
}
}
You did it !!
Now in whatever class you need back button to pop view controller just use like this
class PushedClass: BaseVC,PopableClass
Hope it is helpful
I have a Navigation controller and I'm trying to put a button on the right of navigation bar but I can't handle the tap action. I'm declaring the UIBarButtonItem like this
let navigationButton = UIBarButtonItem.init(title: "Logout", style: .done, target: self, action: #selector(RestaurantsListViewController.logoutAction))
I'm adding the button on the viewDidLoad func
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = navigationButton
}
and the function that I'm trying to use to handle the tap event is this
func logoutAction(sender: AnyObject?){
print("Logout")
}
but when I press the button, the message is not printed in console.
Try this:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "ButtonName", style: .done, target: self, action: #selector(YourViewController.yourAction))
}
let okbtn = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(ViewController.logoutAction))
Try This
otherwise You need to replace func like
func logoutAction()
{
print("logout")
}
The issue here is when you create navigationButton as a class property, it gets initialized before self is initialized. So self doesn't exist yet when you pass it in as the target of the button.
There are a couple ways to fix it, including the answer by #Mannopson where you initialize the button inside the viewDidLoad, which ensures that self has already been created.
Another way to solve this issue is to declare navigationButton to be a lazy var:
lazy var navigationButton = UIBarButtonItem.init(title: "Logout", style: .done, target: self, action: #selector(RestaurantsListViewController.logoutAction))
The lazy var ensures that the property only gets initialized when the property gets accessed (hence it being a lazy initialization). Since the first time it is accessed happens in viewDidLoad, we can also be sure that self has been created. Hope this gives you more context!
How can i create one navigation menu for whole project.
I created one but for specific view only using SWRevealViewController.
You cannot do it in two ways.
a) Create a ViewController with side menu and subclass the view controller every time you need a side menu.
class BaseViewController: UIViewController {
override func viewDidLoad() {
let menuButton = UIBarButtonItem.init(image: image, style: .plain, target: self, action: #selector(funcToCall))
navigationItem.leftBarButtonItem = menuButton
}
}
and subclass BaseViewController
class MyViewController: BaseViewController {
}
b) Another method and the better one is using extension
extension UIViewController {
func addMenu() {
let menuButton = UIBarButtonItem.init(image: image, style: .plain, target: self, action: #selector(SWRevealViewController.rightRevealToggle(_:)))
navigationItem.leftBarButtonItem = menuButton
}
}
and call addMenu() in MyViewController
class MyViewController: UIViewController {
override func viewDidLoad() {
addMenu()
}
}
One of the many ways is to create a swift file Helper.swift
then
import UIKit
extension UIViewController {
func addMenu() {
let menuButton = UIBarButtonItem.init(image: image, style: .plain, target: self, action: #selector(SWRevealViewController.rightRevealToggle(_:)))
navigationItem.leftBarButtonItem = menuButton
}
}
You can embed your RevealVC with navigation controller and add navigation item to RevealVC, but such a menu will be static.
I usually use TopViewController class:
class TopViewController: UIViewController {
#IBOutlet weak var menuButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
if self.revealViewController() != nil {
revealViewController().rightViewRevealWidth = view.frame.size.width / 2
menuButton.target = revealViewController()
menuButton.action = #selector(SWRevealViewController.rightRevealToggle(_:))
view.addGestureRecognizer(revealViewController().panGestureRecognizer())
}
}
}
Each view controller will just inherit this options. In this case you should create navigationVC in storyboard for each view controller (I think it's the best solution). Do not forget to drag outlets from storyboard to TopViewController.
Creating only one NavigationVC is impossible i suppose.
I have done the following customisation to my embedded UINavigationcontroller. I've done this in the view controller of the view that will show up as first.
However I'm trying to clean it up so that I don't have this all in my UIView class by creating a separate class that will take care of this. The code I'm currently using
self.navigationController?.navigationBar.setBackgroundImage(UIImage(named: "navigation.background"), for: .default)
let backButton = UIImage(named: "Back.button")?.withRenderingMode(.alwaysOriginal)
let backButtonHigh = UIImage(named: "Back.button.highlighted")?.withRenderingMode(.alwaysOriginal)
self.navigationController?.navigationBar.backIndicatorImage = backButton
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = backButtonHigh
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
What I've tried to do is extend the UInavigationcontroller in a new class like this
class RSRNavigationController: UINavigationController{
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.setBackgroundImage(UIImage(named: "navigation.background"), for: .default)
let backButton = UIImage(named: "Back.button")?.withRenderingMode(.alwaysOriginal)
let backButtonHigh = UIImage(named: "Back.button.highlighted")?.withRenderingMode(.alwaysOriginal)
self.navigationController?.navigationBar.backIndicatorImage = backButton
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = backButtonHigh
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
}
this doesn't work. It will run without problems but it won't actually do anything.
I've also tried to do the following
class RSRNavigationController: UINavigationBar{
override func draw(_ rect: CGRect) {
super.draw(rect)
self.setBackgroundImage(UIImage(named: "navigation.background"), for: .default)
let backButton = UIImage(named: "Back.button")?.withRenderingMode(.alwaysOriginal)
let backButtonHigh = UIImage(named: "Back.button.highlighted")?.withRenderingMode(.alwaysOriginal)
self.backIndicatorImage = backButton
self.backIndicatorTransitionMaskImage = backButtonHigh
//self.backItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
However I can't seem to set the title for the back button.
Does anyone know how to fix this?
Your problem lies here, when you reference navigationController INSIDE your custom navigationController class
Instead of doing
self.navigationController?.navigationBar.backIndicatorImage = backButton
do this
self.navigationBar.backIndicatorImage = backButton
Reason?
Because when you extend your custom class by UINavigationController, it becomes a navigationController itself. And by doing self.navigationController you are telling your app to look for a navigationController within which your custom navigationCOntroller resides. Ofcourse such a nav controller does not exist because your custom navController is the main or the top navController of the app. So just remove the self.navigationController part from your custom class, the rest of the code that you have written in your viewDidLoad should work good.
If you still have any questions feel free to ask
You do not change these properties in the navigation controller itself, you change it in the viewController that is embedded inside a navigation controller.
These customization has to be done in a parent ViewController of your view controllers.
This Answer has more info:
https://stackoverflow.com/a/16913435/4004429
if you just want to custom the backButton of the navigationBar,you can add this code in your child view controller :
func setupLeftBarItem() -> Void {
let originalImage = UIImage.init(named: "leftBatItem")?.withRenderingMode(.alwaysOriginal)
let leftBarItem = UIBarButtonItem.init(image: originalImage, style: .done, target: self, action: #selector(leftBarButtonItemDidTouch))
navigationItem.leftBarButtonItem = leftBarItem
}
func leftBarButtonItemDidTouch() -> Void {
_ = navigationController?.popViewController(animated: true)
}