SwiftUI Accessibility Focus

I've got a page where there is a text field for the user to enter a value. If they enter an incorrect value it shows an error message (text and a systemImage, not an alert) and when they enter a correct value it shows a continue button.

Once the user has entered anything and dismissed the keyboard I want voiceover to automatically focus on either the error message or the continue button.

The only way I've sort of been able to make this work is by using accessibilitySortPriority with a ternary conditional that says if the field is not empty and the keyboard is not focused then make this the highest sort priority.

Sure, that works (kind of) because it will in fact focus on the correct element once the keyboard is dismissed, but if the user changes their entry in the text field, then dismisses the keyboard, it stays on the text field instead of focusing once again on the error message or continue button.

Also, changing the sort priority of the entire page is a bit problematic because everything is out of order if the user needs to swipe to move around the page elements properly.

I've also looked into AccessibilityFocusState because the docs say that the element will gain VO focus when the bool is true and when you move away from that element the bool becomes false. However, nothing I've tried with this seems to do anything at all.

What I want to have happen is as follows:

  1. User loads the page
  2. VO reads the elements from top to bottom
  3. User taps to enter a value in the text field
  4. User dismisses the keyboard
  5. Either the error msg or continue button appears on screen and VO immediately focuses on it
  6. User taps to change their entry in the text field
  7. User dismisses the keyboard
  8. Either the error msg or continue button appears on screen and VO immediately focuses on it
Answered by kittonian in 707697022

If anyone is interested, the answer is AccessibilityFocusState, but you need to use it in a very specific way.

  1. Create an enum with the various focus choices on your page
  2. Create @AccessibilityFocusState private var accessFocus: EnumName?
  3. Use .accessibilityFocused($accessFocus, equals: .yourEnumCase) as a modifier to a view to which you wish to focus
  4. Your actions can now change the value of accessFocus to the field in which you want voiceover to focus by:
	DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
		accessFocus = .yourEnumCase
	}

Not sure what @FocusState has to do with the question, but just in case I tried it anyway. I am not trying to move focus between two text fields. I am trying to change the voiceover focus to the error msg/continue button when it appears after dismissing the keyboard and setting a variable's value.

My guess is that @AccessibilityFocusState is the right way to do this, but even manually setting that variable to true when tapping the keyboard dismiss button will not actually set the variable to true.

struct TestView: View {

@AccessibilityFocusState var accessFocus: Bool
@FocusState var isFocused: Bool
@State private var testVar = ""

    var body: some View {

        VStack {

            Text("Welcome")

            VStack {
                TextField("", text: $testVar)
                    .focused($isFocused)
            }

            VStack {
                Text("Error Message")
             }
             .accessibilityFocused($accessFocus)
        }
        .toolbar {
	    	ToolbarItemGroup(placement: .keyboard) {
		    	HStack {
			    	Spacer()
				    Button {
					    isFocused = false
					    accessFocus = true
				    } label: {
					    Image(systemName: "keyboard.chevron.compact.down")
			      }
		    } 
	    }
	  }
  }
}
Accepted Answer

If anyone is interested, the answer is AccessibilityFocusState, but you need to use it in a very specific way.

  1. Create an enum with the various focus choices on your page
  2. Create @AccessibilityFocusState private var accessFocus: EnumName?
  3. Use .accessibilityFocused($accessFocus, equals: .yourEnumCase) as a modifier to a view to which you wish to focus
  4. Your actions can now change the value of accessFocus to the field in which you want voiceover to focus by:
	DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
		accessFocus = .yourEnumCase
	}
SwiftUI Accessibility Focus
 
 
Q