Customizing UINavigationBar not working because of weird UINavigationBar view hierarchy - ios

My app has many ViewControllers that are pushed on navigation stack.
I have configured UINavigationBar appearance globally in AppDelegate as below.
let appearance = UINavigationBar.appearance()
appearance.barTintColor = myColor
appearance.tintColor = .white
appearance.isTranslucent = false
let textAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white,
NSAttributedString.Key.font: myFont, size: mySize)]
appearance.titleTextAttributes = textAttributes as [NSAttributedString.Key : Any]
All ViewControllers work as expected except only one ViewController.
Below is one of the VCs that works as expected. It shows color and font that I want.
And below is the ViewController that shows different look unlike others.
I can't understand the reason why only one navigationBar on this VC shows different appearance.
So I've done debugging view hierarchy.
Below is the view hierarchy of VCs that works as expected.
And below is the view hierarchy of VC that shows weird look.
As seen on the picture, the problematic NavigationBar has two more layers, UIVisualEffectView and UIVisualEffectBackdropView.
I am an experienced iOS developer and have no idea why this happens.
I carefully checked all the setting related to NavigationBar on the IB but found no difference from others.
I even removed the ViewController, embedding NavigationController completely and rebuilt them from scratch without luck.
Please somebody explain me why only this NavigationBar has different structure.
I'm working on iOS 13.3 & Xcode 11.3.1

Working here on iOS15, I tried a lot of things to customize the nav bar appearance to achieve a simple opaque color, nothing worked except this:
In viewDidLoad
if (#available(iOS 13.0, *)) {
UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
appearance.backgroundColor = [UIColor orangeColor];
self.navigationController.navigationBar.standardAppearance = appearance;
self.navigationController.navigationBar.scrollEdgeAppearance = appearance;
}
Swift version:
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = .orange
navigationBar.standardAppearance = appearance;
navigationBar.scrollEdgeAppearance = navigationBar.standardAppearance
Finally if you only want to apply this on the current controller, and restore the default nav bar just do the same but with a fresh UINavigationBarAppearance() on viewWillDisappear.

Related

Why the view.safeAreaInset is same for Opaque and Transparent UINavigationBarAppearance?

When we use the old way of making the navigation bar translucent or opaque using isTranslucent property on the navigation bar, the view's safeAreaInset returns some value other than 0 for the translucent navigation bar and viceVersa.
But this behaviour is not seen when using the below code as pe new iOS 13 SDK
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor.systemRed
appearance.titleTextAttributes = [.foregroundColor: UIColor.lightText] // With a red background, make the title more readable.
navigationItem.standardAppearance = appearance
navigationItem.scrollEdgeAppearance = appearance
navigationItem.compactAppearance = appearanc
For this code i am still seeing safeAreaInset.top > 0.
Please help me to understand this behaviour. And how to get inset.top as 0 with new UINavigationBarAppearance api.
Answering my own question:
Using isTranslusent with Appearance API helped me to achieve the old behavior.
As appearance.configureWithOpaqueBackground() only make the bar appearance opaque.

How I can remove navigation bar background with appearance in the iOS 13?

