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
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
}
}
I have a NavigationBar at the top of a TableView. It looks nice opening/closing the search.
However, if I click on a button in a cell and get directed to another page (with segue); and then use Back button to unwind, it seems like bugged.
()
So it looks like it is pressed and opened but it shouldn't have. It should be looked like the top picture instead (just UIBarButtonItem - search button)
I couldn't figure out the issue creating this problem.
Please note that < Back is created automatically and I didn't write any code to create it. Is there something I am doing wrong?
Update: Added some snippets...
First, created a different class for handling the search
class SearchBarViewController: UIViewController, UISearchBarDelegate {
var searchBar : UISearchBar?
var searchBarWrapper : UIView?
var searchBarButtonItem : UIBarButtonItem?
func constructSearchBar()
{
searchBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: "showSearchBar")
self.navigationItem.rightBarButtonItem = searchBarButtonItem
}
func showSearchBar() {
// styling & configuration
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
UIView.animateWithDuration(0.2, animations: {
self.searchBar?.resignFirstResponder()
self.searchBarWrapper?.alpha = 0
}, completion: { (success) -> Void in
self.searchBar = nil
self.searchBarWrapper = nil
self.navigationItem.rightBarButtonItem = self.searchBarButtonItem
})
}
}
And my ViewController:
class ViewController: SearchBarViewController {
override func viewDidLoad() {
super.viewDidLoad()
constructSearchBar()
}
}
Regarding to emrys57's answer, I tried adding viewWillAppear() in my ViewController but I couldn't make it work, as my cancel looks a little different:
override func viewWillAppear(animated: Bool) {
super.viewDidAppear(animated)
// Here, I couldn't figure out what to put because
// my searchBarCancelButtonClicked() needs searchBar and
// forces me to use (!) but then it says, it's optional..
}
The answer is...
override func viewWillAppear(animated: Bool) {
super.viewDidAppear(animated)
navigationItem.titleView = nil
constructSearchBar()
}
You have not posted code, so it's not entirely clear what's gone wrong. Using UISearchBar, I think you must be handling the buttons separately yourself, as opposed to using UISearchController. I think that you may not be clearing away the search bar when coming back from the second VC. This code clears out the search bar in viewWillAppear:
class ViewController: UIViewController {
var cancelButton: UIBarButtonItem?
var searchButton: UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
cancelButton = UIBarButtonItem(barButtonSystemItem: .Cancel, target: self, action: Selector("searchCancelPressed:"))
searchButton = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: Selector("searchPressed:"))
}
override func viewWillAppear(animated: Bool) {
super.viewDidAppear(animated)
searchCancelPressed(nil)
}
func searchPressed(sender: AnyObject) {
navigationItem.titleView = UISearchBar()
navigationItem.rightBarButtonItem = cancelButton
}
func searchCancelPressed(sender: AnyObject?) {
navigationItem.titleView = nil
navigationItem.rightBarButtonItem = searchButton
}
}
and that is working nicely for me when I do a push from a button to the second VC and then hit back.
Following the edit to the original question, this code seems to work, although it may not be the most elegant way of constructing the answer:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
navigationItem.titleView = nil
searchBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: "showSearchBar")
navigationItem.rightBarButtonItem = searchBarButtonItem
}
The function constructSearchBar no longer needs to be called in viewDidLoad, and can be deleted.
When I push a UIViewController, it has some title in back button at new UIViewController, if the title has a lot of text, It does not look good in iPhone 4s So I want to remove it.
If I add some code in prepareForSegue function, it is going to be a trouble.
Any better way to achieve this?
If you want back arrow so following code put into AppDelegate file into didFinishLaunchingWithOptions method.
For Objective-C
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60) forBarMetrics:UIBarMetricsDefault];
For Swift
let BarButtonItemAppearance = UIBarButtonItem.appearance()
BarButtonItemAppearance.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .normal)
Another option give below.
In Objective C
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"" style:UIBarButtonItemStylePlain target:nil action:nil];
In Swift
self.navigationItem.backBarButtonItem = UIBarButtonItem(title:"", style:.plain, target:nil, action:nil)
UPDATE :
let BarButtonItemAppearance = UIBarButtonItem.appearance()
let attributes: [NSAttributedStringKey: Any] = [
BarButtonItemAppearance.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .normal)
NSAttributedStringKey.font: UIFont.systemFont(ofSize: 0.1),
NSAttributedStringKey.foregroundColor: UIColor.clear]
BarButtonItemAppearance.setTitleTextAttributes(attributes, for: .normal)
BarButtonItemAppearance.setTitleTextAttributes(attributes, for: .highlighted)
UPDATE SWIFT 4.1 :
let attributes = [NSAttributedStringKey.font: UIFont(name: "Helvetica-Bold", size: 0.1)!, NSAttributedStringKey.foregroundColor: UIColor.clear]
BarButtonItemAppearance.setTitleTextAttributes(attributes, for: .normal)
BarButtonItemAppearance.setTitleTextAttributes(attributes, for: .highlighted)
Using Offset
UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffsetMake(-1000, 0), for:UIBarMetrics.default)
Work's like charm on Swift 3
self.navigationController?.navigationBar.topItem?.title = " "
I'm using this line of code in AppDelegate file into didFinishLaunchingWithOptions method to remove the backbutton title.
Swift 2.x
let barAppearace = UIBarButtonItem.appearance()
barAppearace.setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), forBarMetrics:UIBarMetrics.Default)
Swift 3.x
UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), for:UIBarMetrics.default)
Swift 4.x
UIBarButtonItem.appearance().setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.clear], for: .normal)
UIBarButtonItem.appearance().setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.clear], for: UIControlState.highlighted)
Just need go to your Parent ViewController from where your other ViewControllers are dependent.
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(true)
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)}
Just copy this code in didFinishLaunchingWithOptions launchOptions
Swift 5
UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffset(horizontal: -1000.0, vertical: 0.0), for: .default)
Swift 4
UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffsetMake(-1000.0, 0.0), for: .default)
it is simple. put a space in the title of the back button and ready.
Remember that it has to be in the previous view where you want to remove the text.
On iOS 14 is now present the backButtonDisplayMode property in UINavigationItem class. So, to remove back button title you can use
navigationItem.backButtonDisplayMode = .minimal
in the viewDidLoad func of the viewController where you want remove it.
To remove it in all navigationBar I used the swizzling technique
import UIKit
private let swizzling: (UIViewController.Type, Selector, Selector) -> Void = { forClass, originalSelector, swizzledSelector in
if let originalMethod = class_getInstanceMethod(forClass, originalSelector), let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
let didAddMethod = class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
}
extension UIViewController {
static func swizzle() {
let originalSelector1 = #selector(viewDidLoad)
let swizzledSelector1 = #selector(swizzled_viewDidLoad)
swizzling(UIViewController.self, originalSelector1, swizzledSelector1)
}
#objc open func swizzled_viewDidLoad() {
if let _ = navigationController {
if #available(iOS 14.0, *) {
navigationItem.backButtonDisplayMode = .minimal
} else {
// Fallback on earlier versions
navigationItem.backButtonTitle = ""
}
}
swizzled_viewDidLoad()
}
}
And in application(_:didFinishLaunchingWithOptions:) call
UIViewController.swizzle()
You could create a subclass for all UIViewControllers you want this behavior for, and in the subclass's viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.backBarButtonItem = UIBarButtonItem(
title: "", style: .plain, target: nil, action: nil)
}
This way, you can choose which controllers you want the behavior for, without duplicating code. I prefer my controllers to just say "Back", rather than the title of the previous controller, so I set that title here.
You can use xcode 8 and swift 3.0
self.navigationController?.navigationBar.backItem?.title = " "
let barAppearace = UIBarButtonItem.appearance()
barAppearace.setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), for:UIBarMetrics.default)
used this line of code in swift 3.0
Taking inspiration from rordulu's answer here, I ended up creating a custom UINavigationController and UINavigation bar which seems to handle all cases of this tricky problem.
1) Initialise new UINavigationController with your custom UINavigationBar:
class CustomNavigationController: UINavigationController {
convenience init() {
self.init(navigationBarClass: CustomNavigationBar.self, toolbarClass: nil)
}
}
2) Set the backItem.title property of the navigation bar to an empty string, every time the view lays itself out
class CustomNavigationBar: UINavigationBar {
override func layoutSubviews() {
backItem?.title = ""
super.layoutSubviews()
}
}
Now every time you use this navigation controller and bar combination, it will never have back button text! 🎉
Note: this should work fine if using storyboards also, just ensure to drop the custom navigation bar component into the view
Simple Solution :
While you are pushing 2nd controller from 1st controller, put self.navigationItem.title = "" in viewWillDisappear of 1st controller. It hides back button title from 2nd controller.
Above statment hides 1st controllers title, hence when we came back we want title for 1st controller again. For that we have add title for 1st controller in viewWillAppear method of 1st controller.
Refer following methods (of 1st controller)
override func viewWillDisappear(_ animated: Bool) {
self.navigationItem.title = ""
}
override func viewWillAppear(_ animated: Bool) {
self.navigationItem.title = "Title"
}
Works on Swift 5:
self.navigationItem.backBarButtonItem?.title = ""
Please note it will be effective for the next pushed view controller not the current one on the display, that's why it's very confusing!
Also, check the storyboard and select the navigation item of the previous view controller then type something in the Back Button (Inspector).
A method for iOS13.
let backButtonAppearance = UIBarButtonItemAppearance(style: .plain)
backButtonAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.clear]
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.backButtonAppearance = backButtonAppearance
UINavigationBar.appearance().standardAppearance = navigationBarAppearance
I usually add or change the back button in viewDidLoad of the UIViewController.
Something like that should work:
let leftButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "closeView:")
self.navigationItem.leftBarButtonItem = leftButton
Don't forget to change and implement the function that it's called to close the view.
Even easier, just change the title:
self.navigationItem.leftBarButtonItem.title = "Back"
if #available(iOS 14.0, *) {
navigationItem.backButtonDisplayMode = .minimal
} else {
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
Swift 3:
self.navigationItem.backBarButtonItem = UIBarButtonItem(title:"", style:.plain, target:nil, action:nil)
Adding a second answer here as my first only partially works. This method is less elegant in the fact that it requires calling a method in each view in the application, however it works without any side-effects.
So firstly, create a UIViewController extension class with a function to remove back button text and add a custom back button:
extension UIViewController {
func setBackButton() {
navigationController?.navigationBar.backIndicatorImage = R.image.backArrow()
navigationController?.navigationBar.backIndicatorTransitionMaskImage = R.image.backArrow()
navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil)
}
Secondly, we can simply call out to this function in the viewDidLoad of each view controller you need it in.
Swift 4.2
UIBarButtonItem.appearance().setTitleTextAttributes([.foregroundColor: UIColor.clear], for: .normal)
Updated Answer For Swift 4.2
Working with UIAppearance is a cleaner way of solving the problem but it would cause all the UIBarButtonItem to have a clear text. An improved version of the solution could be to check if the UIBarButtonItem is contained in a UINavigationBar.
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], for: .normal)
Just create extension of UIViewController with override function awakeFromNib() and make UIBarButtonItem with an empty title and give to navigation backBarButtonItem.
extension UIViewController {
open override func awakeFromNib() {
let backBarBtnItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationItem.backBarButtonItem = backBarBtnItem
}
}
if you want to remove back button title when you open next screen
do this inside the function initialising and pushing a new screen:
navigationItem.backButtonTitle = ""
Full usage:
let view = SomeView()
let controller = UIHostingController(rootView: view)
navigationItem.backButtonTitle = ""
navigationController?.pushViewController(controller, animated: true)
But to customise back buttons for all navigation bars in your app you need to do this:
func setupNavBarAppearance() {
let backButtonImage = Images.west.image.withAlignmentRectInsets(UIEdgeInsets(top: -5, left: -15, bottom: -5, right: -15))
let backButtonAppearance = UIBarButtonItemAppearance(style: .plain)
backButtonAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.clear]
let appearance = UINavigationBarAppearance()
appearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.white]
appearance.backButtonAppearance = backButtonAppearance
appearance.setBackIndicatorImage(backButtonImage, transitionMaskImage: backButtonImage)
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
UINavigationBar.appearance().tintColor = .white
UINavigationBar.appearance().isTranslucent = false
}
You can call it from your AppDelegate.swift
Swift 4.2 & 5
Instead of playing with the navigation bar tint color which will have side effects if you are using image picker anytime later in your code.
Use below code:
extension UIViewController {
open override func awakeFromNib() {
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
}
Call it from your first ViewController:
self.awakeFromNib()
Put the below code in any of the UIViewcontroller extension it will hide all the UIViewcontroller back text
open override func awakeFromNib() {
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
I have a simple solution for those, who don't want to use method swizzling or duplicating a similar code in different view controllers.
To remove back button title, create a UINavigationController subclass and override pushViewController(_, animated:) method:
final class CustomNavigationController: UINavigationController {
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
super.pushViewController(viewController, animated: animated)
let backBarButtonItem = UIBarButtonItem()
backBarButtonItem.title = nil
viewController.navigationItem.backBarButtonItem = backBarButtonItem
}
}
I don't know why but I found problem with hiding back button title in iPhone pluses but in device without plus shows correct with
leftBarButtonItem.title = ""
So I found simple way. It is set tint color to clear in NavigationBar of NavigationViewController in autolayout. It may be problem if you use icons or text tiles with tint. But in my case I don't use it as all.
Just use this:
func removeBackButton(vc:UIViewController) {
let button = UIButton.init(type: .custom)
button.setImage(UIImage.init(named:""), for: .normal)
let leftBarButton = UIBarButtonItem.init(customView: button)
vc.navigationItem.leftBarButtonItem = leftBarButton
}
So call this method in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
removeBackButton(vc:self)
}
You can add this extension to UIViewController
And then call this function in every viewDidLoad()
like : self.updateBackButton()
extension UIViewController {
func updateBackButton(){
if self.navigationController != nil {
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .done, target: self, action: nil)
}
}}
I would like to share a solution that works for me. Also, it can be adjusted base on your needs and requirements.
Note, in my case, I use a storyboard to specify CustomNavigationBar
Swift 4.2
class CustomNavigationBar: UINavigationBar {
override func awakeFromNib() {
super.awakeFromNib()
guard let topItem = topItem else { return }
removeBackButtonTitle(for: topItem)
}
override func pushItem(_ item: UINavigationItem, animated: Bool) {
removeBackButtonTitle(for: item)
super.pushItem(item, animated: animated)
}
func removeBackButtonTitle(for item: UINavigationItem) {
item.backBarButtonItem = UIBarButtonItem()
}
}
Works for Swift 4.2
Using the line of code in AppDelegate file into didFinishLaunchingWithOptions
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], for: .normal)
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], for: .highlighted)