difference between navigationBar.isHidden and setNavigationBarHidden - ios

I have view controller which I am pushing from a view controller where navigation bar is hidden. But I want to show the navigation bar in destination view controller.
I tried with this statement it was not showing navigationBar.
self.navigationController?.navigationBar.isHidden = false
I tried this statement it is working
self.navigationController?.setNavigationBarHidden(false, animated: true)
I want to know what is difference?

.isHidden and setNavigationBarHidden() have different effects and outcomes. We won't be talking about the animated part.
The property of self.navigationController?.navigationBar.isHidden is an extension from UIView. The isHidden property belongs to UIView that means that navigationBar (which extends UIView) had done some overriding in isHidden causing it to have different effect and outcome compared to setNavigationBarHidden().
Example for setNavigationBarHidden(true):
where the navigationBar won't be transparent and page 1 (page with navigationBar hidden).
Example for .isHidden = true:
where the navigationBar is fully transparent and page 1 is displayed under page 2's navigationBar. Number 3 is the UIWindow.

Effect is exactly the same, but when using second version (method) you can also define animation.
When you are doing it via property - animation is off by default.
On top of that you have another option:
self.navigationController?.isNavigationBarHidden = false
More on that topic here:
https://developer.apple.com/documentation/uikit/uinavigationcontroller/1621850-isnavigationbarhidden
If true, the navigation bar is hidden. The default value is false. Setting this property changes the visibility of the navigation bar without animating the changes. If you want to animate the change, use the setNavigationBarHidden(_:animated:)method instead.

Nope they are not the same,
self.navigationController?.isNavigationBarHidden
is a getter property (it shows you wheather the navBar is hidden or not.) It will just return a boolean value conveying the state of the NavBar weather it is hidden or not.
(try assigning its value to bool like var isHidden = self.navigationController?.isNavigationBarHidden it will give you true of false according to state to the NavBar.)
whereas
self.navigationController?.setNavigationBarHidden(false, animated: true)
is a setter propety which gives you the provision of setting the state of the navBar.
Compiler does not throw any error on self.navigationController?.isNavigationBarHidden = false
but will not do anything as you can only know the state from here.

Related

Grey background in navigation bar with searchController added to navigationItem during push

I have a table view in navigation controller so that I can push the detail view controller on the stack. It works fine, until I add a search controller to the navigation item, like so:
searchController = UISearchController(searchResultsController: nil)
searchController.obscuresBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.delegate = self
searchController.searchBar.tintColor = Colors.mlLabel
navigationItem.searchController = searchController
definesPresentationContext = true
It results in temporary grey background, see below:
When debugging the view hierarchy, it looks like UIViewControllerWrapperView's _UIParallaxDimmingView(selected below) is causing this, as both navigation bar and status bar are transparent.
How can I fix this?
Note: Setting the animated property in pushViewController() to false works, but I'd like to keep the animation.
Update: This seems to be issue only on iOS 13. Probably from some recent version even, as I didn't have this issue earlier.
Update 2: I've noticed the same issue on multiple places in my app now, and it's not just in combination with SearchController. Basically the _UIParallaxDimmingView sticks its nose out.
Update
Here's the code I use to go from a large title to a small title. These are properties for the large title viewcontroller, or more specific its navigation controller:
navigationController.navigationBar.prefersLargeTitles = true
navigationController.topViewController?.extendedLayoutIncludesOpaqueBars = true
Perhaps the second line above might help you?
As for pushing any view controllers, I see I've overridden the push-function from the navigation controller (as I use the navigation controller for each tab in my tabbar):
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
if viewControllers.count >= 1 {
viewController.hidesBottomBarWhenPushed = true
viewController.navigationItem.largeTitleDisplayMode = .never
}
super.pushViewController(viewController, animated: animated)
}
Previous answer
I saw this a couple of times before in my life and it always had to do something with the background color of the view controller itself. If it's transparent you see this stuff when animating.
But since it's a search controller, it might be the navigation bar. Anyway, since the issue is only since iOS13, I believe the issue can be resolved using this:
searchController.searchBar.backgroundColor = UIColor.clear (or whatever color)
This new property (UISearchBar.searchTextField.backgroundColor) has been added since iOS13, so maybe this will solve it for you? :)
I've finally found a solution. One of the problems was that I've set a background color for the navbar like so:
UINavigationBar.appearance().backgroundColor = .white
So removing the above line and adding the below line to the view controller being pushed fixed it.
extendedLayoutIncludesOpaqueBars = true

Disable the interactive dismissal of presented view controller

