Creating white-translucent UINavigationBar - ios

Typically, translucent UINavigationBars have a light gray color above a white background.
However, many navigation bars throughout iOS 11 have a white color.
For example, navigation bars in the Files app are white AND translucent which are noticeably different from setting barTintColor to white.
How do I achieve this kind of effect on a UINavigationBar?

Set the barTintColor of the navigation bar to white.
After that, subclass UINavigationBar and set the shadow image to an empty UIImage.
class CustomNavBar: UINavigationBar {
override func awakeFromNib() {
super.awakeFromNib()
shadowImage = UIImage()
}
}
Finally, set the class of the navigation bar to the custom navigation bar class you just created.
Result

I was trying to build an app with the same style as the Files app, but ended up with the same problem.
It seems that when barTintColor is non-nil, UINavigationBar will disable the translucency effect, and there doesn't seem to be a public way to force it back on.
I think there is a reasonable way to get this effect though: you could replace the background view of the navigation bar with your own UIVisualEffectView.
It's possible to suppress both the background view, and the separator view of a navigation bar by providing them with dummy UIImage objects.
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.shadowImage = UIImage()
You would need to create your own UINavigationBar subclass, suppress these two views, and then add your own UIVisualEffectView subview and position it to always sit at the bottom of the view stack.
It's not an amazing solution, but it should hopefully create the desired effect with minimal internal hacking of the navigation bar class.

So I've spent a huge amount of time researching this, and I've found the answer, but beware: it's very, very hacky.
So, the view that's responsible for creating this gray glow on the navigation bar is something called _UIVisualEffectBackdropView. More specifically, this view's layer has four filters attached (all of them are private CAFilters), and first of them, named luminanceCurveMap, creates this gray color for the navigation bar. So my solution is to
Find _UIVisualEffectBackdropView in the view hierarchy of UINavigationBar
Remove luminanceCurveMap filter of of its layer.
Here's the function I created to find _UIVisualEffectBackdropView in the hierarchy:
extension UIView {
fileprivate static func findRecursively(typeName: String, in view: UIView) -> UIView? {
let typeOf = type(of: view)
if String(describing: typeOf) == typeName {
return view
} else {
for subview in view.subviews {
if let found = UIView.findRecursively(typeName: typeName, in: subview) {
return found
}
}
return nil
}
}
}
And then override viewDidAppear in your custom UINavigationController subclass (only viewDidAppear, viewWillAppear for example didn't work):
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let backdrop = UIView.findRecursively(typeName: "_UIVisualEffectBackdropView", in: navigationBar) {
let luminanceCurveMapIndex = backdrop.layer.filters?.firstIndex { filter in
if let caFilter = filter as? NSObject, let name = caFilter.value(forKey: "name") as? String {
return name == "luminanceCurveMap"
} else {
return false
}
}
if let index = luminanceCurveMapIndex {
backdrop.layer.filters?.remove(at: index)
}
}
}
I know it's a lot, but that's the I came out with. It preserves all native translucency behavior while giving me the look I needed.

Related

iOS Swift TabBar status bar text color glitch half white half black

I'm trying to change the status bar text to white on some of the view controllers, but on a few others it still needs to be black. (so cannot change it globally for the whole app).
Everything works fine, except the first few seconds, when clicking on different tab bars, when there is a glitch, and half of the status bar text becomes black, instead of white. Then everything updates perfectly.
The structure is like this:
A tab bar controller with a few tabs, each tab containing a navigation controller, containing a view controller.
Having navigation controllers made it necessary to change the navigationController?.navigationBar.barStyle = .black to make it work.
After pressing a few times on tabs, it works fine.
First thing I did of course was to set to YES the View controller-based status bar appearance from the .plist
I also have override the preferredStatusBarStyle, using a boolean to set which view controllers should have the white or black status bar text:
override var preferredStatusBarStyle: UIStatusBarStyle {
return coloredNavigationBar ? .lightContent : .default
}
var coloredNavigationBar: Bool = true
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if coloredNavigationBar {
navigationController?.navigationBar.barStyle = .black
} else {
navigationController?.navigationBar.barStyle = .default
}
setNeedsStatusBarAppearanceUpdate()
}
The code pasted is from the base view controller used everywhere.
Yes! #matt was right, thank you. Indeed iOS gets confused when using a tabbar.
The solution was to add this to the TabBarController:
override public var childForStatusBarStyle: UIViewController? {
if let controller = selectedViewController as? UINavigationController {
return controller.visibleViewController
}
return selectedViewController
}

