☰
swiftui-expert-skill - accessibility-patterns
2026/10/15 21:09:46 网站建设 项目流程

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 分组时确保导航流程合乎逻辑

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询