iOS 13 introduces a new design of modalPresentationStyle .pageSheet (and its sibling .formSheet) for modally presented view controllers…
…and we can dismiss these sheets by sliding the presented view controller down (interactive dismissal). Although the new "pull-to-dismiss" feature is pretty useful, it may not always be desirable.
THE QUESTION: How can we turn the interactive dismissal off?
- Bear in mind we keep the presentation style the same.
Option 1:
viewController.isModalInPresentation = true
(Disabled interactive .pageSheet dismissal acts like this.)
Since the iOS 13, UIViewController contains a new property called isModalInPresentation which must be set to true to prevent the interactive dismissal.
It basically ignores events outside the view controller's bounds. Bear that in mind if you are using not only the automatic style but also presentation styles like .popover etc.
This property is false by default.
From the official docs: If true, UIKit ignores events outside the view controller's bounds and prevents the interactive dismissal of the view controller while it is onscreen.
Option 2:
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
return false
}
Since the iOS 13, UIAdaptivePresentationControllerDelegate contains a new method called presentationControllerShouldDismiss.
This method is called only if the presented view controller is not dismissed programmatically and its isModalInPresentation property is set to false.
Tip: Don't forget to assign presentationController's delegate. But be aware, it is known that even just accessing the presentationController can cause a memory leak.
If you want the same behaviour as it's in previous iOS version (< iOS13) like model presentation in fullscreen, just set the presentation style of your destination view controller to UIModalPresentationStyle.fullScreen
let someViewController = \*VIEW CONTROLLER*\
someViewController.modalPresentationStyle = .fullScreen
And if you are using storyboard just select the segua and select Full Screen form the Presentation dropdown.
If you just want to disable the interactive dismissal and keep the new presentation style set UIViewController property isModalInPresentation to true.
if #available(iOS 13.0, *) {
someViewController.isModalInPresentation = true // available in IOS13
}
The property isModalInPresentation might help.
From the documentation:
When you set it to true, UIKit ignores events outside the view controller's bounds and prevents the interactive dismissal of the view controller while it is onscreen.
You can use it like this:
let controller = MyViewController()
controller.isModalInPresentation = true
self.present(controller, animated: true, completion: nil)
If you have some business logic, something like all fields should be filled before dismissing, you should:
On ViewDidLoad if your ViewController has been presented within a Navigation Controller:
func viewDidLoad() {
self.navigationController?.presentationController?.delegate = self
}
If not, simply use
func viewDidLoad() {
self.presentationController?.delegate = self
}
Then implement the delegate method:
extension ViewController: UIAdaptivePresentationControllerDelegate {
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
guard let text = firstName.text, text.isEmpty else { return false }
guard let text = lastName.text, text.isEmpty else { return false }
...
return true
}
}
If you are using storyboards to layout your UI I have found the best way to disable this interactive dismissal when using a navigation controller is to change the presentation of the Navigation Controller in the attribute inspector from Automatic to Full Screen. All view controllers in your navigation stack will then be full screen and will not be able to be dismissed by the user.
Attribute Inspector showing presentation option for the navigation controller
Apple shared a sample code about it at this link
It uses isModalInPresentation as many users suggestion.
All solutions are good, but in my case, I need an option to stop movement.
So this is a code for that.
if you want to block movement:
self.yourViewController?.presentedView?.gestureRecognizers?[0].isEnabled = false
And if you want to unblock movement:
self.yourViewController?.presentedView?.gestureRecognizers?[0].isEnabled = true

Accessing UITabBarController for child viewControllers

If I am using UITabBarController Item1 and Item2 viewControllers are displayed properly. But UITabBarButtonItem is not displaying item1's redirect page. UITabBarButtonItem must display on all pages .
My problem is UITabBarButtonItem is does not display the childViewController(red page). How to display the UITabBarButtonItem to childViewController?
Firstly, you should embed the first view controller in the hierarchy (the blue view controller) in a navigation controller. Then, in the red view controller, make sure that hidesBottomBarWhenPushed property is set to false:
// for instance, let's assume that you will do it in the `viewDidLoad()`:
override func viewDidLoad() {
super.viewDidLoad()
hidesBottomBarWhenPushed = false
// ...
}
Or if you want to achieve from the interface builder, select the red view controller and from the attribute inspector and make sure that the "Hide Bottom Bar on Push" option is unchecked:
Otherwise, if there is no navigation controller (presenting instead of pushing), there is no way to display the bottom bar in the red view controller.

How to disable backBarButtonItem? [duplicate]

