3、UIAccessibility协议:核心属性

好,咱们今天聊聊UIAccessibility协议。说实话,这个协议是iOS无障碍开发的基石。你写的每一个控件,只要想被旁白(VoiceOver)识别,就得遵守这个协议。UIKit里大部分组件默认都实现了,但自定义控件嘛……嗯,就得我们自己动手了。

我个人习惯把UIAccessibility协议比作「控件的自我介绍」。你想想看,一个看不见屏幕的用户,怎么知道这个按钮是干嘛的?全靠这几个属性告诉系统。今天咱们就把最核心的四个属性讲透:accessibilityLabel、accessibilityHint、accessibilityValue、accessibilityTraits

3.1 accessibilityLabel:控件的名字

这是最重要的属性,没有之一。旁白用户触摸到控件时,第一个听到的就是label。它应该是一个简短、精准的名词或短语。

核心原则:label回答的是「这是什么?」

我在项目中遇到过不少坑。比如一个「发送」按钮,有人直接写成了「点击这里发送消息」。其实没必要。旁白用户已经知道这是个按钮(traits会告诉它),你只需要说「发送」就够了。

// 正确示例
button.accessibilityLabel = "发送"

// 错误示例
button.accessibilityLabel = "点击这里发送消息"  // 冗余!

还有一个常见问题:图片按钮。比如一个垃圾桶图标,代表删除操作。这时候label应该写「删除」,而不是「垃圾桶图标」。

小技巧:label不要包含控件类型。比如「发送按钮」里的「按钮」二字是多余的,因为traits会告诉用户这是个按钮。

3.2 accessibilityHint:操作的提示

Hint是label的补充。它回答的是「操作后会怎样?」。注意,不是所有控件都需要hint。只有当操作结果不明确时,才需要加。

我曾经接手过一个项目,所有按钮都加了hint,结果旁白用户反馈说「太啰嗦了,每个按钮都要听两句话」。所以,hint要克制使用。

// 需要hint的场景
let deleteButton = UIButton()
deleteButton.accessibilityLabel = "删除"
deleteButton.accessibilityHint = "将永久移除这条记录"

// 不需要hint的场景
let submitButton = UIButton()
submitButton.accessibilityLabel = "提交"
// 提交按钮的操作结果很明显,不需要hint

注意:hint不要重复label的内容。比如label是「保存」,hint写成「点击保存」就完全多余了。

3.3 accessibilityValue:控件的当前状态

这个属性主要用于那些有状态的控件。比如滑块、进度条、开关、自定义分段控件等。它回答的是「当前值是多少?」。

举个例子,一个音量滑块。label是「音量」,value就应该是「50%」或者「0.5」。系统自带的UISlider会自动处理,但自定义的就得我们自己来了。

// 自定义滑块
class CustomSlider: UISlider {
    override var accessibilityValue: String? {
        get {
            // 返回百分比格式
            return "\(Int(self.value * 100))%"
        }
        set {
            // 通常不需要设置
        }
    }
}

我记得有一次做一个自定义分段控件,三个选项分别是「低」「中」「高」。如果不设置value,旁白只会读「低 按钮」。设置了value之后,它会读「低,当前选中」。用户体验完全不一样。

控件类型 label示例 value示例
UISlider 音量 50%
UIProgressView 下载进度 75%
UISwitch Wi-Fi 开 / 关
自定义分段控件 难度选择 高,当前选中

3.4 accessibilityTraits:控件的「身份」

Traits是最容易被忽视的属性。它告诉旁白这个控件是什么类型、有什么交互行为。说白了,就是给控件贴标签。

系统默认的traits已经覆盖了大部分场景:

  • .button — 按钮
  • .image — 图片
  • .staticText — 静态文本
  • .searchField — 搜索框
  • .adjustable — 可调节(如滑块)
  • .selected — 选中状态
  • .header — 标题

我见过最典型的错误:自定义一个可点击的视图,忘了设置traits。结果旁白用户摸上去,系统只读label,不提示「可点击」。用户根本不知道能操作。

// 自定义可点击视图
class TapableView: UIView {
    override var accessibilityTraits: UIAccessibilityTraits {
        get {
            return .button  // 告诉系统这是个按钮
        }
        set {
            // 保持默认
        }
    }
}

重要:traits可以组合使用。比如一个选中的按钮:[.button, .selected]。旁白会读「某某按钮,已选中」。

3.5 四个属性的配合使用

光讲单个属性没意思,咱们看看它们怎么配合。我拿一个自定义的「收藏」按钮举例:

class FavoriteButton: UIButton {
    var isFavorited: Bool = false {
        didSet {
            // 更新无障碍属性
            updateAccessibility()
        }
    }
    
    private func updateAccessibility() {
        // label:告诉用户这是什么
        accessibilityLabel = "收藏"
        
        // value:告诉用户当前状态
        accessibilityValue = isFavorited ? "已收藏" : "未收藏"
        
        // traits:告诉用户这是个按钮,且可切换
        accessibilityTraits = [.button, .selected]
        
        // hint:告诉用户操作结果
        accessibilityHint = isFavorited ? "点击取消收藏" : "点击添加收藏"
    }
}

你看,四个属性各司其职。旁白用户触摸时会听到:「收藏,未收藏,按钮,已选中。点击添加收藏」。信息完整,不啰嗦。

避坑指南:我曾经把label和value写反了。label写了「已收藏」,value写了「收藏」。结果旁白读成了「已收藏,收藏」。用户完全懵了。记住:label是名字,value是状态。

3.6 调试技巧

最后分享几个调试方法。我平时最常用的是「旁白转子」里的「语言」设置,可以调慢语速,仔细听每个属性的朗读顺序。

还有一个神器:Xcode的Accessibility Inspector。打开它,选中你的控件,所有无障碍属性一目了然。哪个没设置、哪个设置错了,一眼就能看出来。

  1. 打开Xcode → Open Developer Tool → Accessibility Inspector
  2. 选择模拟器或真机
  3. 点击控件,查看右侧面板

嗯,这四个属性讲完了。说白了,就是让系统知道「这是什么」「当前值多少」「能怎么操作」「操作后怎样」。下次你写自定义控件时,不妨按这个思路走一遍。用户会感谢你的。