mcpField
A
axiom-accessibility-diag

axiom-accessibility-diag

github.com/charleswiltgen设计与 UI1.2k199 次安装今天更新
代码仓库

关于 axiom-accessibility-diag

帮助 iOS 和 macOS 应用开发者诊断并修复无障碍问题的技能。内容涵盖 VoiceOver、动态字体、对比度、触控目标、键盘导航、减少动态效果和辅助访问。

主要功能

  1. 01VoiceOver 标签与导航检查
  2. 02动态字体和文本缩放
  3. 03颜色对比度指导
  4. 04触控目标尺寸建议
  5. 05键盘与减少动态效果支持
  6. 06辅助访问配置

适用场景

  1. 01为应用界面准备无障碍审查
  2. 02修复 VoiceOver 标签和阅读顺序
  3. 03使用大字号和对比度设置测试布局
  4. 04添加键盘或减少动态效果支持

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-accessibility-diag

安装后在 Claude Code 中输入/axiom-accessibility-diag确认已加载。

详细介绍

无障碍诊断

概述

系统地诊断和修复 iOS/macOS 应用的无障碍问题。涵盖最常见的 7 类无障碍问题,这些问题会导致 App Store 拒审和用户投诉。

核心原则 无障碍支持并非可有可无。iOS 应用必须支持 VoiceOver、动态字体和足够的颜色对比度,才能通过 App Store 审核。残障用户依赖这些功能。

何时使用此技能

  • 修复 VoiceOver 导航问题(缺少标签、元素顺序错误)
  • 支持动态字体(为视力障碍用户缩放文本)
  • 满足颜色对比度要求(WCAG AA/AAA)
  • 修复触控目标尺寸违规(< 44x44pt)
  • 添加键盘导航(iPadOS/macOS)
  • 支持减少动态效果(前庭障碍)
  • 为 App Store 审核的无障碍要求做准备
  • 回复用户关于无障碍的投诉

7 类关键无障碍问题

1. VoiceOver 标签和提示(严重 - 导致 App Store 拒审)

问题 缺少或过于笼统的无障碍标签,会导致 VoiceOver 用户无法理解 UI 的用途。

WCAG 4.1.2 名称、角色、值(A 级)

常见违规
// ❌ WRONG - No label (VoiceOver says "Button")
Button(action: addToCart) {
  Image(systemName: "cart.badge.plus")
}

// ❌ WRONG - Generic label
.accessibilityLabel("Button")

// ❌ WRONG - Reads implementation details
.accessibilityLabel("cart.badge.plus") // VoiceOver: "cart dot badge dot plus"

