SwiftUI 无障碍模式参考
目录
- 核心原则
- 动态类型与 @ScaledMetric
- 无障碍特征
- 装饰性图像
- 元素分组
- 自定义控件
- 总结清单
核心原则
可点击元素优先使用Button而非onTapGesture。Button免费提供 VoiceOver 支持、焦点处理和正确的特征。
动态类型与 @ScaledMetric
系统文本样式会自动随动态类型缩放。当内置样式适合你的 UI 时,优先使用诸如.largeTitle、.title、.title2、.title3、.headline、.subheadline、.body、.callout、.footnote、.caption和.caption2等样式:
VStack(alignment:.leading){Text("Inbox").font(.title2)Text("3 unread messages").font(.body)Text("Updated just now").font(.caption)}对于自定义字体,使用支持动态类型的字体初始化器,使文本仍然遵循用户偏好的内容尺寸:
VStack(alignment:.leading){Text("Article").font(.custom("SourceSerif4-Semibold",size:28,relativeTo:.title2))Text("Body copy").font(.custom("SourceSerif4-Regular",size:17))}Font.custom(_:size:relativeTo:)让你匹配特定的文本样式。Font.custom(_:size:)则相对于 body 样式缩放。对于应响应动态类型的主要内容,避免使用固定尺寸的自定义字体。
对于非文本的数值(如内边距、间距和图像尺寸),使用@ScaledMetric:
structProfileHeader:View{@ScaledMetricprivatevaravatarSize=60.0@ScaledMetricprivatevarspacing=12.0varbody:someView{HStack(spacing:spacing){Image("avatar").resizable().frame(width:avatarSize,height:avatarSize)Text("Username")}}}当数值应跟随特定动态类型样式时,指定relativeTo文本样式,包括应保持与附近文本成比例的图像或图标:
structStatusRow:View{@ScaledMetric(relativeTo:.body)privatevariconSize=18.0varbody:someView{HStack(spacing:8){Image(systemName:"checkmark.circle.fill").font(.system(size:iconSize))Text("Synced").font(.custom("AvenirNext-Regular",size:17,relativeTo:.body))}}}无障碍特征
使用accessibilityAddTraits和accessibilityRemoveTraits处理由状态驱动的特征:
Text(item.title).accessibilityAddTraits(item.isSelected?[.isSelected,.isButton]:.isButton)对非交互元素使用.disabled(true)让 VoiceOver 播报"Dimmed(变暗)"。
装饰性图像
当资源图像纯粹是视觉性的、不应出现在无障碍树中时,使用Image(decorative:bundle:)。
Image(decorative:"confetti")这适用于背景、装饰元素,以及不会在附近文本之外添加含义的图标。
如果图像传达了信息,请保持其可访问并提供清晰的标签:
Image("receipt").accessibilityLabel("Receipt")对于非资源图像(如 SF Symbols),改用accessibilityHidden(true)隐藏装饰性内容:
Image(systemName:"sparkles").accessibilityHidden(true)元素分组
.combine – 自动合并子标签
HStack{Image(systemName:"star.fill")Text("Favorites")Text("(\(count))")}.accessibilityElement(children:.combine)VoiceOver 将所有子标签作为一个元素朗读,用逗号分隔。
.ignore – 为容器手动设置标签
HStack{Text(item.name)Spacer()Text(item.price)}.accessibilityElement(children:.ignore).accessibilityLabel("\(item.name),\(item.price)").contain – 语义分组
HStack{ForEach(tabs){tabinTabButton(tab:tab)}}.accessibilityElement(children:.contain).accessibilityLabel("Tab bar")当焦点进入/离开时,VoiceOver 会播报容器名称。
自定义控件
可调节控件(递增/递减)
PageControl(selectedIndex:$selectedIndex,pageCount:pageCount).accessibilityElement().accessibilityValue("Page\(selectedIndex+1)of\(pageCount)").accessibilityAdjustableAction{directioninswitchdirection{case.increment:guardselectedIndex<pageCount-1else{break}selectedIndex+=1case.decrement:guardselectedIndex>0else{break}selectedIndex-=1@unknowndefault:break}}将自定义视图表示为原生控件
当自定义视图在无障碍方面应表现得像原生控件时:
HStack{Text(label)Toggle("",isOn:$isOn)}.accessibilityRepresentation{Toggle(label,isOn:$isOn)}标签-内容配对
@NamespaceprivatevarnsHStack{Text("Volume").accessibilityLabeledPair(role:.label,id:"volume",in:ns)Slider(value:$volume).accessibilityLabeledPair(role:.content,id:"volume",in:ns)}总结清单
- 可点击元素使用
Button而非onTapGesture - 文本使用内置文本样式或支持动态类型的自定义字体
- 应随动态类型缩放的自定义数值使用
@ScaledMetric - 将纯装饰性图像标记为装饰性的或对无障碍隐藏
- 使用
accessibilityElement(children:)对相关元素分组 - 默认标签不清楚时提供
accessibilityLabel - 自定义控件使用
accessibilityRepresentation - 递增/递减控件使用
accessibilityAdjustableAction - 使用 VoiceOver 分组时确保导航流程合乎逻辑