How to add view on top of navigation bar

How to add view above navigation bar?
I have a custom navigation controller and I want to present a view above nav bar (like on the screen), so it should be visible on other ViewControllers
Would be great if the solution will be on storyboard.
Tried to add on UIWindow did't help.
Swift 4.2, Xcode 10+
Okay, from what I can tell (via your comment reply, though it still isn't 100% clear), the best solution to your question would be to make the navigation bar transparent, such that you can see any navigationController-presented view controllers underneath it. For this, I'd suggest the following extension to UIViewController:
extension UIViewController {
func setupTransparentNavigationBarWithBlackText() {
setupTransparentNavigationBar()
//Status bar text and back(item) tint to black
self.navigationController?.navigationBar.barStyle = .default
self.navigationController?.navigationBar.tintColor = .black
}
func setupTransparentNavigationBarWithWhiteText() {
setupTransparentNavigationBar()
//Status bar text and back(item) tint to white
self.navigationController?.navigationBar.barStyle = .blackTranslucent
self.navigationController?.navigationBar.tintColor = .white
}
func setupTransparentNavigationBar() {
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.backgroundColor = .clear
self.navigationController?.navigationBar.isTranslucent = true
}
}
Using either of the first two methods in viewWillAppear of your UIViewController subclasses will let you make the navigation bar completely transparent with the statusBar text + wifi/battery indicators black or white as desired. From this, you can then display anything under the navigation bar by pinning your constraints to view.bounds.topAnchor. E.g. for a transparent navigation controller with white statusBar text:
class YourViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
setupTransparentNavigationBarWithWhiteText()
}
}

Navigation Bar Overlaps Info

I am having a problem with my navigation Bars overlapping the Information on the top of the iPhone. I originally had the navigation bar as White/ Translucent and it didn't cause a problem. I feel that making it black would be better except for now it overlaps the Time, Battery and Data from the iPhone. How would I move the navigation bar down?
You need to create a custom navigationBar and then add the constraints that you want, but this is not something that should interrupt with the status bar usually. You could try to change the font size of your navigationBar text:
self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedStringKey.font: UIFont.systemFont(ofSize: 10)]
Update:
After some clarification, you donĀ“t want to move done the navigationBar, you just want to change the color of the statusBar (which contains all system icons on the top). Add these two rows in your viewDidLoad function to change the background color of you:
if let statusBar: UIView = UIApplication.shared.value(forKey: "statusBar") as? UIView {
statusBar.backgroundColor = .white
}
Just make this class parent class of your every viewController
For example you have a SignUpViewController class and currently your this view controller will be like this.
class SignUpViewController: UIViewController {
//Your all methods and properties are implemented here.
}
So just change the parent class of above class from UIViewController to below written class LargerNavigationBaseVC like this.
class SignUpViewController: LargerNavigationBaseVC {
//Your all methods and properties are implemented here.
}
class LargerNavigationBaseVC: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.prompt = " "
}
}

How do I create a custom navigationController with image and name?

