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.