How do I set a custom image to all back buttons of view controllers pushed in a UINavigationController?
My issues are:
must look like leftBarButtonItem, position-wise (because the backBarButtonItem itself is too glued to the left and I can't seem to change it's horizontal alignment).
has to be on all back actions (instead of manually setting on each view controller).
having a method setCustomBackButton and calling it on each view controller is also not an option, I'm looking for something like UINavigationBar.appearance(), i.e., throughout the app.
Something like this:
But with the back action working without me manually setting the selector on each view controller.
UPDATE: In response to Joe's solution, I'm getting that error:
UINavigationBar.appearance().backIndicatorImage = UIImage(named: "backArrow")
See Here: https://www.raywenderlich.com/108766/uiappearance-tutorial
Below answer based on the following OP answers:
Custom Back Button With Image and How to remove all navigationbar back button title
Try below code in didFinishLaunchingWithOptions method in AppDelegate.
To setting up a custom back button:
let backArrowImage = UIImage(named: "back") // set your back button image here
let renderedImage = backArrowImage?.withRenderingMode(.alwaysOriginal)
UINavigationBar.appearance().backIndicatorImage = renderedImage
UINavigationBar.appearance().backIndicatorTransitionMaskImage = renderedImage
To hide a back button title:
let barAppearace = UIBarButtonItem.appearance()
barAppearace.setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), for:UIBarMetrics.default)
Output: Updated
Update:
You need to add the following code to your More Information viewController to keep the title position.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
You can create your own subclass of UINavigationController and change the button inside the navigationController(_:willShow:animated:) delegate method as follows:
class MyNavigationController: UINavigationController, UINavigationControllerDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
interactivePopGestureRecognizer?.delegate = self
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if viewController != self.viewControllers.first { // don't add button to rootViewController
let backButton = UIBarButtonItem(image: UIImage(named: "backArrow"), style: .plain, target: self, action: #selector(popViewController(animated:)) )
viewController.navigationItem.leftBarButtonItem = backButton
}
}
}
Theoretically the above delegate method could live anywhere, but this way its logical and easy to select where you want to have this functionality.
Also don't forget to set the interactivePopGestureRecognizer delegate for not loosing the edge swipe gesture to go back (this somehow breaks when setting a new leftBarButtonItem).
The above method could be further improved by keeping track of which view controllers were already shown and then only replace the leftBarButtonItem on new ones (right now it also replaces it when going back/popping to an already shown view controller).
Try this Swift 4.2
extension YouFirstViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if !(viewController is YouFirstViewController) {
let backButton = UIBarButtonItem(image: UIImage(named: "icnBack"), style: .plain, target: self, action: #selector(popview))
viewController.navigationItem.leftBarButtonItem = backButton
}
}
#objc func popview() {
navigationController?.popViewController(animated: true)
}
}
onYouFirstViewController
class YouFirstViewController: UIViewcontroller {
override func viewDidLoad() {
self.navigationController?.delegate = self
}
}
Related
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
UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), for:UIBarMetrics.default)
I use above to remove the backButtonTitle prior to iOS 11. But on iOS 11 that is not properly working.
Arrow shifts bit downwards.
How to fix this?
Edit: Other way by erasing Title can solve my problem but my concern is why that old way is not working anymore.
The other way is to set UINavigationControllerDelegate for your navigationController and erase the title in its function.
class NoBackButtonTitleNavigationDelegate: UINavigationControllerDelegate {
func navigationController(
_ navigationController: UINavigationController,
willShow viewController: UIViewController,
animated: Bool
) {
viewController.navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil)
}
}
Use this code,
var newBackButton = UIBarButtonItem(image: UIImage(named: "back"), style: .plain, target: self, action: #selector(self.back))
navigationItem?.leftBarButtonItem = newBackButton
Create Navigation controller class as below. Assign this "CustomNavViewController" to UINavigationController in your StoryBoard
class CustomNavViewController: UINavigationController,UINavigationControllerDelegate
{
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func navigationController(
_ navigationController: UINavigationController,
willShow viewController: UIViewController,
animated: Bool
) {
viewController.navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil)
}
}
So, no need to do it in every viewControllers.
At last remove below line from AppDelegate class if present,
UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), for:UIBarMetrics.default)
Instead of moving title vertically you can move horizontally. Also in case the title is long you can make its color transparent. Works just fine me:
let barButtonItemAppearance = UIBarButtonItem.appearance()
barButtonItemAppearance.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.clear], for: .normal)
barButtonItemAppearance.setBackButtonTitlePositionAdjustment(UIOffsetMake(-200, 0), for:UIBarMetrics.default)
I want to be able to change the action of the back bar button item on a specific UIViewController in my navigation controller so that it pops to the root view controller. I've tried the following but they don't work:
let backButton = UIBarButtonItem(title: nil, style: .plain, target: self, action: #selector(back))
self.navigationItem.backBarButtonItem = backButton
and
self.navigationItem.backBarButtonItem?.action = #selector(back)
Any suggestions?
You should use self.navigationItem.leftBarButtonItem = backButton
Good luck
First of all backBarButtonItem action not works because you can only change back button title,take a look question about it here.
Solution
In ViewController from which you want to pop to root ViewController you need to set as a delegate of UINavigationControllerDelegate
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
and implement UINavigationControllerDelegate this method`
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if viewController.isKind(of:PreviousViewController.self) {
navigationController.popToRootViewController(animated: animated)
}
}
If my answer not fit your needs you can check similar question here.
To keep the same look and feel of the back button but change the action, see the ViewWillDisappear answer to the question regarding, "Execute action when back bar button of UINavigationController is pressed" Execute action when back bar button of UINavigationController is pressed
This is your solution, just need to set target and selector, nothing more.
private func setNavBar() {
let item = navigationItem.backBarButtonItem
item?.target = self
item?.action = #selector(self.donePressed)
navigationItem.leftBarButtonItem = item
}
#objc private func donePressed() {
self.dismiss(animated: true, completion: nil)
}
I seem to be unable to correctly set up the back button of the navigationController programmatically that shows when a previous view uses
self.navigationController?.pushViewController(newView, animated: true)
I hide all of the views from the previous view in it's viewDidDisappear using a loop and in the new view presented in the viewDidAppear I attempt to set the action of the back button in various ways; however, while I can succeed in manipulating the back button that is automatically shown such as hiding it or changing it's image I am unable to set up it's action.
Any insight would be appreciated as none of the answers I have found seem to work correctly. Also this is done without any use of the storyboard
if let img = UIImage(named: "backButton") {
self.navigationController?.navigationBar.backIndicatorImage = img
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = img
print("IMAGE")
}
topItem.backBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Rewind, target: self,
action:#selector(self.backButtonAction(_:)))
In your case add a custom button on the Navigation.
class YourViewController: UIViewController {
//Navigation Items.
//left bar button item.
private var leftBarButtonItem : UIBarButtonItem!
//left button.
private var navigationLeftButton : UIButton!
//Your other variable/object declaration.
func viewDidLoad() {
super.viewDidLoad()
self.leftBarButtonItem = UIBarButtonItem()
}
func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.setNavigationBackButton()
}
private func setNavigationBackButton() {
if(self.navigationLeftButton == nil) {
self.navigationLeftButton = UIButton(type: UIButtonType.System)
}
//Styling your navigationLeftButton goes here...
self.navigationLeftButton.addTarget(self, action: Selector("backButtonTapped"), forControlEvents: UIControlEvents.TouchUpInside)
self.leftBarButtonItem.customView = self.navigationLeftButton
self.navigationItem.leftBarButtonItem = self.leftBarButtonItem
}
func backButtonTapped(AnyObject:sender) {
// Here add your custom functionalities.
// Note, this will not pop to previous viewcontroller,
}
}
I'm trying to create a custom navigation bar, and I'm having difficulty modifying different parts of the navigation bar. I can change the color of the background, but I can't seem to add buttons or change the title.
class CustomNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
// changing the background color works
self.navigationBar.barTintColor = UIColor.purpleColor()
// none of this works
let leftButton = UIBarButtonItem(title: "Info", style: UIBarButtonItemStyle.Plain, target: self, action: #selector(openInfo))
self.navigationItem.leftBarButtonItem = leftButton
self.navigationItem.title = "MYTITLE"
}
}
I'm not sure if the fact that I'm trying to integrate this NavigationController with a TabBarController is affecting the way the view loads, but this custom NavigationController is being subclassed by each tab in the TabBarController.
According to UINavigationItem class reference, each view controller has its own UINavigationItem instance. "The managing UINavigationController object uses the navigation items of the topmost two view controllers to populate the navigation bar with content", which means its UIViewController's responsibility to create the navigation item content such as left bar item or title.
I can understand that you want to provide the same appearance of navigation bar throughout the app. But why do you want to set the same title for all view controllers? However, if same title and same left bar item for all view controllers is what you need. Here are two solutions:
1). Make an extension to UIViewController:
extension UIViewController {
func customAppearance() {
let leftButton = UIBarButtonItem(title: "Info", style: UIBarButtonItemStyle.Plain, target: self, action: #selector(openInfo))
self.navigationItem.leftBarButtonItem = leftButton
self.navigationItem.title = "MYTITLE"
}
func openInfo() {
// do what you want
}
}
And then whenever you need a customised navigation bar for a view controller, you call this customAppearance function:
let vc = YourViewController()
vc.customAppearance()
2). Subclass the UIViewController:
class CustomViewController: UIViewController {
override func viewDidLoad() {
let leftButton = UIBarButtonItem(title: "Info", style: UIBarButtonItemStyle.Plain, target: self, action: #selector(openInfo))
self.navigationItem.leftBarButtonItem = leftButton
self.navigationItem.title = "MYTITLE"
}
func openInfo() {
}
}
And your every other view controllers subclass this CustomViewController.
For customzing UINavigationBar's appearance, you can set it like:
UINavigationBar.appearance().barTintColor = UIColor.purpleColor()