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

UITextView属性字符串链接无法被VoiceOver单独访问问题咨询

问题原因

UITextView 默认会被系统识别为单个无障碍元素,当你仅通过属性字符串给文本加 .link 标记但未做无障碍适配时,VoiceOver 无法自动拆分内部链接为独立可交互元素,就会出现整段文本被作为单个元素读取、链接无法单独选中交互的问题。

修复步骤

1. 调整UITextView基础无障碍配置

在你的configureTextView方法中添加以下配置,关闭整个textView的单元素模式,允许系统遍历内部子元素:

// 放在配置代码的靠前位置
textView.isAccessibilityElement = false
textView.isSelectable = true // 你原有配置保留,不要改成false
textView.isEditable = false // 原有配置保留
if #available(iOS 15.0, *) {
    textView.textInteractionEnabled = true
}

注意:如果isAccessibilityElement设为true,系统会把整个UITextView当成单个无障碍元素,永远不会识别内部的独立链接。

2. 给每个链接补全无障碍属性

你当前仅给链接设置了.link属性值,需要额外补充无障碍相关的特质标记,修改每个链接的属性添加代码,以服务条款链接为例:

if let range = Subscription.smsPriceDisclaimer.range(of: Subscription.termsOfUse), let url = URL(string: AppURL.Shop.URL.termsOfUse) {
    let nsRange = Subscription.smsPriceDisclaimer.nsRange(from: range)
    attributedString.addAttributes([
        .link: url,
        .accessibilityTraits: UIAccessibilityTraits.link.rawValue,
        .accessibilityTextualContext: "link"
    ], range: nsRange)
}

剩下的隐私政策、财务激励通知两个链接,按完全相同的逻辑补全两个无障碍属性即可。

3. 低版本兼容(iOS 14及以下)

iOS 14及更早系统不会自动识别属性字符串里的链接为独立无障碍元素,需要手动生成UIAccessibilityElement赋值给textView的accessibilityElements属性,核心逻辑是把整段文本拆分为普通文本段、链接段,分别生成对应特质的无障碍元素,给链接元素绑定点击跳转逻辑,参考代码:

// 放在configureTextView方法最后,attributedText赋值完成后调用
var a11yElements = [Any]()
let fullText = Subscription.smsPriceDisclaimer as NSString
let linkConfigs: [(text: String, urlStr: String)] = [
    (Subscription.termsOfUse, AppURL.Shop.URL.termsOfUse),
    (Subscription.privacyPolicy, AppURL.Shop.URL.policiesAndDisclosures),
    (Subscription.noticeOfFinancialIncentive, AppURL.Shop.URL.noticeOfFinancialIncentive)
]
var currentLocation = 0
for config in linkConfigs {
    let linkRange = fullText.range(of: config.text)
    guard linkRange.location != NSNotFound else { continue }
    
    // 添加上一段普通文本
    if linkRange.location > currentLocation {
        let plainRange = NSRange(location: currentLocation, length: linkRange.location - currentLocation)
        let plainElement = UIAccessibilityElement(accessibilityContainer: textView)
        plainElement.accessibilityLabel = fullText.substring(with: plainRange)
        plainElement.accessibilityTraits = .staticText
        plainElement.accessibilityFrameInContainerSpace = textView.layoutManager.boundingRect(forGlyphRange: plainRange, in: textView.textContainer)
        a11yElements.append(plainElement)
    }
    
    // 添加链接元素
    let linkElement = UIAccessibilityElement(accessibilityContainer: textView)
    linkElement.accessibilityLabel = config.text
    linkElement.accessibilityTraits = .link
    linkElement.accessibilityHint = "双击打开页面"
    linkElement.accessibilityFrameInContainerSpace = textView.layoutManager.boundingRect(forGlyphRange: linkRange, in: textView.textContainer)
    linkElement.accessibilityActivateBlock = { [weak self] in
        guard let url = URL(string: config.urlStr) else { return }
        self?.delegate?.showWebview(urlString: "\(url)", titleValue: config.text)
    }
    a11yElements.append(linkElement)
    currentLocation = linkRange.location + linkRange.length
}

// 添加末尾剩余的普通文本
if currentLocation < fullText.length {
    let endRange = NSRange(location: currentLocation, length: fullText.length - currentLocation)
    let endElement = UIAccessibilityElement(accessibilityContainer: textView)
    endElement.accessibilityLabel = fullText.substring(with: endRange)
    endElement.accessibilityTraits = .staticText
    endElement.accessibilityFrameInContainerSpace = textView.layoutManager.boundingRect(forGlyphRange: endRange, in: textView.textContainer)
    a11yElements.append(endElement)
}

textView.accessibilityElements = a11yElements
验证方式

修改完成后打开VoiceOver,单指左右滑动遍历页面元素,选中链接时VoiceOver会朗读“[链接文本],链接,双击打开”,双击即可触发跳转逻辑,说明适配完成。

内容的提问来源于stack exchange,提问作者Akash Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 14:57:21