Reputation: 63
I'm encountering a strange issue with VoiceOver.
Goals:
UIStackView
containing multiple UILabel
's as my navigationItem.titleView
.accessibilityLabel
to an appropriate value.UIAccessibility.post(notification: .screenChanged, argument: navigationItem.titleView)
inside viewDidAppear(animated:)
.Expected Result:
Actual Result:
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
Upvotes: 2
Views: 7427
Reputation: 5671
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 :
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 :
UIView
whose frame is the same as the stackview's.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.
Upvotes: 1