Use `navigationItem.backButtonDisplayMode = .minimal` with a custom back button image - ios

For an iOS 14+ app I'd like to use navigationItem.backButtonDisplayMode = .minimal to hide the back button title, while still having the title available in the back button's long-press menu. Which works.. however I also want to change the back button image, to replace the default chevron.
But no matter what I try, I can't seem to find a solution that shows a custom back button image without a title, while also not showing a blank space in the back button's long-press menu, and not breaking the slide-to-go-back-gesture.
Anyone tried something similar, and succeeded?
So in the first view controller I show a title:
And then in the pushed view controller I want to show a custom back button image WITHOUT the "one" title (as seen below), and still have the long-press menu say "one" instead of a blank space.
This mostly gets me there actually, except that it breaks the gesture to slide to go back:
override func viewDidLoad() {
super.viewDidLoad()
let backImage = UIImage(named: "backImage")?.withRenderingMode(.alwaysOriginal)
navigationController?.navigationBar.backIndicatorImage = backImage
navigationController?.navigationBar.backIndicatorTransitionMaskImage = backImage
navigationItem.backButtonDisplayMode = .minimal
}
Update: actually it only seems to break on the simulator, it's all fine on an actual device. I now have a minimal project setup where it all works, now to find out why it doesn't work in my actual big project!

Okay, I finally figured out all the problems I was having.
Basically, this code works just fine:
override func viewDidLoad() {
super.viewDidLoad()
let backImage = UIImage(named: "backImage")?.withRenderingMode(.alwaysOriginal)
navigationController?.navigationBar.backIndicatorImage = backImage
navigationController?.navigationBar.backIndicatorTransitionMaskImage = backImage
navigationItem.backButtonDisplayMode = .minimal
}
But I was having problems with the swipe back gesture not working anymore. Turns out, that's a simulator bug, works fine on device. Then there was the problems that the custom back button image didn't actually show up in my view, because of this:
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = .pageBackground
appearance.titleTextAttributes = [.foregroundColor: UIColor.abbey]
appearance.shadowColor = .clear
navigationBar.scrollEdgeAppearance = appearance
navigationBar.standardAppearance = appearance
navigationBar.compactAppearance = appearance
As soon as you set a custom appearance, that completely wipes away the custom back button image. Simple fix, just set these things directly on the navigationBar without involving the appearance.
And now it all works!

