Overview:
I'm having difficulty understanding how to properly programmatically shift accessibility focus in a SwiftUI view compatible with IOS 13+.
This means using the newer SwiftUI focus management API available in IOS 15+ is not possible unfortunately.
Most of the suggestions I've seen are similar to the following post:
iOS change accessibility focus.
where it suggests to change focus by using UIAccessibility.post(notification: .screenChanged/layoutChanged) argument: View)
However, my attempts have been unsuccessful.
I have been able to successfully use UIAccessibility.post(notification: .announcement) argument: String) in the same way as the code below and it does announce the sting as expected. It takes considerably longer than the asyncAfter timer (Maybe 8 seconds), but it seemed to indicate the post() call is kind of working.
Code Example:
A custom expand/collapse with a chevron up or down depending on if it is expanded or collapsed.
I want to programmatically be able to control where focus goes when it is expanded. For instance, I may want to be able to have VoiceOver focus on 'Menu Item 3' in the expanded menu on open.
I was thinking that something close to the code below might work, but I have a lack of understanding of how the argument view in the post is working and not sure if there is a way to use a SwiftUI view or if I need to create a UIKit view and use UIViewRepresentable or wrap it using UIHostingController?
Current Behavior
When the menu is expanded, VoiceOver moves to the first item. When the menu is collapsed, the VoiceOver focus shifts to the last text view.
Desired Behavior
I would like to be able to definitively decide where VoiceOver focus goes when I expand a menu and be able to more focus to a specific view when a view appears
I've also considered other approaches with the tools available in SwiftUI/IOS 13+. For example, I've played with accessibility(sortPriority:) and accessibilityElement(children: .combine/.contain/.ignore) to achieve the desired result, but those efforts have proved unsuccessful as well.
Code Search:
The following GitHub search currently produced 10 and 13 results respectively:
And of those results, the ones used within a SwiftUI view have use an argument of 'nil' (supposedly shifting the focus to the first item on the screen)
This makes me wonder if I'm really off-track.
Any guidance/insight in to how best approach this issue is greatly appreciated!
SwiftUI View:
struct ExpandingMenu: View {
@State var expanded: Bool = false
var body: some View {
ScrollView {
VStack(spacing: 12) {
Text("Some views above the menu")
.accessibility(addTraits: .isStaticText)
.accessibility(identifier: "Some view identifier")
if self.expanded {
VStack(spacing: 12) {
Text("Menu Item 1")
Text("Menu Item 2")
Text("Menu Item 3")
.onAppear(perform: {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
UIAccessibility.post(notification: .screenChanged, argument: self)
}
})
Image(systemName: "chevron.up")
FullDivider()
Spacer()
}
.accessibility(addTraits: .isButton)
.accessibility(identifier: "Menu Expanded")
.accessibility(removeTraits: .isStaticText)
.onTapGesture {
self.expanded.toggle()
}
} else {
VStack(spacing: 12) {
FullDivider()
Image(systemName: "chevron.down")
FullDivider()
Spacer()
}
.accessibility(addTraits: .isButton)
.accessibility(identifier: "Menu Collapsed")
.accessibility(removeTraits: .isStaticText)
.onTapGesture {
self.expanded.toggle()
}
}
Text("Some views below the menu")
.accessibility(addTraits: .isStaticText)
.accessibility(identifier: "Some Text 3")
}
}
}