// ✅ CORRECT - Descriptive label
Button(action: addToCart) {
  Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")

// ✅ CORRECT - With hint for complex actions
.accessibilityLabel("Add to cart")
.accessibilityHint("Double-tap to add this item to your shopping cart")
何时使用提示
  • 无法仅从标签看出操作的用途(“添加到购物车”很明确,无需提示)
  • 多步骤交互(“向右轻扫以确认,向左轻扫以取消”)
  • 状态变化(“轻点两下可开启或关闭通知”)
装饰性元素
// ✅ CORRECT - Hide decorative images from VoiceOver
Image("decorative-pattern")
  .accessibilityHidden(true)

// ✅ CORRECT - Combine multiple elements into one label
HStack {
  Image(systemName: "star.fill")
  Text("4.5")
  Text("(234 reviews)")
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Rating: 4.5 stars from 234 reviews")
测试
  • 启用 VoiceOver:按 Cmd+F5(模拟器),或连按三次侧边按钮(设备)
  • 导航:向右/向左轻扫,在元素之间移动
  • 聆听:VoiceOver 是否清楚地播报了用途?
  • 检查顺序:导航顺序是否与视觉布局一致?

2. 动态字体支持(高 - 用户体验)

问题 固定字体大小会让视力障碍用户无法阅读文本。

WCAG 1.4.4 调整文本大小(AA 级 - 支持 200% 缩放,且不丢失内容或功能)

常见违规
// ❌ WRONG - Fixed size, won't scale
Text("Price: $19.99")
  .font(.system(size: 17))

UILabel().font = UIFont.systemFont(ofSize: 17)

// ❌ WRONG - Custom font without scaling
Text("Headline")
  .font(Font.custom("CustomFont", size: 24))

// ✅ CORRECT - SwiftUI semantic styles (auto-scales)
Text("Price: $19.99")
  .font(.body)

Text("Headline")
  .font(.headline)

// ✅ CORRECT - UIKit semantic styles
label.font = UIFont.preferredFont(forTextStyle: .body)

// ✅ CORRECT - Custom font with scaling
let customFont = UIFont(name: "CustomFont", size: 24)!
label.font = UIFontMetrics.default.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
随动态字体缩放的自定义大小
// ❌ WRONG - Fixed size, won't scale
Text("Price: $19.99")
  .font(.system(size: 17))

// ⚠️ ACCEPTABLE - Custom font without scaling (accessibility violation)
Text("Headline")
  .font(Font.custom("CustomFont", size: 24))

// ✅ GOOD - Custom size that scales with Dynamic Type
Text("Large Title")
  .font(.system(size: 60).relativeTo(.largeTitle))

Text("Custom Headline")
  .font(.system(size: 24).relativeTo(.title2))

// ✅ BEST - Use semantic styles when possible
Text("Headline")
  .font(.headline)

relativeTo: 的工作方式

  • 基础大小:指定的精确字号(24pt、60pt 等)
  • 缩放依据:指定的文本样式(.title2、.largeTitle 等)
  • 结果:用户在“设置”中调大文本时,自定义字号也会按比例增大

示例

  • .title2 基础大小:约 22pt → 自定义大小:24pt(大 1.09 倍)
  • 用户将文本大小调至“特大”
  • .title2 增至约 28pt → 自定义字号增至约 30.5pt(保持 1.09 倍比例)

修复方案优先级(从优到次)

  1. 最佳:使用语义样式(.title、.body、.caption)
  2. 良好:必要时使用 .system(size:).relativeTo() 指定自定义字号
  3. 可接受:自定义字体搭配 .dynamicTypeSize() 修饰符
  4. 不可接受:始终不缩放的固定字号
SwiftUI 文本样式
  • .largeTitle - 34pt(在辅助功能字号下缩放至 44pt)
  • .title - 28pt
  • .title2 - 22pt
  • .title3 - 20pt
  • .headline - 17pt 半粗体
  • .body - 17pt(默认)
  • .callout - 16pt
  • .subheadline - 15pt
  • .footnote - 13pt
  • .caption - 12pt
  • .caption2 - 11pt
布局注意事项
// ❌ WRONG - Fixed frame breaks with large text
Text("Long product description...")
  .font(.body)
  .frame(height: 50) // Clips at large text sizes

// ✅ CORRECT - Flexible frame
Text("Long product description...")
  .font(.body)
  .lineLimit(nil) // Allow multiple lines
  .fixedSize(horizontal: false, vertical: true)

// ✅ CORRECT - Stack rearranges at large sizes
HStack {
  Text("Label:")
  Text("Value")
}
.dynamicTypeSize(...DynamicTypeSize.xxxLarge) // Limit maximum size if needed
测试
  1. Xcode 预览:覆盖环境设置

    .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
    
  2. 模拟器:设置 → 辅助功能 → 显示与文字大小 → 更大字体 → 拖至最大

  3. 设备:设置 → 辅助功能 → 显示与文字大小 → 更大字体

  4. 检查:文本是否仍然清晰易读?布局是否能自适应?是否有文本被裁切?


3. 颜色对比度(高 - 视力障碍)

问题 对比度低的文本会让视力障碍用户或处于强光环境中的用户难以阅读。

WCAG
  • 1.4.3 对比度(最低) — AA 级
    • 正文(< 18pt):对比度至少为 4.5:1
    • 大号文本(≥ 18pt 或 ≥ 14pt 粗体):对比度至少为 3:1
  • 1.4.6 对比度(增强) — AAA 级
    • 正文:对比度至少为 7:1
    • 大号文本:对比度至少为 4.5:1
常见违规
// ❌ WRONG - Low contrast (1.8:1 - fails WCAG)
Text("Warning")
  .foregroundColor(.yellow) // on white background

// ❌ WRONG - Low contrast in dark mode
Text("Info")
  .foregroundColor(.gray) // on black background

// ✅ CORRECT - High contrast (7:1+ passes AAA)
Text("Warning")
  .foregroundColor(.orange) // or .red

// ✅ CORRECT - System colors adapt to light/dark mode
Text("Info")
  .foregroundColor(.primary) // Black in light mode, white in dark

Text("Secondary")
  .foregroundColor(.secondary) // Automatic high contrast
不仅依靠颜色进行区分
// ❌ WRONG - Color alone indicates status
Circle()
  .fill(isAvailable ? .green : .red)

// ✅ CORRECT - Color + icon/text
HStack {
  Image(systemName: isAvailable ? "checkmark.circle.fill" : "xmark.circle.fill")
  Text(isAvailable ? "Available" : "Unavailable")
}
.foregroundColor(isAvailable ? .green : .red)

// ✅ CORRECT - Respect system preference
if UIAccessibility.shouldDifferentiateWithoutColor {
  // Use patterns, icons, or text instead of color alone
}
测试
  1. 使用 Color Contrast Analyzer 工具(免费下载)
  2. 为 UI 截图,测量文本与背景的对比度
  3. 检查浅色和深色模式
  4. 设置 → 辅助功能 → 显示与文字大小 → 增强对比度(开启此选项进行测试)
快速参考
  • 白色 (#FFFFFF) 上的黑色 (#000000):21:1 ✅ AAA
  • 白色上的深灰色 (#595959):7:1 ✅ AAA
  • 白色上的中灰色 (#767676):4.5:1 ✅ AA
  • 白色上的浅灰色 (#959595):2.8:1 ❌ 不符合要求

4. 触控目标尺寸(中等——运动障碍)

问题 小型点击目标对有运动障碍的用户来说难以触及,甚至无法触及。

WCAG 2.5.5 目标尺寸(AAA 级——最小 44x44pt)

Apple HIG 所有可点击元素的最小尺寸为 44x44pt

常见违规
// ❌ WRONG - Too small (24x24pt)
Button("×") {
  dismiss()
}
.frame(width: 24, height: 24)

// ❌ WRONG - Small icon without padding
Image(systemName: "heart")
  .font(.system(size: 16))
  .onTapGesture { }

// ✅ CORRECT - Minimum 44x44pt
Button("×") {
  dismiss()
}
.frame(minWidth: 44, minHeight: 44)

// ✅ CORRECT - Larger icon or padding
Image(systemName: "heart")
  .font(.system(size: 24))
  .frame(minWidth: 44, minHeight: 44)
  .contentShape(Rectangle()) // Expand tap area
  .onTapGesture { }

// ✅ CORRECT - UIKit button with edge insets
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
// Total size: icon size + insets ≥ 44x44pt
目标之间的间距
// ❌ WRONG - Targets too close (hard to tap accurately)
HStack(spacing: 4) {
  Button("Edit") { }
  Button("Delete") { }
}

// ✅ CORRECT - Adequate spacing (8pt minimum, 12pt better)
HStack(spacing: 12) {
  Button("Edit") { }
  Button("Delete") { }
}
测试
  1. 辅助功能检查器:Xcode → 打开开发者工具 → 辅助功能检查器
  2. 选择“Audit”选项卡 → 运行审核 → 检查“Small Text”和“Hit Region”警告
  3. 手动测试:用一根手指(而非触控笔)点击——能否稳定命中目标而不出错?

5. 键盘导航(中等——iPadOS/macOS)

问题 无法使用触控或鼠标的用户无法导航应用。

WCAG 2.1.1 键盘(A 级——所有功能均可通过键盘使用)

常见违规
// ❌ WRONG - Custom gesture without keyboard alternative
.onTapGesture {
  showDetails()
}
// No way to trigger with keyboard

// ✅ CORRECT - Button provides keyboard support automatically
Button("Show Details") {
  showDetails()
}
.keyboardShortcut("d", modifiers: .command) // Optional shortcut

// ✅ CORRECT - Custom control with focus support
struct CustomButton: View {
  @FocusState private var isFocused: Bool

  var body: some View {
    Text("Custom")
      .focusable()
      .focused($isFocused)
      .onKeyPress(.return) {
        action()
        return .handled
      }
  }
}
焦点管理
// ✅ CORRECT - Set initial focus
.focusSection() // Group related controls
.defaultFocus($focus, .constant(true)) // Set default

// ✅ CORRECT - Move focus after action
@FocusState private var focusedField: Field?

Button("Next") {
  focusedField = .next
}
测试(iPadOS/macOS)
  1. 将键盘连接到 iPad,或使用 Mac
  2. 按 Tab 键——焦点是否移至交互元素?
  3. 按空格键/Return 键——焦点所在元素是否被激活?
  4. 检查自定义控件是否有可见的焦点指示器
  5. 无需鼠标或触控,能否使用所有功能?

6. 减少动态效果支持(中等——前庭障碍)

问题 动画会让有前庭障碍的用户感到不适、恶心,或引发癫痫发作。

WCAG 2.3.3 交互产生的动画(AAA 级——可禁用动态动画)

常见违规
// ❌ WRONG - Always animates (can cause nausea)
.onAppear {
  withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
    scale = 1.0
  }
}

// ❌ WRONG - Parallax scrolling without opt-out
ScrollView {
  GeometryReader { geo in
    Image("hero")
      .offset(y: geo.frame(in: .global).minY * 0.5) // Parallax
  }
}

// ✅ CORRECT - Respect Reduce Motion preference
.onAppear {
  if UIAccessibility.isReduceMotionEnabled {
    scale = 1.0 // Instant
  } else {
    withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
      scale = 1.0
    }
  }
}

// ✅ CORRECT - Simpler animation or cross-fade
if UIAccessibility.isReduceMotionEnabled {
  // Cross-fade or instant change
  withAnimation(.linear(duration: 0.2)) {
    showView = true
  }
} else {
  // Complex spring animation
  withAnimation(.spring()) {
    showView = true
  }
}
SwiftUI 修饰符
// ✅ CORRECT - Automatic support
.animation(.spring(), value: isExpanded)
.transaction { transaction in
  if UIAccessibility.isReduceMotionEnabled {
    transaction.animation = nil // Disable animation
  }
}
测试
  1. 设置 → 辅助功能 → 动态效果 → 减少动态效果(开启)
  2. 浏览应用——动画是否减少或被禁用?
  3. 测试:转场、滚动效果、视差、粒子效果
  4. 视频自动播放也应遵循此偏好设置

7. 常见违规(高——App Store 审核)
未添加标签的图像
// ❌ WRONG - Informative image without label
Image("product-photo")

// ✅ CORRECT - Informative image with label
Image("product-photo")
  .accessibilityLabel("Red sneakers with white laces")

// ✅ CORRECT - Decorative image hidden
Image("background-pattern")
  .accessibilityHidden(true)
特征设置错误的按钮
// ❌ WRONG - Custom button without button trait
Text("Submit")
  .onTapGesture {
    submit()
  }
// VoiceOver announces as "Submit, text" not "Submit, button"

// ✅ CORRECT - Use Button for button-like controls
Button("Submit") {
  submit()
}
// VoiceOver announces as "Submit, button"

// ✅ CORRECT - Custom control with correct trait
Text("Submit")
  .accessibilityAddTraits(.isButton)
  .onTapGesture {
    submit()
  }
无法访问的自定义控件
// ❌ WRONG - Custom slider without accessibility support
struct CustomSlider: View {
  @Binding var value: Double

  var body: some View {
    // Drag gesture only, no VoiceOver support
    GeometryReader { geo in
      // ...
    }
    .gesture(DragGesture()...)
  }
}

// ✅ CORRECT - Custom slider with accessibility actions
struct CustomSlider: View {
  @Binding var value: Double

  var body: some View {
    GeometryReader { geo in
      // ...
    }
    .gesture(DragGesture()...)
    .accessibilityElement()
    .accessibilityLabel("Volume")
    .accessibilityValue("\(Int(value))%")
    .accessibilityAdjustableAction { direction in
      switch direction {
      case .increment:
        value = min(value + 10, 100)
      case .decrement:
        value = max(value - 10, 0)
      @unknown default:
        break
      }
    }
  }
}
缺少状态播报
// ❌ WRONG - State change without announcement
Button("Toggle") {
  isOn.toggle()
}

// ✅ CORRECT - State change with announcement
Button("Toggle") {
  isOn.toggle()
  UIAccessibility.post(
    notification: .announcement,
    argument: isOn ? "Enabled" : "Disabled"
  )
}

// ✅ CORRECT - Automatic state with accessibilityValue
Button("Toggle") {
  isOn.toggle()
}
.accessibilityValue(isOn ? "Enabled" : "Disabled")

8. 辅助访问支持(iOS 17+——认知障碍)

问题 应用在辅助访问模式下不可用或无法正常运行,导致依赖简化系统体验的认知障碍用户无法使用。

辅助访问是一种系统级模式(设置 > 辅助功能 > 辅助访问),会以大型控件、简化导航和更低的认知负担取代标准 iOS UI。未选择启用的应用会在此模式下对用户隐藏。

症状:应用未显示在辅助访问主屏幕上

应用未显示在辅助访问设置中的“优化的 App”下。


<key>UISupportsAssistiveAccess</key>
<true/>

这会使应用可用,并在辅助访问模式下以全屏方式启动。若没有此键,处于辅助访问模式的用户将完全无法使用应用。

症状:标准 UI 对辅助访问用户来说过于复杂

应用在辅助访问模式下启动,但显示完整的标准界面,让需要简化控件的用户难以应对。

// ✅ FIX - Provide a dedicated Assistive Access scene
@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView() // Standard UI
    }

    AssistiveAccess {
      AssistiveAccessContentView() // Simplified UI
    }
  }
}

AssistiveAccess 场景类型提供单独的入口点。当系统处于辅助访问模式时,会使用此场景,而不是标准的 WindowGroup。此场景中的原生 SwiftUI 控件会自动采用辅助访问视觉样式(大按钮、醒目的导航、网格/行布局)。

症状:应用已针对认知无障碍设计,但显示在缩小的框架中

如果你的应用本身就是为认知障碍用户(例如 AAC 应用)量身打造的,它可能会显示在缩小的框架中,而不是全屏显示。


<key>UISupportsFullScreenInAssistiveAccess</key>
<true/>

这会让你的应用保持与标准外观完全一致,并绕过辅助访问框架。

在运行时检测辅助访问
struct MyView: View {
  @Environment(\.accessibilityAssistiveAccessEnabled) var assistiveAccessEnabled

  var body: some View {
    if assistiveAccessEnabled {
      // Simplified content
    } else {
      // Standard content
    }
  }
}
UIKit 实现

对于 UIKit 应用,可在 UISceneConfiguration 中使用 .windowAssistiveAccessApplication 场景会话角色,将请求路由到专门负责辅助访问体验的场景代理。

辅助访问场景的设计原则
  • 提炼核心功能 — 保留一到两个必要功能,而非完整应用
  • 控件大且醒目 — 留出充足间距,不使用隐藏手势或限时交互
  • 多种呈现方式 — 将文本与图标搭配使用,并提供视觉替代方案
  • 逐步导航 — 提供清晰的返回按钮,并保持模式一致
  • 交互安全 — 移除不可逆操作;对破坏性操作进行确认
添加导航图标
NavigationStack {
  MyView()
    .navigationTitle("My Feature")
    .assistiveAccessNavigationIcon(systemImage: "star.fill")
}
测试
  1. 设备 — 在“设置”>“辅助功能”>“辅助访问”中启用辅助访问,确认应用显示在“优化应用”中,并测试完整的用户流程
  2. 辅助功能检查器 — 对辅助访问场景运行审核,检查标签、对比度和点击区域问题

辅助功能检查器工作流

1. 启动辅助功能检查器

Xcode → 打开开发者工具 → 辅助功能检查器

2. 选择目标
  • 下拉菜单:选择正在运行的模拟器或已连接的设备
  • 目标:选择你的应用
3. 检查模式
  • 点击“检查指针”按钮(十字准线图标)
  • 将指针悬停在 UI 元素上,即可查看:
    • 标签、值、提示、特征
    • 框架、路径
    • 可用操作
    • 父级/子级层级结构
4. 运行审核
  • 点击“审核”标签页
  • 点击“运行审核”按钮
  • 查看审核结果:
    • 对比度 — 颜色对比度问题
    • 点击区域 — 触摸目标尺寸问题
    • 文本裁切 — 动态字体导致的文本截断
    • 元素描述 — 缺少标签/提示
    • 特征 — 无障碍特征错误
5. 修复并重新测试
  • 点击每项结果查看详情
  • 在代码中修复
  • 重新运行审核以验证

VoiceOver 测试清单

启用 VoiceOver
  • 模拟器 Cmd+F5,或前往“设置”→“辅助功能”→“VoiceOver”
  • 设备 连按三次侧边按钮(如果已在“设置”中启用)
导航测试
  1. ☐ 向右/向左轻扫 — 按照合理顺序浏览 UI 元素
  2. ☐ 清晰播报每个元素的用途
  3. ☐ 不存在未标记的元素(装饰性元素除外)
  4. ☐ 标题导航正常(用两根手指向上/向下轻扫)
  5. ☐ 容器导航正常(用三根手指向左/向右轻扫)
交互测试
  1. ☐ 连按两次可激活按钮
  2. ☐ 向上/向下轻扫可调整滑块/选择器(使用 .accessibilityAdjustableAction)
  3. ☐ 自定义手势提供对应的 VoiceOver 操作
  4. ☐ 文本字段会播报键盘类型
  5. ☐ 会播报状态变化
内容测试
  1. ☐ 图像具有描述性标签,或设为隐藏
  2. ☐ 会播报错误消息
  3. ☐ 会播报加载状态
  4. ☐ 模态表单会播报其角色
  5. ☐ 自动播报警告

App Store 审核准备

必需的无障碍功能(iOS)
  1. VoiceOver 支持

    • 所有 UI 元素都必须有标签
    • 导航必须符合逻辑
    • 所有操作都必须可执行
  2. 动态字体

    • 文本必须支持从 -3 到 +12 的大小
    • 布局必须能够自适应,且不发生裁切
  3. 足够的对比度

    • 正常文本的对比度至少为 4.5:1
    • 大号文本(≥18pt)的对比度至少为 3:1
App Store Connect 元数据

提交时:

  1. 无障碍功能 → 选择应用支持的功能:

    • ☑ VoiceOver
    • ☑ 动态字体
    • ☑ 增强对比度
    • ☑ 减少动态效果(如支持)
  2. 测试备注:记录无障碍测试情况

    Accessibility Testing Completed:
    - VoiceOver: All screens tested with VoiceOver enabled
    - Dynamic Type: Tested at all size categories
    - Color Contrast: Verified 4.5:1 minimum contrast
    - Touch Targets: All buttons minimum 44x44pt
    - Reduce Motion: Animations respect user preference
    
常见的拒绝原因
  1. “App 无法通过 VoiceOver 完全正常使用”

    • 图片/按钮缺少标签
    • 自定义控件没有标签
    • 无法通过 VoiceOver 执行操作
  2. “文本在所有动态字体大小下都无法阅读”

    • 字体大小固定
    • 字体较大时文本被截断
    • 辅助功能字号下布局错乱
  3. “颜色对比度不足”

    • 文本未达到 4.5:1 的对比度
    • UI 元素未达到 3:1 的对比度
    • 仅通过颜色传递信息

设计评审压力:捍卫无障碍要求

问题

在设计评审压力下,你会遇到以下要求:

  • “那些 VoiceOver 标签让代码变得很乱——能不能不加?”
  • “动态字体会破坏我们精心设计的布局——把字体大小固定下来吧”
  • “高对比度要求破坏了我们的品牌美感”
  • “44pt 的触控目标太大了——缩小一些,外观会更简洁”

这些听起来像是合理的设计偏好。但它们违反了 App Store 要求,并将 15% 的用户拒之门外。 你的职责是依据 App Store 指南和法律要求进行辩护,而不是发表个人意见。

危险信号——设计师提出的违反无障碍要求的请求

如果你听到以下任何一项,请停下来并参考此技能:

  • ❌ “仅图标按钮不用加 VoiceOver 标签” – 会导致 App Store 拒绝(指南 2.5.1)
  • ❌ “紧凑设计就用固定的 14pt 字体” – 将视力障碍用户拒之门外
  • ❌ “3:1 的对比度够用了” – 不符合针对文本的 WCAG AA 标准(需要 4.5:1)
  • ❌ “为了外观简洁,把按钮做成 36x36pt” – 不符合触控目标要求(至少 44x44pt)
  • ❌ “在此屏幕上停用动态字体” – 有被 App Store 拒绝的风险
  • ❌ “只用颜色区分,不加标签(红色=错误,绿色=成功)” – 将色盲用户拒之门外(男性中占 8%)
如何专业地提出异议
步骤 1:展示指南
"I want to support this design direction, but let me show you Apple's App Store
Review Guideline 2.5.1:

'Apps should support accessibility features such as VoiceOver and Dynamic Type.
Failure to include sufficient accessibility features may result in rejection.'

Here's what we need for approval:
1. VoiceOver labels on all interactive elements
2. Dynamic Type support (can't lock font sizes)
3. 4.5:1 contrast ratio for text, 3:1 for UI
4. 44x44pt minimum touch targets

Let me show where our design currently falls short..."
步骤 2:说明风险

启用无障碍功能后打开 App:

  • VoiceOver(Cmd+F5):展示按钮只播报“按钮”,而非其用途
  • 最大字号:展示布局错乱或文本被截断
  • 颜色对比度分析器:展示未通过的对比度检测结果
  • 触控目标覆盖层:展示小于 44pt 的目标
参考
  • App Store 审核指南 2.5.1
  • WCAG 2.1 AA 级(行业标准)
  • ADA 合规要求(在美国存在法律风险)
步骤 3:提出折中方案
"I can achieve your aesthetic goals while meeting accessibility requirements:

1. VoiceOver labels: Add them programmatically (invisible in UI, required for approval)
2. Dynamic Type: Use layout techniques that adapt (examples from Apple HIG)
3. Contrast: Adjust colors slightly to meet 4.5:1 (I'll show options that preserve brand)
4. Touch targets: Expand hit areas programmatically (visual size stays the same)

These changes won't affect the visual design you're seeing, but they're required
for App Store approval and legal compliance."
步骤 4:记录决定

如果你的意见未被采纳(设计师坚持违规做法):

Slack message to PM + designer:

"Design review decided to proceed with:
- Fixed font sizes (disabling Dynamic Type)
- 38x38pt buttons (below 44pt requirement)
- 3.8:1 text contrast (below 4.5:1 requirement)

Important: These changes violate App Store Review Guideline 2.5.1 and WCAG AA.
This creates three risks:

1. App Store rejection during review (adds 1-2 week delay)
2. ADA compliance issues if user files complaint (legal risk)
3. 15% of potential users unable to use app effectively

I'm flagging this proactively so we can prepare a response plan if rejected."
为什么这样做有效
  • 你没有质疑对方的设计品味
  • 你指出了 App Store 被拒的风险(业务影响)
  • 你引用了具体指南(而非个人意见)
  • 你提供了保留视觉设计的解决方案
  • 你记录了决定(在被拒后保护自己)
实例:App Store 拒绝(48 小时内重新提交)
情境
  • 被拒后,距离重新提交截止时间还有 48 小时
  • Apple 指出:“2.5.1 - VoiceOver 支持不足”
  • 设计师说:“赶紧加上通用标签就行”
  • PM 正在旁听会议,希望尽快解决
应对方法
// ❌ WRONG - Generic labels (will fail re-review)
Button(action: addToCart) {
    Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Button") // Apple will reject again

// ✅ CORRECT - Descriptive labels (passes review)
Button(action: addToCart) {
    Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")
.accessibilityHint("Double-tap to add this item to your shopping cart")
在会议中演示
  1. 启用 VoiceOver(Cmd+F5)
  2. 展示播报“按钮”(通用标签——不合格)
  3. 展示播报“添加到购物车”(描述性标签——合格)
  4. 引用 Apple 的拒绝信息:“元素必须具有描述性标签”

预计耗时:2-4 小时,审核所有交互元素并添加适当的标签。

结果
  • 如实预估耗时可避免再次被拒
  • 添加适当的标签即可通过 Apple 审核
  • 在 48 小时内重新提交并获接受
何时接受设计决定(即使你不同意)

有时设计师有充分的理由不遵循无障碍指南。出现以下情况时可以接受:

  • 他们了解 App Store 拒绝的风险
  • 如果被拒,他们愿意推迟发布
  • 你以书面形式记录了决定
  • 他们承诺如果被拒就进行修复
在 Slack 中记录
"Design review decided to proceed with [specific violations].

We understand this creates:
- App Store rejection risk (Guideline 2.5.1)
- Potential 1-2 week delay if rejected
- Need to audit and fix all instances if rejected

Monitoring plan:
- Submit for review with current design
- If rejected, implement proper accessibility (estimated 2-4 hours)
- Have accessibility-compliant version ready as backup"

这能保护你们双方,也能表明你并非在阻挠,而是在降低风险。


WCAG 合规等级

A 级(最低要求 — App Store 必需)
  • 1.1.1 非文本内容 — 图片具有文本替代内容
  • 2.1.1 键盘 — 所有功能均可通过键盘操作(iPadOS/macOS)
  • 4.1.2 名称、角色、值 — 元素具有无障碍名称
AA 级(标准 — 推荐)
  • 1.4.3 对比度(最低) — 文本为 4.5:1,UI 为 3:1
  • 1.4.4 调整文本大小 — 支持 200% 文本缩放
  • 1.4.5 文字图片 — 尽可能使用真实文本
AAA 级(增强 — 最佳实践)
  • 1.4.6 对比度(增强) — 文本为 7:1,UI 为 4.5:1
  • 2.3.3 交互引发的动画 — 支持减少动态效果
  • 2.5.5 目标尺寸 - 最小目标尺寸为 44x44pt

目标 所有内容达到 AA 级,条件允许时达到 AAA 级。

快速命令参考

修复后:

# Quick scan for new issues
/axiom:audit-accessibility

# Deep diagnosis for specific issues
/skill axiom:accessibility-diag

资源

文档:/accessibility/voiceover、/uikit/uifont/scaling_fonts_automatically


请记住 无障碍不是一项功能,而是一项要求。15% 的用户存在某种形式的残障。让应用具备无障碍性不仅是正确的做法,还能扩大用户群体,并改善每个人的使用体验。

评论

支持 Markdown