Accessibility focus order not working as expected [iOS]

6.2k Views Asked by At

OVERVIEW I'm having trouble getting correct focus order (Accessibility in iOS). It seems like becomeFirstResponder() overwrites my focus order I have specified in the array and causes Voice Over Accessibility functionality to read wrong Accessibility Label first.

DETAILS: I have a View Controller with containerView. Inside I have UIView of my progress bar image and text input field (placeholder). Both elements have isAccessibilityElement = true attributes and they have been added to my focus order array. However upon screen launch, focus order goes to the input field instead of progress bar UIView.

After extended testing I've noticed that this issue is no longer replicable if I remove below line of code.

 otpNumberTextField.becomeFirstResponder()

But this is not a solution. I need cursor in the textfield but Voice Over functionality to read my Progress Bar Accessibility Label first. How to fix it?

SPECIFIC SCENARIO I've noticed this issue occurs only when I have VC with a last active focus on a Textfield and then transition to the next VC (with a Textfield and a Progress Bar).

Bug is not replicable when I have VC with a last active focus on the Button and then transition to the next VC (with a Textfield and a Progress Bar).

CODE SNIPPET

import UIKit

class MyViewController: UIViewController, UITextFieldDelegate {

    var otpNumberTextField = UITextField()
    var progressMainDot = UIImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view.
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
        
        setupView()
        setupBinding()
    }

    override func viewWillAppear(_ animated: Bool) {
        setupView()
        textFieldDidChange(otpNumberTextField)
    }
    
    func setupView(){
        let containerView = UIView()
        containerView.backgroundColor = UIColor.init(named: ColourUtility.BackgroundColour)
        view.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
        containerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
        containerView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
        containerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
        
        //Progress Bar
        let progressBarView = UIView()
        containerView.addSubview(progressBarView)
        progressBarView.isAccessibilityElement = true
        progressBarView.accessibilityLabel = "my accessibility label"
        progressBarView.translatesAutoresizingMaskIntoConstraints = false
        
        progressMainDot.image = UIImage(named:ImageUtility.progressMain)
        progressMainDot.contentMode = .scaleAspectFit
        progressBarView.addSubview(progressMainDot)
        
        //Text Field
        otpNumberTextField.borderStyle = UITextField.BorderStyle.none
        otpNumberTextField.font = UIFontMetrics.default.scaledFont(for: FontUtility.inputLargeTextFieldStyle)
        otpNumberTextField.adjustsFontForContentSizeCategory = true
        otpNumberTextField.isAccessibilityElement = true
        otpNumberTextField.accessibilityLabel = AccessibilityUtility.enterVerificationCode
        otpNumberTextField.placeholder = StringUtility.otpPlaceholder
        otpNumberTextField.textColor = UIColor.init(named: ColourUtility.TextfieldColour)
        otpNumberTextField.textAlignment = .center
        otpNumberTextField.keyboardType = .numberPad
        otpNumberTextField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged)
        containerView.addSubview(otpNumberTextField)
        otpNumberTextField.becomeFirstResponder()
        
        //Accessibility - focus order
        view.accessibilityElements = [progressBarView, otpNumberTextField]
    }
      
    //... more code goes here ...

}
1

There are 1 best solutions below

1
On

If you have already set accessibilityElements, then voice over should respects that order but calling becomeFirstResponder() changes the focus to that text field.

You can try below code, which notifies voice over for shifting the focus to new element due to layout changes.

UIAccessibility.post(notification: .layoutChanged, argument: progressBarView)

So now your modified method should be like below:

func setupView(){
    .....
    otpNumberTextField.becomeFirstResponder()

    //Accessibility - focus order
    view.accessibilityElements = [progressBarView, otpNumberTextField]
            
    UIAccessibility.post(notification: .layoutChanged, argument: progressBarView)

    .....
}