I have a button on screen. When a user taps that button, my app opens a modal (view). When the user then closes that view, the focus of accessibility VoiceOver goes to the top of the screen. In UIKit, I can use UIAccessibility.post(notification:argument:) passing .layoutChanged into the notification
parameter and passing a reference to one of my views into the argument
parameter. How can I achieve this same behaviour in SwiftUI?
How I managed this was using a .accessibilityHidden
wrapper on the very top level of the parent view and then used a @State variable as the value to pass into accessibilityHidden
. This way the parent view is ignored while the modal is showing. And then reintroduced into the view once the modal is closed again.
struct MainView: View {
@State var showingModal = false
var body: some View {
VStack {
Button(action: {
showingModal = true
}, label: {
Text("Open Modal")
})
.fullScreenCover(isPresented: $showingModal, onDismiss: {
print("Focus coming back to main view")
} content: {
Modal()
})
}
.accessibilityHidden(self.showingModal)
}
}
struct Modal: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("Focus will move here")
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Close Modal to Refocus Back")
}
}
}
}
You can also chain multiple modal / alerts as long at you have @State values to handle the changes to them so the focus moves properly
.accessibilityHidden(self.showingModel1 || self.showingModel2 || self.showingAlert1 || self.showingAlert2)
I know this question is really old, but I literally just was handling this and thought if someone else stumbled onto this question there would be an answer here.
From iOS 15 we can(and I even think should) use @AccessibilityFocusState
, which allows to control accessibility focus programmatically.
You can define @AccessibilityFocusState
property as a simple boolean and turn it to true once you dismissed modal. With the help of accessibilityFocused(_:)
SwiftUI takes care to move focus on desired element, example with button and sheet would be:
struct ContentView: View {
@AccessibilityFocusState private var accessibilityFocus: Bool
@State private var isSheetPresented: Bool = false
var body: some View {
NavigationView {
VStack {
Spacer()
Button("Open Sheet") {
isSheetPresented.toggle()
}
.accessibilityFocused($accessibilityFocus)
Spacer()
}
.navigationTitle("Title")
.sheet(isPresented: $isSheetPresented, content: {
Button("Dismiss") {
accessibilityFocus = true
isSheetPresented.toggle()
}
})
}
}
}
Approach for advanced accessibility focus handling
In case if you want to move focus dynamically on different views depending on pressed button or whatever, you would create an enum and use it as a value of @AccessibilityFocusState
like I did in the example below by using accessibilityFocused(_:equals:)
:
fileprivate enum FocusType: Int, Hashable {
case subtitle
case button
}
struct ContentView: View {
@AccessibilityFocusState private var accessibilityFocus: FocusType?
@State private var isSheetPresented: Bool = false
var body: some View {
NavigationView {
VStack {
Text("Subtitle")
.font(.title2)
.accessibilityFocused($accessibilityFocus, equals: .subtitle)
Spacer()
Button("Open Sheet") {
isSheetPresented.toggle()
}
.accessibilityFocused($accessibilityFocus, equals: .button)
Spacer()
Divider()
VStack {
Button("Subtitle") { accessibilityFocus = .subtitle }
Button("Button") { accessibilityFocus = .button }
}
}
.navigationTitle("Title")
.sheet(isPresented: $isSheetPresented, content: {
SheetContent(focusType: _accessibilityFocus, isSheetPresented: $isSheetPresented)
})
}
}
}
fileprivate struct SheetContent: View {
@AccessibilityFocusState var focusType: FocusType?
@Binding var isSheetPresented: Bool
var body: some View {
VStack {
Button("Subtitle") { changeState(.subtitle)}
Button("Button") { changeState(.button)}
}
}
func changeState(_ focusType: FocusType) {
self.focusType = focusType
self.isSheetPresented.toggle()
}
}
I added as an example two buttons to switch the state on the same view and also I passed @AccessibilityFocusState
to sheet's content where you can dismiss the sheet and move accessibility focus depending on which button you pressed. Note that you need to pass your @AccessibilityFocusState
with accessed synthesized property.
© 2022 - 2025 — McMap. All rights reserved.