You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS:VoiceOver模式下UITextView链接未出现在转子无法激活问题

问题根因

UITextView 默认对 URL 链接的 VoiceOver 支持是基于系统内置的 UITextItemInteraction 机制实现的,当你手动覆写了点击/手势事件,或者修改了 isEditable、isSelectable 的默认配置、或者实现了 UITextViewDelegate 的 shouldInteractWith 方法返回了 false,都会打断系统内置的无障碍元素解析流程,导致 VoiceOver 无法将文本中的 URL 识别为独立的可交互无障碍节点。

可行解决方案
  • 方案1:优先保留系统默认交互逻辑,仅在自定义逻辑处做分流
    不要直接全局覆写 UITextView 的点击手势,而是通过 UITextViewDelegate 的 textView(_:shouldInteractWith:in:interaction:) 方法处理链接交互,同时保留系统的无障碍解析能力。如果需要区分常规模式和 VoiceOver 模式的交互,可以通过 UIAccessibility.isVoiceOverRunning 动态判断返回值:

    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        // 执行自定义链接处理逻辑
        yourCustomHandleURLMethod(URL)
        // 返回false阻断系统默认跳转,返回true保留系统默认行为
        return false
    }
    

    注意要保证 UITextView 的 isSelectable = true、isEditable = false,这两个配置是系统识别文本链接的前提。

  • 方案2:手动添加无障碍节点适配 VoiceOver 场景
    如果必须全局覆写点击手势实现自定义逻辑,需要手动将文本中的 URL 对应的区域注册为独立的无障碍元素,让 VoiceOver 可以识别到:
    首先遍历 UITextView 的 attributedText 中的所有 NSLinkAttributeName 属性,获取每个链接的文本范围、对应的 URL、在视图中的 frame,然后创建 UIAccessibilityElement 实例添加到 textView 的 accessibilityElements 数组中:

    // 遍历属性文本获取所有链接
    textView.attributedText.enumerateAttribute(.link, in: NSRange(location: 0, length: textView.attributedText.length), options: []) { value, range, stop in
        guard let url = value as? URL else { return }
        // 获取链接文本对应的视图frame
        let glyphRange = textView.layoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil)
        let linkRect = textView.layoutManager.boundingRect(forGlyphRange: glyphRange, in: textView.textContainer)
        // 转换为textView的坐标系
        let convertedRect = linkRect.offsetBy(dx: textView.textContainerInset.left, dy: textView.textContainerInset.top)
        
        // 创建无障碍元素
        let linkElement = UIAccessibilityElement(accessibilityContainer: textView)
        linkElement.accessibilityLabel = "链接,\(url.absoluteString)"
        linkElement.accessibilityTraits = .link
        linkElement.accessibilityFrame = textView.convert(convertedRect, to: UIScreen.main.coordinateSpace)
        linkElement.accessibilityValue = url.absoluteString
        // 存储自定义信息用于点击时识别
        linkElement.accessibilityUserInputLabels = [url.absoluteString]
        
        // 添加到无障碍元素数组
        accessibilityElements.append(linkElement)
    }
    

    同时需要覆写 accessibilityActivate() 方法处理 VoiceOver 下的双击激活逻辑:

    override func accessibilityActivate() -> Bool {
        guard let focusedElement = UIAccessibility.focusedElement(using: .notificationVoiceOver) as? UIAccessibilityElement,
              let urlStr = focusedElement.accessibilityValue,
              let url = URL(string: urlStr) else {
            return false
        }
        yourCustomHandleURLMethod(url)
        return true
    }
    
  • 方案3:如果使用自定义手势识别器,需要设置手势的委托避免拦截系统事件
    给你添加的点击手势添加如下配置:

    yourTapGesture.cancelsTouchesInView = false
    yourTapGesture.delegate = self
    
    // 实现UIGestureRecognizerDelegate方法
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        // VoiceOver模式下不拦截手势,交给系统无障碍逻辑处理
        if UIAccessibility.isVoiceOverRunning {
            return false
        }
        // 常规模式下判断点击位置是否是链接,是则先执行自定义逻辑再拦截
        let tapLocation = touch.location(in: self)
        if let linkURL = getLinkURLAtLocation(tapLocation) {
            yourCustomHandleURLMethod(linkURL)
            return false
        }
        return true
    }
    
验证步骤
  • 完成配置后打开 VoiceOver,单指滑动遍历文本区域,可听到系统播报「链接,xxx」的提示即说明识别成功
  • 双击触发时可执行自定义逻辑即为适配完成

内容的提问来源于stack exchange,提问作者linto jacob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 20:18:03