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
Related
So I have a custom view controller that displays a dialog with a couple of buttons.
When the view appears I want voiceover to read out some basic information describing the dialog.
To achieve this I made the parent view to be an accessible element and the subviews which are two buttons are also accessible elements.
My problems now is that the buttons are not clickable directly.
They must be reached only by swiping right on the screen.
class MyViewController: UIViewController {
let parent = UIView()
let button1 = UIButton()
let button2 = UIButton()
init() {
parent.addSubview(button1)
parent.addSubview(button2)
parent.isAccessibilityElement = true
button1.isAccessibilityElement = true
button2.isAccessibilityElement = true
parent.accessibilityLabel = "Message"
self.view.addSubview(parent)
self.view.accessibilityElements = [parent, button1, button2]
}
override func viewDidAppear(_ animated: Bool) {
}
}
If there is a better way to get voiceover to give description of the view when the opens, I am open to that too.
Also, the view needs to be a modal so that focus is trapped on the view.
To achieve this I made the parent view to be an accessible element and the subviews which are two buttons are also accessible elements.
That's definitely the problem: you can't have the parent view and its children accessible all together ⟹ see the example sheet of this explanation.
If a parent view is accessible, its children won't be seen by VoiceOver and conversely.
If there is a better way to get voiceover to give description of the view when the opens, I am open to that too.
Using VoiceOver, you must be as accurate and brief as possible.
The description of a view is provided by its elements when you explore the screen or by its title itself : in my view, you shouldn't read out a description that a perfect title should provide in addition to the correct implementation of the different components of your page.
There's a great presentation made by a blind person who explains how to write labels inside an app to be well understood.
Also, the view needs to be a modal so that focus is trapped on the view.
The best way to reach this purpose is to use the accessibilityViewIsModal property of your view ⟹ take a look at this example introduced during a WWDC session if need be.
You can post a notification with a message as parameter, so you would not need to set the parent view as an accessibility element. This would solve both of your problems.
Example code:
let parentVc = UIView()
let button1 = UIButton()
let button2 = UIButton()
init() {
parentVc.addSubview(button1)
parentVc.addSubview(button2)
button1.setTitle("btn1", for: .normal)
button2.setTitle("btn2", for: .normal)
button1.isAccessibilityElement = true
button2.isAccessibilityElement = true
self.view.addSubview(parentVc)
self.view.accessibilityElements = [button1, button2]
UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: "Message here");
}
I have a navigation bar placed directly on my scene. On his I have a navigation item. I can successfully change title on it.
Now I want a back button to appear - I do not need any customization at present, just the ability to catch the click and default ios look for whatever plaform the app is running (Since Swift that is ios7+).
In viewDidLoad() I have written
outletCatalogNavItem.backBarButtonItem = UIBarButtonItem(title: "test", style .plain, target: self, action: "ownbackNavigationFuncCode");
In *ViewDidAppear()* outletCatalogNavItem.backBarButtonItem is non-nil
Note: I am not using any shared navigation control across scenes in my storyboard - nor wish to at this point since I am porting an app from another tool/language which has its own navigation/stack logic already (meaning I handle switching and navigation myself in code)
Embed your view controller in a UINavigationController. When you push/segue to a new view controller the < Back button you're looking for will appear on the left hand-side.
let backItem = UIBarButtonItem()
backItem.title = "Back"
navigationItem.backBarButtonItem = backItem
I need help showing and hiding my custom navigation bar that is not linked with a navigation controller. I have initialized my nav bar and UITapGestureRecognizer in viewWillAppear and want to have the gesture selector to show and hide the nav bar. I have tried numerous navigationController functions but since I have not embedded one they do not seem to work:
navigationController?.barHideOnTapGestureRecognizer
// OR
navigationController?.hidesBarsOnTap = true
// OR
navigationController?.setNavigationBarHidden(true, animated: true)
I have seen that the:
navigationBar.hidden = true //OR FALSE
can change the initial appearance of the navBar, but it cannot toggle show/hide like i need it to. Basically it just needs to mimic the apple Photos app. Thanks in advance.
One way to go is to create an IBAction for your custom navigation bar. In this IBAction you set
navigationBar.hidden = true
or
navigationBar.hidden = false
depending on the current hidden state
Or if you create your elements programmatically, you can try an approach using Target and Event for your UIGestureRecognizer.
First you add a Target to your element:
gestureRecognizer.addTarget(self, action: "pressed:", forControlEvents: .TouchUpInside)
In action parameter of .addTarget, you set the method name that should be called.
Then write the method where you alter the hidden state:
func pressed(sender: UIGestureRecognizer!) {
if navigationBar.hidden = true {
navigationBar.hidden = false}
else{
navigationBar.hidden = true}
}
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
I have a settings bar button item (set as left bar button item). I only want to display it if the user is logged in.
I thought I could use the following for anonymous users
navigationItem.leftBarButtonItem = nil
But then how would I show it as soon as they logged in?
You can store a copy of the leftBarButtonItem in a strong property and update it after the users log in.
var leftBarButtonItem : UIBarButtonItem!
Inside viewDidLoad:
self.leftBarButtonItem = UIBarButtonItem(title: "test", style: UIBarButtonItem.Style.Plain, target: nil, action: nil)
In logic:
if loggedIn
{
self.navigationItem.leftBarButtonItem = self.leftBarButtonItem
}
else
{
self.navigationItem.leftBarButtonItem = nil
}
Best Way is just custom your Bar buttom with image. Set barbuttom.image = nil to Hide again assign the image to show. And dont forget to make the barbutton isEnabled as false.
I have more that 2 menuitems and remove/add menuitem is an overhead. This code snippet worked for me.
func showMenuItem(){
menuItemQuit.customView?.isHidden = false
menuItemQuit.plainView.isHidden = false
}
func hideMenuItem(){
menuItemQuit.customView?.isHidden = true
menuItemQuit.plainView.isHidden = true
}
if you want to hide/show UIBarButtonItem : For Swift 3
Used below simple code :
Declaration :
var doneButton = UIBarButtonItem()
In ViewDidLoad() or ViewWillAppear() or where you want to hide it : [hide bar button]
self.navigationItem.rightBarButtonItem = nil
where you want to show bar button : [use anywhere in your code]
self.navigationItem.rightBarButtonItem = self.doneButton
doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(YourViewController.dismissPicker))
Swift 5.x
I faced the same dilemma and unfortunately no solution worked for me. Adding and removing buttons and related segues is unnecessarily too much of code when it includes multiple buttons on multiple screens. I have taken this approach for one or two buttons in the past and it becomes pretty ugly pretty fast.
The code menuItemQuit.customView?.isHidden = false doesn't seem to work on iOS 13 and above either, otherwise it would have made life so much easier.
My approach was to simply disable the bar button and change its tint to the navigation colour's tint.
In my app What.To.Eat I display bar buttons based on user's login status. Every element of the app is themed so that I could control all the colors based on various factors.
The navigation bar's color is named commonButtonColor and the bar buttons tint color is named commonButtonColor.
When I have to hide a bar button, I simply do the following:
let nav = self.navigationController?.navigationBar
nav?.tintColor = Theme.shared.titleText
nav?.barTintColor = Theme.shared.headerBg
if person.loggedIn {
mealPrefsBarButton.tintColor = Theme.shared.commonButtonColor
mealPrefsButton.isEnabled = true
} else {
mealPrefsBarButton.tintColor = Theme.shared.headerBg
mealPrefsButton.isEnabled = false
}
Where theme colors are defined in a separate file like this:
static var headerBg: UIColor {
return UIColor(red: 0.965, green: 0.969, blue: 0.973, alpha: 1.00)
}
The above is a simplified version of what I do in the app to make it clear what I am doing. I hope it would help someone trying to achieve the same. It is simple solution and works just perfectly with a few lines of code.
As an example from the app, this is how two buttons appear and disappear based on whether the My Recipes button is selected or not:
I have a same problem and solved. I have a bar button item with image
barbtnClose.isEnabled = false
barbtnClose.image = nil
barbtnClose.customView?.isHidden = true // do not work in iOS 13
Swift 5
A better solution and works even if you have set a custom navigation bar.
Hide navigation bar button item or back button leftBarButtonItem / rightBarButtonItem
if login == true {
self.navigationItem.leftBarButtonItem = nil
} else {
print("set your bar button or return")
}
Hide back bar button in navigation controller with swift 5
self.navigationItem.leftBarButtonItem = nil
self.navigationItem.hidesBackButton = true