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
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()
}
}
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)
I want to customize the image for a back button in just a viewController.
So for this viewController i have:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.backIndicatorImage = #customImage
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.backIndicatorImage = #restoreImage
}
But when im displaying the previous viewController (viewWillDissapear is called) this previous viewController wait until its displayed to set the image (if i swippe this doesnt happen):
Here, changes of image (imageSize 40 * 40) working fine. You can try this.
SecondViewController:
override func viewWillAppear(_ animated: Bool) {
var backButtonImage = UIImage(named: "lineBack.png")
UIBarButtonItem.appearance().setBackButtonBackgroundImage(backButtonImage, for: .normal, barMetrics: .default)
}
ThirdViewController:
override func viewWillAppear(_ animated: Bool) {
var backButtonImage = UIImage(named: "roundBack.png")
UIBarButtonItem.appearance().setBackButtonBackgroundImage(backButtonImage, for: .normal, barMetrics: .default)
}
I'm using 2 different bar tint colors at UINavigationBar in different views. I'n changing color with that method in both views:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.barTintColor = COLOR
}
When I tap on back button color is not changed smoothly (you can see blink on last second).
But everything is okay if just swipe view back instead of tapping on back button.
How to make smooth transition in both situations?
To achieve this kind of animation you should use UIViewControllerTransitionCoordinator as Apple documentation say it is :
An object that adopts the UIViewControllerTransitionCoordinator protocol provides support for animations associated with a view controller transition.(...)
So every UIViewController has own transitionController. To get this you should call in the UIViewControllerClass :
self.transitionCoordinator()
From documentation:
Returns the active transition coordinator object.
So to get the result that you want you should implement animateAlongsideTransition method in viewController transitionCoordinatior. Animation works when you click backButton and swipe to back.
Example :
First Controller :
class ViewControllerA: UIViewController {
override func loadView() {
super.loadView()
title = "A"
view.backgroundColor = .white
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "NEXT", style: .plain, target: self, action: #selector(self.showController))
setColors()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
animate()
}
func showController() {
navigationController?.pushViewController(ViewControllerB(), animated: true)
}
private func animate() {
guard let coordinator = self.transitionCoordinator else {
return
}
coordinator.animate(alongsideTransition: {
[weak self] context in
self?.setColors()
}, completion: nil)
}
private func setColors() {
navigationController?.navigationBar.tintColor = .black
navigationController?.navigationBar.barTintColor = .red
}
}
Second Controller:
class ViewControllerB : UIViewController {
override func loadView() {
super.loadView()
title = "B"
view.backgroundColor = .white
setColors()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
animate()
}
override func willMove(toParentViewController parent: UIViewController?) { // tricky part in iOS 10
navigationController?.navigationBar.barTintColor = .red //previous color
super.willMove(toParentViewController: parent)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.navigationBar.barTintColor = .blue
}
private func animate() {
guard let coordinator = self.transitionCoordinator else {
return
}
coordinator.animate(alongsideTransition: {
[weak self] context in
self?.setColors()
}, completion: nil)
}
private func setColors(){
navigationController?.navigationBar.tintColor = .black
navigationController?.navigationBar.barTintColor = .blue
}
}
UPDATE iOS 10
In the iOS 10 the tricky part is to add the willMoveTo(parentViewController parent: UIViewController?) in the second ViewController. And set the navigationBar tintColor to the color value of previous controller. Also, in viewDidAppear method in second ViewControler set the navigationBar.tintColor to the color from second viewController.
Check out my example project on github
I've coded final solution that looks most comfortable to use (don't need to use a lot of overrides in own view controllers). It works perfectly at iOS 10 and easy adoptable for own purposes.
GitHub
You can check GitHub Gist for full class code and more detailed guide, I won't post full code here because Stackoverflow is not intended for storing a lot of code.
Usage
Download Swift file for GitHub. To make it work just use ColorableNavigationController instead of UINavigationController and adopt needed child view controllers to NavigationBarColorable protocol.
Example:
class ViewControllerA: UIViewController, NavigationBarColorable {
public var navigationBarTintColor: UIColor? { return UIColor.blue }
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Push", style: .plain, target: self, action: #selector(self.showController))
}
func showController() {
navigationController?.pushViewController(ViewControllerB(), animated: true)
}
}
class ViewControllerB: UIViewController, NavigationBarColorable {
public var navigationBarTintColor: UIColor? { return UIColor.red }
}
let navigationController = ColorableNavigationController(rootViewController: ViewControllerA())
This worked for me:
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
navigationController?.navigationBar.barTintColor = previous view controller's navigation bar color
}
I am just wondering. For the same purpose I use UINavigationControllerDelegate. In navigationController(_:willShow:) I start the animation using transitionCoordinator?.animate(alongsideTransition:completion:). It works great when pushing new controllers, however pop doesn't.
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
let dst = viewController as! ViewController
guard animated else {
navigationController.navigationBar.barTintColor = dst.navigationBarColor
navigationController.navigationBar.tintColor = dst.tintColor
navigationController.navigationBar.barStyle = dst.barStyle
return
}
navigationController.transitionCoordinator?.animate(alongsideTransition: { context in
navigationController.navigationBar.barTintColor = dst.navigationBarColor
navigationController.navigationBar.tintColor = dst.tintColor
navigationController.navigationBar.barStyle = dst.barStyle
}, completion: { context in
if context.isCancelled {
let source = context.viewController(forKey: UITransitionContextViewControllerKey.from) as! ViewController
navigationController.navigationBar.barTintColor = source.navigationBarColor
navigationController.navigationBar.tintColor = source.tintColor
navigationController.navigationBar.barStyle = source.barStyle
}
})
Do you see any reason why it should work with pushes but not pops?
How can I hide a navigation bar from first ViewController or a particular ViewController in swift?
I used the following code in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = true
}
and also on viewWillAppear:
override func viewWillAppear(animated: Bool) {
self.navigationController?.isNavigationBarHidden = true
}
Both methods hide the navigation controller from all ViewControllers.
If you know that all other views should have the bar visible, you could use viewWillDisappear to set it to visible again.
In Swift:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
}
Swift 3
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide the navigation bar on the this view controller
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Show the navigation bar on other view controllers
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}
You can unhide navigationController in viewWillDisappear
override func viewWillDisappear(animated: Bool)
{
super.viewWillDisappear(animated)
self.navigationController?.isNavigationBarHidden = false
}
Swift 3
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}
You could also create an extension for this so you will be able to reuse the extension without implementing this again and again in every view controller.
import UIKit
extension UIViewController {
func hideNavigationBar(animated: Bool){
// Hide the navigation bar on the this view controller
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
func showNavigationBar(animated: Bool) {
// Show the navigation bar on other view controllers
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}
}
So you can use the extension methods as below
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
hideNavigationBar(animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
showNavigationBar(animated: animated)
}
In Swift 3, you can use isNavigationBarHidden Property also to show or hide navigation bar
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide the navigation bar for current view controller
self.navigationController?.isNavigationBarHidden = true;
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Show the navigation bar on other view controllers
self.navigationController?.isNavigationBarHidden = false;
}
Ways to hide Navigation Bar in Swift:
self.navigationController?.setNavigationBarHidden(true, animated: true)
self.navigationController?.navigationBar.isHidden = true
self.navigationController?.isNavigationBarHidden = true
Ways to show Navigation Bar in Swift:
self.navigationController?.setNavigationBarHidden(false, animated: true)
self.navigationController?.navigationBar.isHidden = false
self.navigationController?.isNavigationBarHidden = false
private func setupView() {
view.backgroundColor = .white
navigationController?.setNavigationBarHidden(true, animated: false)
}
Alternative
in viewDidLoad use this settings
title = "Madman"
navigationController?.isNavigationBarHidden = false
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
Check the constraints of Collectionview, scrollview or tableView
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
Want to Hide the NavigationBar on the First ViewController
override func viewWillAppear(animated: Bool) {
self.navigationController?.isNavigationBarHidden = true
}
Want to Show the NavigationBar on the Second ViewController
override func viewWillAppear(animated: Bool) {
self.navigationController?.isNavigationBarHidden = false
}
/*. Swift 5 */
let controller = self.storyboard?.instantiateViewController(withIdentifier: "sc_userNavigation") as! UserNavigationViewController
let navigationController = UINavigationController(rootViewController: controller)
navigationController.setNavigationBarHidden(true, animated: false)
navigationController.modalPresentationStyle = .fullScreen
self.present(navigationController, animated: false, completion: nil)
In IOS 8 do it like
navigationController?.hidesBarsOnTap = true
but only when it's part of a UINavigationController
make it false when you want it back
I use a variant of the above, and isolate sections of my app to be embedded in differing NavControllers. This way, i don't have to reset visibility. Very useful in startup sequences, for example.
Call the set hide method in view Will appear and Disappear. if you will not call the method in view will disappear with status false.It will hide the navigation bar in complete navigation hierarchy
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(false, animated:true)
}
You can do it from the window controller (Swift3)
class WindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
window?.titleVisibility = .hidden
}
}