To set the image, you can use:
navigationController?.navigationBar.backIndicatorImage = yourBackImage
navigationController?.navigationBar.backIndicatorTransitionMaskImage = yourBackImage
Let's say you have navigation from AVC to BVC.
If you want to disable the long press menu, you can follow this answer.
If you want the long press menu to work with the correct titles, you need to set navigationItem.title for your AVC to the right value, and navigationItem.backBarButtonItem should be nil(it's the default value) for BVC.
If you don't want to display the title in your AVC, you can hide it with titleView:
navigationItem.title = "title for long press navigation menu"
navigationItem.titleView = UIView()

Related

RightBarButton doesn't appear but it appears when the ViewController disappear for a second

In my app, the first ViewController appears with right navigation bar items. I want to show different bar items on right side in child VC which is appeared by pushing. The items in the first VC shows fine, but the second doesn't. The bar button shows for a second when the VC disappeared.
// The things the first VC did
navigationItem.setHidesBackButton(true, animated: true)
navigationController?.navigationBar.tintColor = .gray600
let stackView = UIStackView()
stackView.addArrangedSubviews(views: [registerButton, notificationButton])
stackView.spacing = 16
let rightBarButton = UIBarButtonItem(customView: stackView)
navigationController?.navigationBar.topItem?.rightBarButtonItem = rightBarButton
// The things the second did
navigationController?.navigationBar.tintColor = .gray600
navigationController?.navigationBar.topItem?.backButtonTitle = ""
navigationController?.navigationBar.backIndicatorImage = .back
navigationController?.navigationBar.backIndicatorTransitionMaskImage = .back
let rightBarButton = UIBarButtonItem(customView: editButton)
navigationController?.navigationBar.topItem?.rightBarButtonItem = rightBarButton
They did almost same things but the second doesn't work.
Here is the gif file i recorded. You can see Edit button for a second when the second VC disappeared. I tried to find the clues but i couldn't. Please check it and give me any comments. Thank you.
Delete the phrase navigationController?.navigationBar.topItem everywhere it appears, and never use it again, ever. It will totally break the navigation controller — as you yourself have demonstrated.
The only navigation item a view controller can talk to is its own navigationItem property. Thus the first vc is much more correct than the second vc, and so it works much better than the second vc.
you should not set a button and its title by using "topItem", instead you should set them by using navigationItem's rightBarButtonItem.
let rightBarButton = UIBarButtonItem(customView: editButton)
navigationItem.rightBarButtonItem = rightBarButton
let backButton = UIBarButtonItem(...)
navigationItem.leftBarButtonItem = backButton

Xcode 11.4 Navigation Bar Button Issues

Since updating to Xcode 11.4 I'm running into the issue that I can't figure out how to change the color of the back arrow in the navigation bar.
Before I was able to simply use:
navigationBar.barTintColor = DisplayUtils.sweetGreenColor()
However, 11.4 has forced me to use the standardAppearance functionality to set the color of my navbar and everything. Which is no big deal, this works for setting the barTint and title colors:
navigationBar.standardAppearance.backgroundColor = DisplayUtils.sweetGreenColor()
let buttonAppearance = UIBarButtonItemAppearance()
buttonAppearance.normal.titleTextAttributes = [.foregroundColor : DisplayUtils.whiteColor()]
navigationBar.standardAppearance.buttonAppearance = buttonAppearance
navigationBar.standardAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: DisplayUtils.whiteColor(), NSAttributedString.Key.font: UIFont(name: "Roboto-Bold", size: 18) as Any]
I just don't see a property that replaced barTintColor on the UIBarButtonItemAppearance object. It lets you change the color of the text, change the image itself, but no color property.
I found a solution that works in my instance. It works because I'm creating my viewControllers in code such as:
if let stbrd = self.storyboard {
if let vc: ViewController = stbrd.instantiateViewController(withIdentifier: "VC") as? ViewController {
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil) // remove back button text
self.navigationItem.backBarButtonItem?.tintColor = DisplayUtils.sweetGreenColor()
self.navigationController?.pushViewController(vc, animated: true)
}
}
It's the line:
self.navigationItem.backBarButtonItem?.tintColor = DisplayUtils.sweetGreenColor()
That properly sets the color of the back arrow on the newly presented view controller.
I still don't see how to simply set that color on the view controller itself like before this change. So, if anyone can offer up that knowledge it would be great.
On the storyboard, for your Navigation Controller change the "Bar Tint" to its "Default" value, then on your code you can change it as you normally would.
By programmatically simply first change navigation bar color to default then you can update what you are already doing.
if #available(iOS 13.0, *) {
self.navigationController?.navigationBar.standardAppearance.configureWithDefaultBackground()
}

Top layout guide moves underneath navigation bar after pushing search results