I am tried to remove the navigation bar background in iOS 13 in the if's statements with #available. I know the original code to remove the navigation bar background for iOS 12 and older iOS in the else's statements. However, Apple did announce a new system called Appearance in anywhere to support that new iOS 13 system.
let app = UINavigationBarAppearance()
let navigationBar = self.navigationController?.navigationBar
app.configureWithOpaqueBackground()
app.shadowImage = UIImage()
self.navigationController?.navigationBar.scrollEdgeAppearance = app
navigationBar!.standardAppearance = app
navigationBar!.scrollEdgeAppearance = app
I believe this configureWithOpaqueBackground() allows us to remove the navigation bar background, But I test on iOS 13.1 simulator appear black navigation bar background. I know what caused it.
app.configureWithOpaqueBackground()
app.titleTextAttributes = [.foregroundColor: UIColor.white]
app.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
app.backgroundColor = #colorLiteral(red: 0.1603881121, green: 0.1677560508, blue: 0.2133775949, alpha: 1)
That code allows us to put the custom color on the black background. I ready to add that code in the viewWillDisappear's statements to restore the navigation bar background to normal color background before self.navigationController?.navigationBar.scrollEdgeAppearance = app with remove app.configureWithOpaqueBackground() and app.shadowImage = UIImage(). Now, I need to create the translucent navigation bar background in the viewWillAppear's statements, but it can't see any translucent background due to the black background still display.
I really appreciate your help in resolving the problem! :)
If you want the navigation bar to become completely transparent:
let app = UINavigationBarAppearance()
app.configureWithTransparentBackground()
self.navigationController?.navigationBar.standardAppearance = app
self.navigationController?.navigationBar.scrollEdgeAppearance = app
self.navigationController?.navigationBar.compactAppearance = app
Do not mess with the isTranslucent of the navigation bar.

Visible UISearchBar changes UINavigationBar background color

A tableview controller is embedded into a navigation controller.
I programmatically added a search bar to the tableview controller's navigation bar. I only changed the navigation bar Background color into something different than Default (purple) - all the rest I left default.
class TableViewController: UITableViewController {
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.hidesSearchBarWhenScrolling = true
navigationItem.searchController = searchController
}
}
Code above is reduced to bare minimum for demonstration purpose.
All done with Xcode 11 (11A420a).
I ran the project in iOS 12.0 and 13.0 simulators and devices.
iOS 13.0
The search bar shows upon start.
Navigation bar background color is correctly presented.
While scrolling, navigation bar background color remains correct.
With iOS 13.0, all works as expected!
iOS 12.0
The search bar doesn't show upon start.
Navigation bar background color is correctly presented.
While scrolling, navigation bar background color goes white as soon search bar is visible.
I tried to change all kind of color setting in storyboard as well as properties programmatically. I didn't succeed in changing the navigation bar background color when search bar is visible.
It seems (?!) that the navigation bar foreground looses transparency when search bar becomes visible.
If I use a Bar Tint color of the navigation bar (!= Default), all works as expected (and as with iOS 13.0), but I loose the gradient effect, which I would like to keep.
What did I miss?
How can I avoid this?
Bug?
I had some luck with the navigationItem.scrollEdgeAppearance property when facing a similar problem. For example:
vc.navigationItem.scrollEdgeAppearance?.backgroundColor = .red
This is only available on iOS 13 though.
Here's what I ended up doing to get correct colors in the navigation bar while allowing the search controller's scroll bar to hide during scroll:
if #available(iOS 13.0, *) {
let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial)
navbar.standardAppearance.backgroundEffect = blurEffect
navbar.standardAppearance.backgroundColor = appMainColor.withAlphaComponent(0.75)
navbar.standardAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.white]
navbar.compactAppearance = nil
navbar.scrollEdgeAppearance = navbar.standardAppearance.copy()
navitem.standardAppearance = nil
navitem.compactAppearance = nil
navitem.scrollEdgeAppearance = nil
}
I don't know if it is the look you're going for but I found if you disable hideSearchBarWhenScrolling the background stops changing color. However, the search bar is always there.
Add this to viewDidLoad():
navigationItem.hidesSearchBarWhenScrolling = false

How to use UINavigationBarAppearance in iOS 13

