New Features
Features Introduced in iOS 17
Apple added a lot of new accessibility features to iOS 17 that make it easier for you to enhance the user experience for your app’s VoiceOver users.
Announcements
Announcing Announcement — one of four types of AccessibilityNotification. The others are LayoutChanged, ScreenChanged and PageScrolled. Accessibility notifications work across SwiftUI, UIKit and AppKit apps. Creating one can be as simple as passing a string:
Button(action: {
AccessibilityNotification.Announcement("Loading Photos View")
.post()
}) { Text("Photos") }
You can also set the priority for an announcement. If users need to hear it, give it a high priority to make VoiceOver speak it before any other speech. Give it a low priority if it doesn’t matter if VoiceOver doesn’t speak it in time.
- High priority announcements can interrupt other speech and cannot be interrupted once started.
- Default priority announcements can interrupt existing speech but are interruptible if a new speech utterance is started.
- Low priority announcements are queued and spoken when other speech utterances have completed, if no new announcements have started.
You’ll see this code again in the demo video:
var scoreAnnouncement: AttributedString {
var scoreString = AttributedString("You scored \(score) points on this color.")
scoreString.accessibilitySpeechAnnouncementPriority = .high
return scoreString
}
Zoom Actions
Most of the actions you take for granted are difficult or impossible to do when VoiceOver is on. Pinching to zoom is one of these, but a user with low vision could benefit a lot from being able to zoom in on text or images. And now they can, with the accessibilityZoomAction modifier:
struct ZoomingImageView: View {
@State private var zoomValue = 1.0
@State var imageName: String?
var body: some View {
Image(imageName ?? "")
.scaleEffect(zoomValue)
.accessibilityZoomAction { action in
let zoomQuantity = "\(Int(zoomValue)) x zoom"
switch action.direction {
case .zoomIn:
zoomValue += 1.0
AccessibilityNotification.Announcement(zoomQuantity).post()
case .zoomOut:
zoomValue -= 1.0
AccessibilityNotification.Announcement(zoomQuantity).post()
}
}
}
}
Direct Touch
Many UI controls are clumsy to use when VoiceOver is on — you’ve already experienced how much harder it is to move the color sliders in RGBullsEye. Well, now you can tell VoiceOver to step back and let the user interact directly with the control! It’s as easy as this:
Slider(value: $value)
.accessibilityDirectTouch(options: .silentOnTouch)
You add the accessibilityDirectTouch modifier to the control. There are two options:
-
silentOnTouchtells VoiceOver to keep quiet when the user touches the direct touch area, so it doesn’t interfere with the app’s audio. -
requiresActivationmeans direct touch won’t happen until VoiceOver activates the element.
isToggle Trait
VoiceOver recognizes standard traits like isButton — it tells the user it’s a button and knows it can be activated. Now, suppose the button actually behaves like a toggle? You’d like VoiceOver to convey this information to the user, and now you can because iOS 17 added the isToggle trait:
Button(action: {
object.isFavorite.toggle()
}) {
Image(systemName: object.isFavorite ? "star.fill" : "star")
}
.accessibilityAddTraits(.isToggle)
Adding this trait causes VoiceOver to describe it as “switch button. Double-tap to toggle setting.”
Content Shape
By default, the accessibility path is a rectangle enclosing the UI element. Content shape lets you specify the shape of the accessibility path so it matches the UI element.
struct ImageView: View {
var body: some View {
Image("circle-red")
.resizable()
.frame(width: 200, height: 200)
.accessibilityLabel("Red")
.contentShape(.accessibility, Circle())
}
}