I have a pretty standard iOS page layout like the following. There's a search bar and there's a back button on the navbar to go back to the parent VC.
I have code to set the tint color of the navbar into some custom color, like the following:
self.navigationController?.navigationBar.tintColor = COLOR
which works fine. However, if I click on the search bar (which brings it to the top and focuses on it) and then dismiss it, the back button becomes apple default blue. If I go to another app then come back, or use the interactive pop recognizer to go back to the parent VC but cancel it midway (so I return to this VC), the black color is restored. However, even if I call the above method in didDismissSearchController(_ searchController: UISearchController), I can't set the color to something other than blue.
I think it's likely an Apple bug. That said, I'd love to know if there may be a hack to make it work. Fwiw, when inspecting the view hierarchy, the navbar actually has the correct tint color, but the back button doesn't. Setting tint color for the back button however has no effect whatsoever.
I had faced the similar issue in iOS 13.1
and the way I fixed it was a bit odd.
I added custom back button on navigation bar.
Here is my code
You can create a new back button in UISearchBarDelegate function searchBarCancelButtonClicked(_ searchBar: UISearchBar)
func searchBarCancelButtonClicked(_ searchBar: UISearchBar)
{
//Hide Cancel
searchBar.setShowsCancelButton(false, animated: true)
searchBar.text = String()
searchBar.resignFirstResponder()
if #available(iOS 13.0, *) {
createBackButton()
}
}
func createBackButton() {
let image = UIImage.init(named: "BackIcon")
let customBackButton = UIBarButtonItem(image: image, style: .done, target: self, action: #selector(backButtonPressed))
navigationItem.leftBarButtonItem = customBackButton
navigationItem.leftBarButtonItem?.setBackButtonTitlePositionAdjustment(.zero, for: .default)
}
#objc func backButtonPressed() {
navigationController?.popViewController(animated: true)
}
Related
short: I don´t understand how to programmatically add the default back button.
long: I have been asked to write an iOS app, without any previous experience I decided
to follow the advices and code given by Nicola Zaghini.
In the code given along with the article, I really don´t understand
where does the back button come from.
The app has three screens
one to choose a city (folder WeatherLocation)
one that displays all
cities that have been already choosen (folder WeatherList)
one that
displays the weather for a city clicked in the list (folder
WeatherDetail)
There is + button for WeatherLocation:
This button is added in the code but I can not find where
and how is coded the back button in WeatherDetails (see above), and how is coded the action to do when one click the back button.
I search the web and found how to set a button in the NavigationBar:
let leftBarButtonItem: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem(title: "Left Item", style: .plain, target: self, action: nil)
barButtonItem.tintColor = UIColor.red
return barButtonItem
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.leftBarButtonItem = leftBarButtonItem
}
I also found that there is a backBarButtonItem
but I could not find how to properly use this property.
Moreover in the code of Nicola Zaghini there is nothing like backBarButtonItem to enable the
back button, neither in the xib and storyboard.
Can someone give me some hints about on how to set the back button ?
Thanks a lot!!
In the code of Nicola Zaghini the secret to have a default back button resides (for example) in the function navigateToWeatherDetail in the class WeatherListDefaultRouter where the new ViewController is pushed on a NavigationController:
func navigateToWeatherDetail(withLocation location: Location) {
if let weatherDetailVC = self.weatherDetailBuilder()?.buildWeatherDetailModule(withLocation: location) {
self.viewController?.navigationController?.pushViewController(weatherDetailVC, animated: true)
}
}
Overview:
I would like to set the accessibility focus to the navigation bar's title item.
By default the focus is set from top left, meaning the back button would be on focus.
I would like the title item to be in focus.
Attempts made so far:
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
navigationController?.navigationBar.items?.last)
Problem:
The above code makes no difference, the back button is still in focus.
Possible Cause:
Not able to get the item corresponding to the title to be able to set the focus.
Solution 1
I don't like it, but it was the minimum amount of hacking that does not rely on digging through hidden subviews (internal implementation of UINavigationBar view hierarchy).
First in viewWillAppear, I store a backup reference of the back button item,
and then remove the back button item (leftBarButtonItem):
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
backButtonBackup = self.navigationItem.leftBarButtonItem
self.navigationItem.leftBarButtonItem = nil
}
Then I restore the back item, but only after I dispatch the screen changed event in viewDidAppear() :
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.navigationItem.leftBarButtonItem = self?.backButtonBackup
}
}
Solution 2:
Disable all accessibility on the nav bar and view controller up until viewDidAppear() is finished:
self.navigationController.navigationBar.accessibilityElementsHidden = true
self.view.accessibilityElementsHidden = true
, and then in viewDidAppear manually dispatching the layout element accessibility focused event to the label subview of UINavigationBar:
UIAccessibilityPostNotification( UIAccessibilityLayoutChangedNotification, self.navigationController.navigationBar.subviews[2].subviews[1])
// The label buried inside the nav bar. Not tested on all iOS versions.
// Alternately you can go digging for the label by checking class types.
// Then use DispatchAsync, to re-enable accessibility on the view and nav bar again...
I'm not a fan of this method either.
DispatchAsync delay in viewDidAppear seems to be needed in any case - and I think both solutions are still horrible.
I invoked UIAccessibilityScreenChangedNotification on navigation title from viewDidLoad and it worked
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
self.navigationItem.title);
First, we'll need to create an useful extension:
extension UIViewController {
func setAccessibilityFocus(in view: UIView) {
UIAccessibility.post(notification: .screenChanged, argument: view)
}
}
Then, we'll be able to set our focus in the navigation bar title like this:
setAccessibilityFocus(in: self.navigationController!.navigationBar.subviews[2].subviews[1])
I have a UIBarButtonItem, "Done" - created in storyboard IB for ViewController A.
ViewController A is the root View Controller of the navigation stack.
If I push a View Controller B onto the navigation stack and then pop it again. the font weight of the Done button changes.
The font colour of the Done button is applied in A.viewWillAppear(..) and looks pretty much like
doneButton.tintColor = [CMPThemes navigationBarItemColour]; // it's a blue
I have stripped all appearance proxy code from the app (because there
are more than one style of navigation bars/buttons/titles appear in
the app) so I'm not looking for a fix that can only be done via the
appearance proxy...
I have checked in the debug view hierarchy that the Done button is
the same instance before and after the transitions
I have tried to re-apply the tint colour after the pop
I don't apply a font weight anywhere in the process
Also, to my eye, the font and font size seem to be unchanged during
the process.
In ViewController A
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
...
self.navigationController?.pushViewController(vcB, animated: true)
...
In ViewController B
viewDidLoad() {
...
let backButton = UIBarButtonItem(image: UIImage(named: "arrowLeft"), style: .plain, target: self, action: #selector(goBack))
backButton.tintColor = CMPThemes.popoverNavigationBarItemColour()
self.navigationItem.leftBarButtonItem = backButton
}
The storyboard looks like: (I've added the A and B to the image to maintain clarity).
If someone recognises the problem and can point me in the right direction for a fix that would be great!
I found the problem. The applied tint colour is not the issue. I think, Initially when it was loading the done button from the storyboard, the doneButton Style was equals .done and later when you are popping to ViewControllerA, somehow the style getting changed to .plan, so I think setting the style below the tint should fix the issue.
Try updating the viewWillAppear method in ViewControllerA with following code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
doneButton.tintColor = [CMPThemes navigationBarItemColour]; // it's a blue
doneButton.style = .done
}
Hope it helps!
I have faced same issue.
I have back button custom Image and when I push view controller and press back it will getting bold (Default image)
So what I did in storyboard is apply single BLANK space to Back Button
Step 1
--> Tap on Navigation item
Step 2
--> Put blank space on back Button
Now you can observe that
Navigation item has one blank item
Our app has an UINavigationBar with an image on it. When we segue (push) to another screen then click the back button the image on the Navigation Bar seems to animate from left to right as it reappears. This is a little distracting. How can you remove this back button animation?
We tried changing the segue Animates setting but this changes both the push animation and not the back animation.
Our Nav Bar code:
let logoImage:UIImage = UIImage(named: "ABC")!
viewController.navigationItem.titleView = UIImageView(image: logoImage)
Figured this out in large part due to this answer https://stackoverflow.com/a/8602982/47281
Create a custom Nav Bar and override popItem:
class MyNavigationBar: UINavigationBar {
override func popItem(animated: Bool) -> UINavigationItem? {
return super.popItem(animated: false)
}
}
Entered MyNavigationBar as the Navigation Bar class for our Navigation Controller via the Storyboard:
Note I did not override NavigationController popViewControllerAnimated as in the linked answer.
You can do this:
override func viewDidLoad() {
super.viewDidLoad()
let logoImage: UIImage = UIImage(named: "ABC")!
self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: logoImage, style: .plain, target: self, action: #selector(backBtnPressed))
}
And then create a method to handle the tap on the button
func backBtnPressed(){
_ = self.navigationController?.popViewController(animated: false)
}
I have developed an app that makes use of the iOS8 feature to show or hide the navigation bar on a tap of the view.
However, the main view contains a UIButton which also act upon taps. The problem is that both 'objects' are receiving the tap and if I tap the button, the navigation bar toggles its visibility.
I can get to the barHideOnTapGestureRecognizer via the navigation controller but not really sure what can be done with it to stop it responding if a button is tapped.
Is there a way (apart from switching off or changing to 'Swipe to Hide') to subdue the navigation bar's appearance/disappearance when a button is pressed?
Don't use the standard barHideOnTapGestureRecognizer. Fortunately, it's not hard to roll your own:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let gestureRecognizer = UITapGestureRecognizer(target: self, action: "toggleBarsOnTap:")
self.view.addGestureRecognizer(gestureRecognizer)
}
func toggleBarsOnTap(sender: AnyObject?) {
let hidden = !self.navigationBarHidden
self.setNavigationBarHidden(hidden, animated: true)
self.setToolbarHidden(hidden, animated: true)
}
Taps on the view will show/hide the bars, and taps on controls (subviews of the view) will not.
[self.navigationController setNavigationBarHidden:YES];