My goal is to create a UI that looks like this
The only way that I could think of is to embed a navigationController on the UIViewController then add UIView
Storyboard
Code - ProfileViewController
class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationBarColor()
}
func navigationBarColor() {
navigationController?.navigationBar.backgroundColor = UIColor(red:0.91, green:0.04, blue:0.51, alpha:1.0)
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
}
}
Result
Is my approach correct? or do I need to use a different method to achieve this result?
The best way to do this is to make the navigationBar invisible basically. Here is what I did to achieve the look you want:
Step 1:
Add the navigation controller and a view controller and hold control and drag from the navigation controller to the view controller and select root view controller(which i am pretty sure you have already done). Also, drag and drop a UIBarButtonItem onto the navigationBar in the view controller, not the navigationBar in the navigation controller.
Step 2:
Subclass the navigation controller and the view controller, in my example, mine are called CustomNavController.swift and MainVC.swift. In the storyboard, set them as the class of the controllers.
Step 3:
In the class you made for the view controller, set the code in the viewDidLoad to change the background color.
import Foundation
import UIKit
class MainVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor(red: 231/255, green: 11/255, blue: 129/255, alpha: 1)
}
}
And in the navigation controller class, add this code.
import UIKit
class CustomNavController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationBar.shadowImage = UIImage()
self.navigationBar.isTranslucent = true
self.navigationBar.tintColor = .white
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
The Result:
Since you are using a navigation controller, you have to set the status bar style to lightContent in the navigation controller subclass like shown above. If you have other view controller that you want the status bar to be black in, you will have to implement the method preferredStatusBarStyle in the class and return .default unless, if the view controller has a navigation controller associated with it, the you would have to implement preferredStatusBarStyle in the navigation controller subclass like I showed in my example. If you have any questions feel free to leave a comment and I will answer when I can.
You can subclass navigation bar, but with that amount of size, i think it's not a good approach. What you are doing seems ok. It's a solution.
Your approach will work. To match your Sketch mockup as closely as possible, here are a few tips:
UINavigationBar's background color should be set using the barTintColor property. This will give the status bar the same background color.
Set the UINavigationBar's barStyle property to black. This will set the status bar's content color to white.
You might notice how the navigation bar's color is slightly different from the view you created beneath it. This is because UINavigationBar is translucent by default, so the background color has a reduced alpha value. You can set the UINavigationBar property isTranslucent to false to fix this.
An alternative approach (without UINavigationController) would involve creating a simple UIView to use as the status bar background, as shown here: https://stackoverflow.com/a/43264566/5223633.
First step, add this 3 lines of codes in your viewDidLoad methods of your controller class:
navigationController?.navigationBar.barTintColor = UIColor(red:0.91, green:0.04, blue:0.51, alpha:1.0)
navigationController?.navigationBar.tintColor = UIColor.white
UIApplication.shared.statusBarStyle = .lightContent
And then, select your info.plist file. When it's opened, add a row by right clicking on it. Give the key name:
View controller-based status bar appearance
and set it's value into:
NO
as boolean value.
see this info.plist screenshot example
Then run it.
You can create custom viewcontroller with Xib file after that load and add its view inside your navigation bar. Hide back button if required and update navigationBar height
accordingly
navigationItem.hidesBackButton = true
navigationController?.navigationBar.frame.size.height = 100
guard let yourCustomView = UINib(nibName: "yourCustomXib", bundle: nil).instantiate(withOwner: nil, options: nil).first as? YourCustomView else {
fatalError("Missing yourCustomXib")
}
navigationController?.navigationBar.addSubview(yourCustomView)

Custom navigationItem.titleView not setting

I am having troubles setting the self.navigationItem.titleView, could someone please help me catch my mistake.
import Foundation
class CustomNavigationController: UINavigationController
{
override func viewDidLoad() {
let logo = UIImage(named: "browse_back")
var hexColor = 0x21BBD4 as UInt
self.navigationBar.barTintColor = GeneralHelper.UIColorFromRGB(hexColor)
self.navigationItem.titleView = UIImageView(image: logo)
}
}
Here is my code for setting the titleView to an image.
When I run the application, the color of the navigation bar is being changed to the correct color, but the titleView image is not displaying.
I've tested to ensure the image does exist.
Thanks.
The managing UINavigationController object uses the navigation items
of the topmost two view controllers to populate the navigation bar
with content.
Source: UINavigationItem Class Reference
You have to set the titleView of the navigationItem of the controller that is the top most controller in the navigation stack managed by your custom navigation controller.
For those using a UILabel as your titleView
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationItem.titleView?.sizeToFit()
}
Hope this works!

Resources