-
Build accessible apps with SwiftUI and UIKit
Discover how advancements in UI frameworks make it easier to build rich, accessible experiences. Find out how technologies like VoiceOver can better interact with your app's interface through accessibility traits and actions. We'll share the latest updates to SwiftUI that help you refine your accessibility experience and show you how to keep accessibility information up-to-date in your UIKit apps.
Capítulos
- 0:00 - Welcome
- 1:30 - Explore the toggle trait
- 2:46 - Discover multi-platform accessibility announcements
- 3:58 - Assign priority to announcements
- 6:36 - Meet the zoom action
- 8:00 - Refine VoiceOver direct touch experiences
- 11:08 - Customize accessibility content shapes in SwiftUI
- 12:48 - Keep accessibility attributes up-to-date in UIKit using block-based setters
Recursos
Vídeos relacionados
WWDC23
-
Buscar neste vídeo...
-
-
1:54 - Add the accessibility toggle trait
import SwiftUI struct FilterButton: View { @State var filter: Bool = false var body: some View { Button(action: { filter.toggle() }) { Text("Filter") } .background(filter ? darkGreen : lightGreen) .accessibilityAddTraits(.isToggle) } } -
2:31 - Add the accessibility toggle trait with UIKit
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let filterButton = UIButton(type: .custom) setupButtonView() filterButton.accessibilityTraits = [.toggleButton] view.addSubview(filterButton) } } -
3:43 - Post an accessibility notification
import SwiftUI struct ContentView: View { var body: some View { NavigationView { PhotoFilterView .toolbar { Button(action: { AccessibilityNotification.Announcement("Loading Photos View") .post() }) { Text("Photos") } } } } } -
5:13 - Assign announcement priority
import SwiftUI struct ZoomingImageView: View { var defaultPriorityAnnouncement = AttributedString("Opening Camera") var lowPriorityAnnouncement: AttributedString { var lowPriorityString = AttributedString("Camera Loading") lowPriorityString.accessibilitySpeechAnnouncementPriority = .low return lowPriorityString } var highPriorityAnnouncement: AttributedString { var highPriorityString = AttributedString("Camera Active") highPriorityString.accessibilitySpeechAnnouncementPriority = .high return highPriorityString } // ... } -
5:46 - Post announcements with priority set
import SwiftUI struct CameraButton: View { // ... var body: some View { Button(action: { // Open Camera Code AccessibilityNotification.Announcement(defaultPriorityAnnouncement).post() // Camera Loading Code AccessibilityNotification.Announcement(lowPriorityAnnouncement).post() // Camera Loaded Code AccessibilityNotification.Announcement(highPriorityAnnouncement).post() }) { Image("Camera") } } } } -
6:15 - Assign announcement priority with UIKit
class ViewController: UIViewController { let defaultAnnouncement = NSAttributedString(string: "Opening Camera", attributes: [NSAttributedString.Key.UIAccessibilitySpeechAttributeAnnouncementPriority: UIAccessibilityPriority.default] ) let lowPriorityAnnouncement = NSAttributedString(string: "Camera Loading", attributes: [NSAttributedString.Key.UIAccessibilitySpeechAttributeAnnouncementPriority: UIAccessibilityPriority.low] ) let highPriorityAnnouncement = NSAttributedString(string: "Camera Active", attributes: [NSAttributedString.Key.UIAccessibilitySpeechAttributeAnnouncementPriority: UIAccessibilityPriority.high] ) // ... } -
6:56 - Add the accessibility zoom action
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() } } } } -
7:18 - Add the accessibility zoom action with UIKit
import UIKit class ViewController: UIViewController { let zoomView = ZoomingImageView(frame: .zero) let imageView = UIImageView(image: UIImage(named: "tree")) override func viewDidLoad() { super.viewDidLoad() zoomView.isAccessibilityElement = true zoomView.accessibilityLabel = "Zooming Image View" zoomView.accessibilityTraits = [.image, .supportsZoom] zoomView.addSubview(imageView) view.addSubview(zoomView) } } -
7:43 - Respond to accessibility zoom actions with UIKit
import UIKit class ZoomingImageView: UIScrollView { override func accessibilityZoomIn(at point: CGPoint) -> Bool { zoomScale += 1.0 let zoomQuantity = "\(Int(zoomValue)) x zoom" UIAccessibility.post(notification: .announcement, argument: zoomQuantity) return true } override func accessibilityZoomOut(at point: CGPoint) -> Bool { zoomScale -= 1.0 let zoomQuantity = "\(Int(zoomValue)) x zoom" UIAccessibility.post(notification: .announcement, argument: zoomQuantity) return true } } -
10:10 - Use accessibility direct touch options
import SwiftUI struct KeyboardKeyView: View { var soundFile: String var body: some View { Rectangle() .fill(.white) .frame(width: 35, height: 80) .onTapGesture(count: 1) { playSound(sound: soundFile, type: "mp3") } .accessibilityDirectTouch(options: .silentOnTouch) } } -
10:46 - Use accessibility direct touch options with UIKit
import UIKit class ViewController: UIViewController { let waveformButton = UIButton(type: .custom) override func viewDidLoad() { super.viewDidLoad() waveformButton.accessibilityTraits = .allowsDirectInteraction waveformButton.accessibilityDirectTouchOptions = .silentOnTouch waveformButton.addTarget(self, action: #selector(playTone), for: .touchUpInside) view.addSubview(waveformButton) } } -
12:21 - Set the accessibility content shape
import SwiftUI struct ImageView: View { var body: some View { Image("circle-red") .resizable() .frame(width: 200, height: 200) .accessibilityLabel("Red") .contentShape(.accessibility, Circle()) } } -
13:35 - Update accessibility values using block-based setters with UIKit
import UIKit class ViewController: UIViewController { var isFiltered = false override func viewDidLoad() { super.viewDidLoad() // Set up views zoomView.accessibilityValueBlock = { [weak self] in guard let self else { return nil } return isFiltered ? "Filtered" : "Not Filtered" } } }
-