问题
I'm encountering a strange issue with VoiceOver.
Goals:
- Set a
UIStackView
containing multipleUILabel
's as mynavigationItem.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)
insideviewDidAppear(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
回答1:
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.
来源:https://stackoverflow.com/questions/54954861/voiceover-reads-accessibility-label-twice-when-focusing-non-uilabel-titleview