There are many answers in SO that provide solutions for hiding the navigation bar shadow. Those work for me except for this particular case, which I'm describing here. Therefore, this question is not a duplicate.
To test this particular case, I created a new project using the master-detail app template. In the DetailViewController -> viewDidAppear, I coded the following:
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
self.navigationController?.navigationBar.shadowImage = UIImage()
The above code works for a Single View App and on iPad Air 2 simulator. However, it doesn't work on the detailViewController of a master-detail app in iPhoneX simulator.
Alternatively, I also tried retrieving the subviews of the navigationBar in viewDidAppear and tried hiding the shadow (see code below). However, the subview count is zero. How could that be?
for parent in self.navigationController!.navigationBar.subviews {
for childView in parent.subviews {
if(childView is UIImageView) {
childView.removeFromSuperview()
}
}
}
Any help on this is much appreciated.
For Generic flow
You could use this setup. Say ViewController is the all other view controller class (where you want the shadow), and DetailViewController is the detail view controller class.
The thing i'm doing preserving the shadow image.
ViewController.swift
class ViewController: UIViewController {
var shadowImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
shadowImage = self.navigationController?.navigationBar.shadowImage
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.shadowImage = shadowImage
}
}
And DetailViewController.swift
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationController?.navigationBar.shadowImage = UIImage()
}
}
Storyboard setup
Output
Note: Another neat approach would be storing the shadow within the DetailsViewController and setting it while the view is about to disappear
class DetailsViewController: UIViewController {
var shadowImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
shadowImage = self.navigationController?.navigationBar.shadowImage
self.navigationController?.navigationBar.shadowImage = UIImage()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.navigationBar.shadowImage = shadowImage
}
}
This solution is more elegant and resulting in a clean management.
For MasterDetailFlow, Using SplitViewController
In your MasterViewControlelr.swift
override func viewWillAppear(_ animated: Bool) {
clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed // placeholder code when you created the project
super.viewWillAppear(animated)
self.navigationController?.navigationBar.shadowImage = nil
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.navigationBar.shadowImage = UIImage()
}
In your DetailViewController.swift
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.shadowImage = UIImage()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.navigationBar.shadowImage = nil
}
Output(Master/Detail flow)
Related
I want to hide the navigationbar for only one viewcontroller which is the root viewcontroller of the UINavigationController.
Currently I am using below code to hide the navigation bar for a particular viewcontroller.
To hide the navigationbar,
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = true
super.viewWillAppear(animated)
}
To show the navigationbar for other viewcontrollers,
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = false
super.viewWillDisappear(animated)
}
When I am trying to use this code, the app is being crashed in iOS 13 devices because of threading violation: expected the main thread.
Please checkout the issue which I am getting when I use the above code to hide the navigationbar,
iOS 13: threading violation: expected the main thread
Please let me know if there is any other way to hide the navigationbar for only one viewcontroller.
I got the another way to hide/show navigationbar from one of my friend.
Set a delegate for the NavigationController:
navigationController.delegate = self
Hide/Show navigationbar for each ViewController all in one place
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
let hide = (viewController is YourVC)
navigationController.setNavigationBarHidden(hide, animated: animated)
}
import UIKit
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = true
}
override func viewWillDisappear(_ animated: Bool){
super.viewWillDisappear(animated)
self.navigationController?.isNavigationBarHidden = false
}
}
You can make it transparent (Completely invisible) when viewWillApper get called and back to normal when view willDisappear get called. Here are helper functions.
func makeNaBarTransparent() {
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.isTranslucent = true
}
func restoreNavigationBarToDefault() {
navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
navigationController?.navigationBar.shadowImage = nil
}
USAGE
import UIKit
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
makeNaBarTransparent()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
restoreNavigationBarToDefault()
}
}
I have structure:
*- TabBarViewController (Root)
*-- NavigationViewController
*---- ChatViewController
*-- NavigationViewController
*---- MenuViewController
and while I'm switching tabbar items, viewWillAppear in (Chat, Menu) called only once, but in NavigationVC called every times i switch.
Is it possible to call automatically viewWillAppeare in Chat and Menu ViewControllers while switching items?
super.viewWillAppear is inside method.
my code looks like:
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let v1 = storyboard!.instantiateViewController(withIdentifier: "ChatViewController")
let v2 = storyboard!.instantiateViewController(withIdentifier: "MenuViewController")
viewControllers = [v1,v2]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
class ChatViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(self,#function)
}
}
class MenuViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(self,#function)
}
}
It works on clear new project but on old ( where im working, and i have loot of funcionality, doesn't work)
StoryboardId is linked to NavigationViewController in Storyboard
I found issue:
In extension UINavigationController i have method
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !UserDefaults.standard.isUserPresented {
navigationBar.barTintColor = .rgbColor(red: 43, green: 43, blue: 43, alpha: 1)
} else {
navigationBar.barTintColor = .rgbColor(red: 100, green: 100, blue: 100, alpha: 1)
}
}
and this block viewWillAppear in child view controllers in NavigationBar
Your question is not clear about adding viewcontroller to tabbarcontroller and navigation controller . I have created everything in storyboard . View will appear in view controller are :
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Menu View will appear")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Chat View will appear")
}
Am able to get below output when i switch :
Menu View will appear
Chat View will appear
Menu View will appear
Chat View will appear
Is it possible to set the navigation bar color for just a single View Controller in the navigation hierarchy? Let the default navigation bar color be red and just the last view controller in the line should have a blue one. I've used these two lines to color the navigation bar of said view controller:
navigationController?.navigationBar.barTintColor = .blue
navigationController?.navigationBar.tintColor = .white
But when going back (e.g. by pressing the back button) the navigation bar stays blue. Setting the color back to red using above code doesn't do anything.
The navigationBar is shared across all the view controllers that are in the same UINavigationController stack.
If you want to change it's look for a specific view controller, you'll have to set the new style when the view controller is shown and remove it when the view controller is dismissed. This could be done in the viewWillAppear/viewWillDisappear of your view controller for example.
I could get the navigation bar to change colors coming from a ViewControllerB to ViewControllerA perfectly fine with your code. I am not sure what your initial problem was. Here is my code which worked:
ViewController A:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.barTintColor = .red
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonAction(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "Second")
//self.present(controller, animated: true, completion: nil)
self.navigationController?.pushViewController(controller, animated: true)
}
}
ViewController B:
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.barTintColor = .blue
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
It worked without an issue.
I want to make a Navigation Bar that goes transparent in detail but with my current code the Bar doesn't return to it's non transparent state. How can this be fixed? I want to this code to also work in the MoreNavigationController from the UITabBarController.
The code that has been placed in the Detail ViewController.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in
self?.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self?.navigationController?.navigationBar.shadowImage = UIImage()
}, completion: { context in
})
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in
self?.navigationController?.navigationBar.setBackgroundImage(nil, for: UIBarMetrics.default)
self?.navigationController?.navigationBar.shadowImage = nil
}, completion: { context in
})
}
Add the code below to DetailViewController.
It was confirmed that this code also works in the UINavigationController from the UITabBarController.
NextController.swift
class NextViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setNavigationBar()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
self.navigationController?.navigationBar.shadowImage = nil
self.navigationController?.navigationBar.isTranslucent = true
}
func setNavigationBar() {
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = false
}
}
Preview
I am presenting a loading screen where I do not want the navigation bar to be shown. So I use
self.navigationController.navigationBar.hidden = true
Which does the trick and hides the navigation bar. But when I want to show the navigation bar, I want to animate it in.
I have tried using this code but the bar does not appear.
self.navigationController.setNavigationBarHidden(false, animated: true)
After running the above code the bar is still hidden, how can I show/animate the bar?
I think your method is correct already. You just need to know where to put the code. Try the following code.
Code for ViewController 1
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBarHidden = true;
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.navigationBarHidden = true;
}
#IBAction func buttonTapped(sender: UIButton) {
self.performSegueWithIdentifier("goToScreen2", sender: self)
}
}
Code for ViewController 2
import UIKit
class ViewController2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
}
Update Answer:
I am able to unhide the navigation bar using the following code.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBarHidden = true;
}
#IBAction func buttonTapped(sender: UIButton) {
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
Screen shot of the implementation:-