I'm encountering a strange issue with VoiceOver.
Goals:
Set a UIStackView containing multiple UILabel's as my navigationItem.titleView.
Mark the stack view as an accessibility element and set its accessibilityLabel to an appropriate value.
Set the stack view as the initial VoiceOver focus by calling UIAccessibility.post(notification: .screenChanged, argument: navigationItem.titleView) inside viewDidAppear(animated:).
Expected Result:
When the view controller appears the focus appears to be on the title view and VoiceOver reads the contents of the accessibility label one time.
Actual Result:
VoiceOver starts to read the contents of the accessibility label and then part way through (or sometimes after finishing) it proceeds to read it a second time.
This issue does not occur if I set navigationItem.titleView to an instance of UILabel.
Does anyone know why this happens? Is it a bug in iOS?
I have set up a simple project demonstrating the issue here:
https://github.com/rzulkoski/Focus-TitleView-Bug
The reason why you have a second time reading of your title is in your code.
In your viewDidLoad, you set the stackview accessibility label that VoiceOver automatically reads out to inform the user of the changing.
Next, you notify this changing with a post in your viewDidAppear that VoiceOver naturally reads out as well.
To prevent from this behavior, just delete stackView.accessibilityLabel = label.text in your setupNavigationItem function and add this snippet in your private lazy var label init :
if (self.view.subviews.contains(stackView)) {
stackView.accessibilityLabel = label.text
}
Updating the stackView.accessibilityLabel this way doesn't trigger VoiceOver to inform the user and allows to get your purpose.
However, I don't recommend to read out the title as the first element of a new page unless you reorder the presented elements.
VoiceOver users won't naturally guess that another element is present before the title :
They may not find a way to get back to the previous page.
They may be lost if they get the first element of the page with a 4 fingers simple-tap because they'll get the back button and not the title.
Technically, your problem is solved with the piece of code above but, conceptually, I suggest to reorder your elements if you still want to expose the title as the first element.
==========
EDIT (workaround)
About the technical problem, you're right in your comment, this solution above works thanks to the label reading by VoiceOver.
I commited a solution in your git branch you gave in your initial post.
The problem deals with the UIStackView I cannot explain in this case and cannot solve neither as is.
To reach your purpose, I created a UIAccessibilityELement for the stackview that can be perfectly reached and exposed with no double reading with a postnotification.
I did that because I couldn't get programmatically the stackview new size when the labels are in... maybe creating a UIStackView subclass and get into its layoutSubviews could be the trick ?
This solution should work as a workaround but I don't know the reason why this behavior appears with a UIStackview.
==========
EDIT (solution)
The problem is the way the titleView of the navigationItem is created. The best way to achieve your purpose is to :
Initialize your titleView as a simple UIView whose frame is the same as the stackview's.
Add the stackview as a subview after having specified its frame and its accessibility properties.
Follow the steps hereafter in your code :
Add the .header trait in the stackview property :
private lazy var stackView: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .equalSpacing
stackView.isAccessibilityElement = true
stackView.accessibilityTraits = .header
return stackView
}()
Change the stackview case in your 'switch...case...' code section as below :
case .stackView:
label.text = "UIStackView"
label.sizeToFit()
stackView.addArrangedSubview(label)
label2.text = subtitle
label2.sizeToFit()
stackView.addArrangedSubview(label2)
stackView.frame.size.width = max(label.frame.width, label2.frame.width)
stackView.frame.size.height = label.frame.height + label2.frame.height
stackView.accessibilityLabel = label.text?.appending(", \(label2.text!)")
navigationItem.titleView = UIView(frame: stackView.frame)
navigationItem.titleView?.addSubview(stackView)
}
Now, the postNotification reads out your stackview only once as the first element of your screen.
Related
I've been doing iOS development for a long time and I've never run into this before. The UITextField appears in the UI, it is correctly sized and placed by the constraints I've given it, but when I tap/click on it and doesn't respond. The cursor doesn't appear in the field and the keyboard doesn't show up.
let textfield = UITextField()
textfield.translatesAutoresizingMaskIntoConstraints = false
self.contentview.addSubview(textfield)
textfield.keyboardType = .numberPad
textfield.borderStyle = .roundedRect
textfield.text = "stuff"
// and then I set up constraints which are working as expected
I've double checked my simulator to make sure it isn't a soft keyboard issue.
I don't need any specialized UITextFieldDelegate behavior, I just need to standard behavior that tapping will cause it to become first responder, set the cursor there, and open the keyboard. When I add a UITextField via a storyboard, I don't need to set a delegate in order to get this behavior, so I can't imagine I would need to create specialized delegate code in order to get this basic behavior.
Just to test if something else was wrong, I've tried programmatically forcing the textfield to become first responder and that works.
I've tried forcing this to be true, just in case:
textfield.isUserInteractionEnabled = true
but that didn't change anything.
Any ideas? Why isn't my programmatically created UITextField accepting taps?
EDIT: Got an excellent suggestion to check out the Hierarchy Inspector to see if anything else is on top of it. It looks like I don't have anything on top of it: Here's a screenshot of the sim and of the hierarchy inspector turned a bit to the side so you can see the layers.
There isn't anything in front of the UITextField. (The UITextField has like 4 internal layers, but nothing is in front of them.)
As we have discussed in the comments under the OP, it's always a good idea to check the view hierarchy in the View Hierarchy Inspector to see if the frame is laid out properly and also if perhaps some other view isn't covering the other view which should become the first responder.
The view hierarchy inspector can be found here once the app is running on a simulator or a device.
I have a horizontal stackview with a button and label horizontally
When I turn on voice over, text in these buttons are not reading at all. All others works fine.
Below is how i setup my code. Please guide how to enable these. Not sure why this isn't working
I have my button text and label text taking dynamic values
Also if any info why the accessibilityIdentifier is used would really help. I couldn't find clear explanation for this
self.myHorizontalStackView.isAccessibilityElement = true
//Accessibility for Button
self.submitButton.isAccessibilityElement = true
self.submitButton.accessibilityTraits = UIAccessibilityTraitStaticText
self.submitButton.accessibilityLabel = screenControls.buttonTitle
//Accessibility for Label
self.addressLabel.isAccessibilityElement = true
self.addressLabel.accessibilityTraits = UIAccessibilityTraitStaticText
self.addressLabel.accessibilityLabel = screenControls.address1
So the problem looks like it's coming from your stackView itself. See this other SO question/answer on the subject:
If a container view is accessible, its child views are often obscured to avoid confusion about what the user is trying to interact with.
So here, change your first line in the snippet you shared to:
self.myHorizontalStackView.isAccessibilityElement = false
and that should enable VoiceOver to find the subviews of the stackView.
As far as your other question:
Also if any info why the accessibilityIdentifier is used would really help. I couldn't find clear explanation for this
If you are asking what the identifier would be used for, it's primarily for testing purposes. By setting unique identifiers to your views, your tests can easily use the accessibility engine to find specific views and perform any actions you'd want to test. In terms of VoiceOver, the accessibilityIdentifier does not have a role.
This is cute ... I've copied the code from Apple's docs -- adding the lines about isUserInteractionEnabled for good measure -- but my MPVolumeView slider is totally unresponsive when I try and drag it. It does move appropriately when I click the volume button.
myVolumeViewParentView.backgroundColor = UIColor.clear
let myVolumeView = MPVolumeView(frame: myVolumeViewParentView.bounds)
myVolumeViewParentView.isUserInteractionEnabled = true
myVolumeView.isUserInteractionEnabled = true
myVolumeViewParentView.addSubview(myVolumeView)
When I look in the view debugger, I don't see any other view in front of it.
Any ideas? I saw a posting about this a couple of years ago, but there was no answer. Here's hoping the second time's the charm ...
I had the same issue. What solved it for me was to give the volume view a height constraint if you don't explicitly set it's frame.
To test this do:
myVolumeView.clipsToBounds = true
If the volume view doesn't show than you know there's no frame. You than need to set it's frame or give it a height constraint.
HI, i set my uisegmentedcontrol's width manually, but when the width gets too small, the words becomes ...
Is that anyway that it won't behave in this way? Instead, i just want to show the text just like the picture shown below.
I'd suggest changing your design here and going for a different approach.
The design that you seem to want makes readability pretty much impossible.
Plus, what happens if I'm using your app and add another "Active Project". What happens if I have 10 active projects?
Take the fact that the UI does not work as a sign that you are using the wrong UI for the problem you are trying solve.
I'd suggest possibly just have the current project title here with a button to maybe present a list of projects to switch to... or something.
The text has been truncated. If you want it to fit your segment, you need to update the segment control size based on the text length. If you just want to get rid of truncation, you can use the following snippet. However, it's not recommended, as later Apple might change the UISegmentControl hierarchy.
for item in segmentedControl.subviews {
for subview in item.subviews {
if subview.isKind(of: UILabel.self) {
let _label = subview as! UILabel
_label.numberOfLines = 0
_label.lineBreakMode = .byWordWrapping
}
}
}
I have a UITableView with cells based on an XIB with UIStackViews containing UILabels, in it. Labels have the texts A,B,C,D
(look at screenshot).
When I enable VoiceOver or use the Accesibility Inspector, It automaticallly groups the labels in a cell, reading all the texts on the labels - that's fine.
But I want it to change the order that it reads the sub-labels, when the superview is focused, so it's read like A,B,C,D - currently it's A,C,B,D.
I've tried a lot of things with no luck, like overriding the accesibilityElements, shouldGroup... and isAccessiblityElement. Every time I change something, i get either:
No change
Empty accesiblity label for the cell
The single labels become selectable (which is not what I wanted)
Any tips on how to fix this one? I guess I can't be the only one in the world with this problem, but apparently it's hard to find any info on recent iOS versions. I use 10.3 and Swift 3...
I gave up on the idea of profiting form iOS own concatenation of the subviews' accesibility labels and made a piece of code to handle it myself.
extension UIView {
open func updateCombinedAccessibilityLabel (elements: [AnyObject]) {
let accLabels: [String] = elements.map { $0.accessibilityLabel ?? "" }
accessibilityLabel = accLabels.joined(separator: ", ")
}
}
Then, when I set the labels' text values, I tell the view how to arrange the subviews' accesiblity order:
view.updateCombinedAccessibilityLabel(elements: [view.titleLabel, view.subtitleLabel, view.detailsLabel])