How do you use this new object to customize the navigation bar in iOS 13? I tried the following code in Objective-C but it's not working correctly. It only shows my title text attributes while a view controller is being pushed or popped on to the navigation stack.
UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
appearance.titleTextAttributes = #{NSFontAttributeName: font};
Here is the documentation for this object.
https://developer.apple.com/documentation/uikit/uinavigationbarappearance?language=objc
It isn't enough to just create an instance of UINavigationBarAppearance. You have to actually set it on a UINavigationBar instance (or its appearance proxy).
// Setup the nav bar appearance
UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
appearance.titleTextAttributes = #{NSFontAttributeName: font};
// Apply it to a specific nav bar
someNavBarInstance.standardAppearance = appearance;
// There are also the compactAppearance and scrollEdgeAppearance properties that can be set as needed.
If you want this same customization on all nav bars in the app, apply it to the UINavigationBar.appearance proxy.
UINavigationBar.appearance.standardAppearance = appearance;
For me, even though I correctly set the appearance to the current UINavigationBar, it was still not working.
I found out that you need to call navigationBar?.setNeedsLayout(), if you set an updated appearance on a UINavigationBar that is already being displayed.
in iOS13 you need to set the title color on the UINavigationBarAppearance object like here (Objective C version):
UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init];
appearance.titleTextAttributes = #{NSForegroundColorAttributeName: [UIColor whiteColor]};
self.navigationController.navigationBar.standardAppearance = appearance;
self.navigationController.navigationBar.scrollEdgeAppearance = appearance;

UISearchController doesn't work properly with a non-translucent UINavigationBar

Currently I am trying to embed a UISearchController into my application. But the UISearchBar, which is a property of the UISearchController, doesn't get displayed properly, if the UINavigationBar is non-translucent. Usually after tapping the UISearchBar property, the UINavigationBar moves up to make room for the UISearchBar. You can see the result on the following screenshot:
https://www.dropbox.com/s/172k63zr2bhj84t/Normal_behaviour.png?dl=0
But if the "translucent" property of the UINavigationBar is set to "NO", the UISearchBar doesn't get displayed properly, because the background of the status bar remains transparent, as you can see on the following screenshot:
https://www.dropbox.com/s/v5cnxoj9ms6976r/Wrong_behaviour.png?dl=0
To demonstrate this weird behaviour, I have modified the sample project provided by Apple:
https://developer.apple.com/library/ios/samplecode/TableSearch_UISearchController/Introduction/Intro.html
Here you can download the modified version:
https://www.dropbox.com/s/7icfe6kap98g1e8/TableSearchwithUISearchControllerObj-CandSwift_MODIFIED.zip?dl=0
The modification is in file "APLMainTableViewController.m" line 33.
It's clearly a bug (rdar://20942583).
My workaround is to set
self.edgesForExtendedLayout = UIRectEdgeAll;
self.extendedLayoutIncludesOpaqueBars = YES;
This allows you to keep the navigation bar opaque. The downside is that the content flows below the bar even if it can't be seen, creating some overhead.
All I needed was:
func viewDidLoad() {
extendedLayoutIncludesOpaqueBars = true
}
One workaround for this is to make the status bar translucent just before the search is going to become active, and remove the translucency when the search is about become inactive.
You can do this by registering your view controller as a delegate of UISearchController, and implementing the willPresentSearchController and willDismissSearchController methods. For example (in Swift):
Declare your view controller as a delegate of UISearchController:
class MyViewController: UITableViewController, UISearchControllerDelegate
Don't forget to actually set it as the delegate, for instance in viewDidLoad add:
searchController.delegate = self
And finally:
func willPresentSearchController(searchController: UISearchController) {
navigationController?.navigationBar.translucent = true
}
func willDismissSearchController(searchController: UISearchController) {
navigationController?.navigationBar.translucent = false
}
Ok, this one is a SUPER pain to debug but not that bad to fix. It's all down to the way Apple changed the appearance of navigation bars. It can be fixed by creating a UINavigationBarAppearance object, configuring it with the visual properties you want (i.e. background colour etc) and then assigning it to standardAppearance and scrollEdgeAppearance on UINavigationBar.appearance() - you can have two different instances with different settings if you want.
A simple implementation might look like this:
let appearance = UINavigationBarAppearance()
appearance.configureWithDefaultBackground()
appearance.backgroundColor = barColor
appearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
(Naturally replace barColor and textColor with the colours of your choice!)
if someone have a problem like non-translucent hidden the search bar u can just had this :
self.definesPresentationContext = true
Regards

Resources