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

如何让VoiceOver读取NSTextView中带NSTextAttachment的NSAttributedString无障碍文本

问题

我在使用NSTextView展示包含图片的NSAttributedString时,遇到VoiceOver无法正确读取图片无障碍文本的问题。具体来说,我通过NSAttributedString(attachment:...)构造带图片的富文本,给富文本添加了.accessibilityCustomText属性,但VoiceOver只会读取“text”,不会读取我设置的描述内容。

我的实现代码如下:

import SwiftUI

struct TextViewWithAttachments: NSViewRepresentable {
  
  func makeNSView(context: Context) -> some NSView {
    let textView = NSTextView()
    
    textView.isEditable = false
    textView.isSelectable = true
    textView.isVerticallyResizable = true
    textView.isHorizontallyResizable = false
    textView.backgroundColor = .green
    
    textView.translatesAutoresizingMaskIntoConstraints = false
    
    let nsImage = NSImage(systemSymbolName: "star.fill", accessibilityDescription: "Filled Star")
    let attachment = NSTextAttachment()
    attachment.image = nsImage
    let attrString = NSMutableAttributedString(attachment: attachment)
    attrString.addAttribute(.accessibilityCustomText, value: "this is a filled star", range: NSMakeRange(0, attrString.length))
    
    textView.textStorage?.setAttributedString(attrString)
    
    return textView
  }
  
  func updateNSView(_ nsView: NSViewType, context: Context) {
    // updating logic
  }
  
}

#Preview {
  TextViewWithAttachments()
}

我目前能通过枚举富文本的.accessibilityCustomText属性来设置textView的无障碍标签,代码如下:

attrString.enumerateAttribute(.accessibilityCustomText, in: NSRange(location: 0, length: attrString.length)) { value, _, _ in
  guard let label = value as? String else { return }
  textView.setAccessibilityLabel(label)
}

但这种方法不够优雅,想知道有没有更好的实现方式。


解决方案

方法1:直接为NSTextAttachment设置无障碍标签

NSTextAttachment本身是NSObject子类,可直接设置它的无障碍属性,无需通过富文本的.accessibilityCustomText:

let attachment = NSTextAttachment()
attachment.image = nsImage
// 直接给attachment设置无障碍标签
attachment.setAccessibilityLabel("this is a filled star")
let attrString = NSMutableAttributedString(attachment: attachment)

VoiceOver会自动识别附件的无障碍标签,无需额外处理富文本属性。

方法2:复用NSImage的accessibilityDescription

你已经给NSImage设置了accessibilityDescription,可以让NSTextAttachment直接复用这个描述,避免重复设置:

let nsImage = NSImage(systemSymbolName: "star.fill", accessibilityDescription: "Filled Star")
let attachment = NSTextAttachment()
attachment.image = nsImage
// 复用NSImage的无障碍描述
if let desc = nsImage.accessibilityDescription {
    attachment.setAccessibilityLabel(desc)
}
let attrString = NSMutableAttributedString(attachment: attachment)

这种方式更简洁,符合DRY原则。

方法3:自定义NSTextAttachment子类(复杂场景适用)

如果需要批量处理带无障碍需求的图片附件,可以自定义NSTextAttachment子类,统一管理无障碍逻辑:

class AccessibleTextAttachment: NSTextAttachment {
    override func accessibilityLabel() -> String? {
        // 优先返回自定义标签,无则复用图片的无障碍描述
        return super.accessibilityLabel() ?? image?.accessibilityDescription
    }
}

使用时替换原NSTextAttachment即可:

let attachment = AccessibleTextAttachment()
attachment.image = nsImage
// 可手动设置标签,也可依赖图片的accessibilityDescription
// attachment.setAccessibilityLabel("自定义描述")
let attrString = NSMutableAttributedString(attachment: attachment)

方法4:自定义NSTextView解析附件无障碍属性

如果需要深度自定义NSTextView的无障碍行为,可以重写其无障碍相关方法,确保附件标签被正确读取:

class AccessibleTextView: NSTextView {
    override func accessibilityLabel() -> String? {
        var labels = [String]()
        // 遍历所有附件,收集无障碍标签
        textStorage?.enumerateAttribute(.attachment, in: NSRange(location: 0, length: textStorage?.length ?? 0), options: []) { value, _, _ in
            if let attachment = value as? NSTextAttachment, let label = attachment.accessibilityLabel() {
                labels.append(label)
            }
        }
        // 拼接文本内容(如果有)
        if let text = self.string, !text.isEmpty {
            labels.append(text)
        }
        return labels.joined(separator: ", ")
    }
}

之后在NSViewRepresentable中使用这个自定义TextView即可。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 21:05:15