This one has kept be up a few days. I think the easiest way to illustrate my issue is with this 10 second gif.
Description
As you can see, when you go back after pushing to a search result, the segmented top bar disappears when you go back, but when I switch to the tab with "Tise" in the header and back, it resets and works properly.
As seen in the below screenshot, my issue is that the top layout guide gets offset underneath the navigation bar, which makes the segmented bar hide underneath it.
Code
My search controller is an extremely normal implementation.
/* Search controller */
let exploreSearchController = StandaloneExploreSearchResultController.initFromStoryboard(type: .top)
searchController = UISearchController(searchResultsController: exploreSearchController)
searchController?.searchResultsUpdater = exploreSearchController
searchController?.delegate = self
searchController?.searchBar.searchBarStyle = .minimal
searchController?.searchBar.isTranslucent = false
searchController?.searchBar.backgroundColor = .clear
searchController?.hidesNavigationBarDuringPresentation = false
searchController?.dimsBackgroundDuringPresentation = false
searchController?.searchBar.tintColor = #colorLiteral()
navigationItem.titleView = searchController?.searchBar
definesPresentationContext = true
and I push the result controllers in StandaloneExploreSearchResults with
presentingViewController?.navigationController?.pushViewController(viewController, animated: true)
For every new view controller, I update the navigation bar style, which I suspect might be triggering it. If I disable this function, I get different offset bugs, as seen in the following gif.
func updateNavigationBarStyle(viewController: UIViewController) {
let style = (viewController as? NavigationControllerStyling)?.preferredStyle() ?? .default
print(#file.components(separatedBy: "/").last!,":",#line,"-",#function, viewController.classForCoder)
//Showhide
UIApplication.shared.setStatusBar(hidden: style.statusBar.hidden, style: style.statusBar.style)
setNavigationBarHidden(style.hidden.hidden, animated: style.hidden.animated)
navigationBar.setCustomShadow(hidden: style.shadowHidden)
//Colors
navigationBar.tintColor = style.tintColor
navigationBar.barTintColor = style.barTintColor
navigationBar.backgroundColor = style.backgroundColor
navigationBar.setBackgroundImage(style.backgroundImage, for: .default)
navigationBar.titleTextAttributes = style.titleTextAttributes
navigationBar.isTranslucent = style.translucent
navigationBar.hairlineImageView?.isHidden = true //Always hide the native shadow
}
Question
Is there a way to either force the auto layout reload that switching tabs triggers (I've tried all the methods I could find with no success). or fix the bug somehow?
Well, that solved it. I guess setting the colors and/or images somehow fiddled with the opaque property.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
extendedLayoutIncludesOpaqueBars = true
}
Try this code in viewDidLoad -
self.edgesForExtendedLayout = []

hide and show a ui nav bar button

I am having a situation in which I have to hide the button on the right side of nav bar. Button name is btnRefresh, and I can hide it successfully by this way
self.nvbar.topItem?.rightBarButtonItem = nil
but when I use this for showing it again, it didn't get displayed again
self.nvbar.topItem?.rightBarButtonItem = btnRefresh
Any help???
You need to change the tint color to clear and disable the button as the following :
let barButtonItem = UIBarButtonItem()
barButtonItem.tintColor = .clear
barButtonItem.isEnabled = false
And to display it again change the color and enable it again :
barButtonItem.tintColor = .black
barButtonItem.isEnabled = true

NavigationBar back button change text after popViewController

I have a UINavigationController connected to a UITabBarCotnroller.
When i push a ViewController and in that class i write this code in their viewWillAppear method
self.navigationController?.navigationBarHidden = false
let yourBackImage = UIImage(named: "Back.png")
self.navigationController?.navigationBar.backIndicatorImage = yourBackImage
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = yourBackImage
self.navigationController!.navigationBar.backItem?.title = "";
By this code i am just setting a back button image with empty title.
But when i push another screen over the current pushed screen and then tap back it again shows the title with text "Back"
I am writing the above code in every view controller's viewWillAppear method which will be push.
While I believe setting the backIndicatorImage and backIndicatorTransitionMaskImage will work in viewDidAppear(), I found issues with setting the back text itself. I always had to set the text with a new button, and even then, it worked better for me to do it from the transitioning controller (as that's the view they would go back to, so I didn't have to care where the user was transitioning from).
You can try using this in your viewWillAppear(), but I'm using this in my prepare(for:, sender:) function.
let backItem = UIBarButtonItem()
backItem.title = "" // In my case, I was setting it here; you would blank it out
self.navigationItem.backBarButtonItem = backItem

Resources