Is there any official way how to set UIBarButtonItem.enabled property? I tried to set a backButtonItem in previous controller. But enabled property is ignored.
More in this simple example project.
I don't want to some solution like "make your own leftBarButtonItem and set its alpha ..."
Edit: I don't want to hide it, only disable it with dimmed colour and disabled user interaction. It's exactly the same behaviour as for disabled leftBarButtonItem.
As of today it is not possible to disable the back button using the enabled property. The backBarButtonItem property will be nil unless you create a custom item and even then it will ignore the enabled property. There are a couple (non-satisfactory) ways around this.
Hide the button
This is what Apple wants you to do given that they ignore the enabled property. It is as simple as
navigationItem.hidesBackButton = true
and should be the preferred approach unless you have good reasons.
Disable and Tint the Navigation Bar
You can disable user interaction on the whole navigation bar and tint it to make the back button appear disabled.
navigationController?.navigationBar.isUserInteractionEnabled = false
navigationController?.navigationBar.tintColor = UIColor.lightGray
This does, unfortunately, affect other elements in the navigation bar as well so it might not be an option if, for instance, you have another bar button item on the right side.
Use a Custom Left Bar Button Item
The leftBarButtonItem does not ignore the enabled property so you could create a custom item and trigger the pop manually when it is activated.
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(ThisClass.backButtonTapped))
...
navigationItem.leftBarButtonItem?.isEnabled = false
func backButtonTapped() {
self.navigationController?.popViewController(animated: true)
}
This will, however, not have the back bar button style with the leading triangular indicator.
Add below code in your ViewController2.swift Class.
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.hidesBackButton = true;
}
It will hide your back button.
If you want to hide it, UInavigationItem has a hidesBackButton property.
I know this is an old thread, but this may help someone else.
As mentioned by hennes, you can no longer disable the back button. Instead, you will need to disable the entire navigationBar.
The approach I took, was disabling the navigationBar, and then applying an 0.5 alpha to the subviews of the navigation bar.
In your ViewController class:
func changeBarButtons(alpha: CGFloat) {
navigationController?.navigationBar.subviews.forEach { firstViews in
firstViews.subviews.forEach { view in
if ["_UIButtonBarButton", "_UIButtonBarStackView"].contains(type(of: view).description()) {
view.alpha = alpha
}
}
}
}
func set(loading: Bool) {
let alpha: CGFloat = loading ? 0.5 : 1
navigationController?.navigationBar.isUserInteractionEnabled = !loading
changeBarButtons(alpha: alpha)
}
Keep in mind, that Apple could change the names of the class any time. That being said, it's highly unlikely they do so. If you don't mind the title of the View Controller fading out, you can apply the alpha to all the subviews, without checking the class name.
Don't try to disable your custom back button (won't work), just set a new one which is disabled. You can reach the previous navigation item through the UINavigationBar.backItem property.
// set disabled back button
let backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItem.Style.plain, target: nil, action: nil)
backButton.isEnabled = false
navigationController?.navigationBar.backItem?.backBarButtonItem = backButton
// disable pop gesture
navigationController?.interactivePopGestureRecognizer?.isEnabled = false

How to disable back button in navigation bar

Is there any official way how to set UIBarButtonItem.enabled property? I tried to set a backButtonItem in previous controller. But enabled property is ignored.
More in this simple example project.
I don't want to some solution like "make your own leftBarButtonItem and set its alpha ..."
Edit: I don't want to hide it, only disable it with dimmed colour and disabled user interaction. It's exactly the same behaviour as for disabled leftBarButtonItem.
As of today it is not possible to disable the back button using the enabled property. The backBarButtonItem property will be nil unless you create a custom item and even then it will ignore the enabled property. There are a couple (non-satisfactory) ways around this.
Hide the button
This is what Apple wants you to do given that they ignore the enabled property. It is as simple as
navigationItem.hidesBackButton = true
and should be the preferred approach unless you have good reasons.
Disable and Tint the Navigation Bar
You can disable user interaction on the whole navigation bar and tint it to make the back button appear disabled.
navigationController?.navigationBar.isUserInteractionEnabled = false
navigationController?.navigationBar.tintColor = UIColor.lightGray
This does, unfortunately, affect other elements in the navigation bar as well so it might not be an option if, for instance, you have another bar button item on the right side.
Use a Custom Left Bar Button Item
The leftBarButtonItem does not ignore the enabled property so you could create a custom item and trigger the pop manually when it is activated.
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(ThisClass.backButtonTapped))
...
navigationItem.leftBarButtonItem?.isEnabled = false
func backButtonTapped() {
self.navigationController?.popViewController(animated: true)
}
This will, however, not have the back bar button style with the leading triangular indicator.
Add below code in your ViewController2.swift Class.
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.hidesBackButton = true;
}
It will hide your back button.
If you want to hide it, UInavigationItem has a hidesBackButton property.
I know this is an old thread, but this may help someone else.
As mentioned by hennes, you can no longer disable the back button. Instead, you will need to disable the entire navigationBar.
The approach I took, was disabling the navigationBar, and then applying an 0.5 alpha to the subviews of the navigation bar.
In your ViewController class:
func changeBarButtons(alpha: CGFloat) {
navigationController?.navigationBar.subviews.forEach { firstViews in
firstViews.subviews.forEach { view in
if ["_UIButtonBarButton", "_UIButtonBarStackView"].contains(type(of: view).description()) {
view.alpha = alpha
}
}
}
}
func set(loading: Bool) {
let alpha: CGFloat = loading ? 0.5 : 1
navigationController?.navigationBar.isUserInteractionEnabled = !loading
changeBarButtons(alpha: alpha)
}
Keep in mind, that Apple could change the names of the class any time. That being said, it's highly unlikely they do so. If you don't mind the title of the View Controller fading out, you can apply the alpha to all the subviews, without checking the class name.
Don't try to disable your custom back button (won't work), just set a new one which is disabled. You can reach the previous navigation item through the UINavigationBar.backItem property.
// set disabled back button
let backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItem.Style.plain, target: nil, action: nil)
backButton.isEnabled = false
navigationController?.navigationBar.backItem?.backBarButtonItem = backButton
// disable pop gesture
navigationController?.interactivePopGestureRecognizer?.isEnabled = false

Resources