Update: This bug has been fixed in iOS 14.5
I have the following class embedded in a UINavigationController:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let barButton = UIBarButtonItem(title: "Save", style: .plain, target: nil, action: nil)
barButton.accessibilityLabel = "Save meeting"
navigationItem.rightBarButtonItem = barButton
}
}
When running iOS 14.4, the accessibility label is ignored and only the visible title is announced by VoiceOver. However, on iOS 13.7, the accessibility label is correctly used. Has the UIBarButtonItem usage changed or is this an iOS bug?
Screenshot for context:
When I must implement a UIBarButtonItem, I always follow these instructions to be sure that a11y will be stable and completely functional. 👍
I don't know if this situation is a bug or a kind of regression due to the new iOS version but implementing a11y in the navigation bar buttons as customization is a perfect way to encounter no unfortunate surprises even if it looks like a boilerplate solution. 🤓
I've created a blank project with a simple view controller embedded in a navigation controller where a right bar button is displayed as follows:
class NavBarViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
var a11yRightBarButton: UIBarButtonItem?
let a11y = UILabel()
a11y.text = "OK"
a11y.sizeToFit()
a11y.isUserInteractionEnabled = true //Mandatory to use the 'tap gesture'.
a11yRightBarButton = UIBarButtonItem(customView: a11y)
let tap = UITapGestureRecognizer(target: self,
action: #selector(validateActions(info:)))
a11yRightBarButton?.customView?.addGestureRecognizer(tap)
a11yRightBarButton?.isAccessibilityElement = true
a11yRightBarButton?.accessibilityTraits = .button
a11yRightBarButton?.accessibilityLabel = "validate your actions"
navigationItem.rightBarButtonItem = a11yRightBarButton
}
#objc func validateActions(info: Bool) -> Bool {
print("hello")
return true
}
}
Your right bar button displays "OK" and VoiceOver reads out "validate your actions" under iOS 14.4 and Xcode 12.4. 😉
Following this rationale, you can use a UIBarButtonItem as supporting the accessibilityLabel property in iOS 14. 🎉
Set accessibility of UIBarButtonItem to true.
let barButton = UIBarButtonItem(title: "Save", style: .plain, target: nil, action: nil)
barButton.accessibilityLabel = "Save meeting"
barButton.isAccessibilityElement = true
navigationItem.rightBarButtonItem = barButton
Related
I tried to identify what the "Back" text is from by the following:
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.setTransparent(style: .default)
print(self.navigationItem.backButtonTitle)
print(self.navigationItem.backBarButtonItem?.title)
print(self.navigationItem.title)
print(self.navigationItem.titleView?.largeContentTitle)
}
However I got all nil outputs, which means these titles are not there:
nil
nil
nil
nil
I also tried this:
self.navigationItem.setHidesBackButton(true, animated: false)
It works but it hides the whole thing, including the back arrow "<" and the text "Back". I wanted to keep the arrow but only remove the text.
Where can I find this title or text that says "Back" and set it to empty string?
This will work for you
self.navigationController?.navigationBar.topItem?.title = " "
one way is shown in attached Image.
Second one is follow:
navigationItem.backButtonTitle = ""
You can use custom left bar button item, in viewDidLoad:
let myimage = UIImage(systemName: "chevron.backward")
navigationItem.leftBarButtonItem = UIBarButtonItem(image: myimage, style: .plain, target: self, action: #selector(handleBack))
to go back the button call handleBack func:
#objc fileprivate func handleBack() {
navigationController?.popViewController(animated: true)
}
I've been creating a small iOS app using Swift just for fun, and I have already decided that I want a notification box (a bell-shaped button you can click on to check if there's any notification), and I also wanted to add the bell-shaped button to every screen.
So, I decided to make a base view controller and have other view controllers inherit it. However, that's when my problem arose; I have no idea how to add an action func for that button. Since I create the bell-shaped button programmatically, I cannot just ^ drag and create a new IBaction.
I found this post: link, but this is for a UIButton, not for a UIBarButton, and it didn't work for me.
Sorry for this long question. Below is a simple, one-sentenced question:
MY PROBLEM
How can I add an action to a UIBarButton programmatically?
UPDATE
Here's my base view controller:
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// add a notification button
let notificationButton = UIBarButtonItem(image: UIImage(systemName: "bell.fill"))
notificationButton.tintColor = .black
self.navigationItem.rightBarButtonItem = notificationButton
}
}
UPDATE2
Here's my new code:
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// add a notification button
let notificationButton = UIBarButtonItem(
image: UIImage(systemName: "bell.fill"),
style: .plain,
target: self,
action: #selector(notificationButtonPressed)
)
notificationButton.tintColor = .black
self.navigationItem.rightBarButtonItem = notificationButton
}
#objc func notificationButtonPressed() {
print("Hello")
}
}
You can pass a target-action pair to the initialiser of UIBarButtonItem:
let barButton = UIBarButtonItem(
image: UIImage(systemName: "bell.fill"),
style: .plain,
target: self, action: #selector(buttonTapped)
)
// somewhere in your view controller:
#objc func buttonTapped() {
// do something when the bar button is tapped
}
See the documentation here.
This is similar to UIButton's addTarget(_:action:for:_) method, if you are familiar with that.
After adapting to the new iOS 11 UISearchController in the NavigationItem, I encountered some problems.
I've added a UIBarButtonItem to the toolbar of the search bar of a UISearchController. This button calls a function when clicked (passed as the action-parameter in the UIBarButtonItem constructor).
Prior to iOS 11, the search bar has been attached to the tableHeaderView, and this worked (and still works) perfectly. The function is called when the button is clicked.
However, in iOS 11 the function is not called even though the implementation is the same.
Any ideas what could be wrong? Or is a bug in iOS 11?
private func setupSearchController() {
...
let toolbar = UIToolbar()
toolbar.sizeToFit()
// Create bar button item with image.
let qrBarButton = UIBarButtonItem(image: #imageLiteral(resourceName: "QR Code"), style: .plain, target: nil, action: #selector(didPressQRToolbarButton))
// Add the new button.
toolbar.items = [qrBarButton]
searchController.searchBar.inputAccessoryView = toolbar
// If the device is on iOS 11, use the "native" search bar placement.
if #available(iOS 11.0, *) {
navigationItem.searchController = searchController
// Don't use the large title in the navigation bar.
navigationController?.navigationBar.prefersLargeTitles = false
} else {
// Handled in subclasses.
}
}
/// Action for the QR toolbar button
func didPressQRToolbarButton(sender: Any) {
...
// NOT CALLED
}
First, check the sender of the function func didPressQRToolbarButton(sender: Any).
It should be like this:
func didPressQRToolbarButton(sender: UIBarButtonItem)
Then, you must be getting some warning from xCode since the automatic #objc inference is deprecated on Swift 4.
So, on Swift 4, didPressQRTToolbar or any function passed as an argument to #selector:
#selector(didPressQRToolbarButton)
must add #objc on its declaration:
#objc func didPressQRToolbarButton(sender: Any) {
The problem was fixed by passing self as the target in the UIBarButtonItem contructor. Somehow this wasn't needed prior to iOS 11.
This fix along with festeban26's answer fixed the problem.
I am writing a simple iOS app in XCode 7.3, which I believe puts me using Swift 2.2. I am trying to use a UIDatePicker with UIToolbar with a UITextfield, and for some reason I tapping on the Cancel button seems not to call the method datePickerCancelled on the controller. Everything displays fine(picker, buttons , etc.), but the event won't fire. I have tried several variations of adding the selector to the UIBarButtonItem, and nothing seems to work. As you can see from the code this a pretty trivial case so it escapes why it should be this difficult. Thank you.
override func viewDidLoad() {
super.viewDidLoad()
var datePicker = UIDatePicker()
var datePickerToolbar = UIToolbar()
let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Done, target: nil, action: nil)
let cancelButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Cancel, target: self, action: #selector(datePickerCancelled))
let flexSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
datePickerToolbar.setItems([cancelButton,flexSpace, doneButton], animated: true)
datePicker.userInteractionEnabled = true
cancelButton.enabled = true
self.dateField.inputView = datePicker
self.dateField.inputAccessoryView = datePickerToolbar
}
func datePickerCancelled(){
self.datePicker.resignFirstResponder();
}` I
The problem is this line:
var datePickerToolbar = UIToolbar()
This results in a toolbar of zero size. The Cancel button is visible, but it is outside of its superview, namely the toolbar — because the toolbar has zero size.
A view outside of its superview cannot be tapped.
You can easily confirm this by setting the toolbar's clipsToBounds to true and running the app. The Cancel button will now be invisible, because things outside the toolbar are no longer shown.
If the event did fire, your datePickerCancelled function still wouldn't do anything:
func datePickerCancelled(){
self.datePicker.resignFirstResponder();
}
Your date picker was never first responder, so that line wouldn't cause anything to happen.
Perhaps you meant this:
func datePickerCancelled(){
self.dateField.resignFirstResponder();
}
The first screenshot is taken before playing video in full screen.
The second is taken after the video is opened in full screen and closed.
Any idea why navigation toolbar has extend?
Note: The hamburger button is not the part of the navigation item. It is faked in overlay in parent that holds its child controller inside standard container.
Nothing special inside the source:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
bbiListic = UIBarButtonItem(image: UIImage(identifier: .IcoHeaderListic), style: .Plain, target: self, action: #selector(UIViewController.showListic))
bbiFavorite = UIBarButtonItem(image: UIImage(identifier: .IcoHeaderStarEmpty), style: .Plain, target: self, action: #selector(LiveDogadjajViewController.toggleFavorite(_:)))
...
let items = [bbiListic!,bbiFavorite!]
navigationItem.rightBarButtonItems = items
}
func someRefresh() {
var items = [UIBarButtonItem]()
items.append(bbiListic!)
...
navigationItem.rightBarButtonItems = items
}
Update:
This appears to be a problem only on the latest version of iOS, 9.3
From your screenshots, it look like that height of status bar gets doubled. Try this:-
Before playing your video, hide the status bar
UIApplication.sharedApplication().setStatusBarHidden(true, withAnimation: .None)
After ending the video, show the status bar
UIApplication.sharedApplication().setStatusBarHidden(false, withAnimation: .None)
Pre-requisite:
a) Uncheck "extends edges" by selecting your uiviewcontroller from main.storyboard
b) no constraints exist between your video player and container controller
Solution:
Check if any of your buttons on the nav bar are bottom constrained. Either remove that constraint or apply a fixed height constraint to your custom nav bar view so that it stays the same height.
I contacted the Apple developer technical support about this issue.
They have determined it is probably a bug in iOS 9.3.
Bug id: 26439832, iOS SDK
This is easy workaround for view controller on stack:
// ... add this to init method
let nc = NSNotificationCenter.defaultCenter()
nc.addObserver(self, selector: #selector(didExitFullscreen(_:)), name: MPMoviePlayerDidExitFullscreenNotification, object: nil)
func didExitFullscreen(notification: NSNotification)
{
// hack, fix for 9.3.
if #available(iOS 9.3, *)
{
navigationController?.setNavigationBarHidden(true, animated: false)
navigationController?.setNavigationBarHidden(false, animated: false)
}
}