rzulkoski
rzulkoski

Reputation: 63

VoiceOver reads accessibility label twice when focusing non-UILabel titleView

I'm encountering a strange issue with VoiceOver.

Goals:

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

Answers (1)

XLE_22
XLE_22

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 :

  • 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.

Upvotes: 1

Related Questions