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

Swift macOS获取选中文本数据问题求助

在macOS中跨场景获取选中文本的解决方案

你的代码存在几个关键问题,导致无法在浏览器、PDF阅读器这类应用中正常获取选中文本:

  • 仅聚焦于顶层focusedElement,但很多应用的文本内容嵌套在子Accessibility元素中(比如浏览器的AXWebArea、PDF阅读器的AXStaticText)
  • 强制解包as!容易引发崩溃,且未处理不同应用的属性差异
  • 错误处理过于模糊,无法定位具体失败原因

以下是修正后的实现,支持浏览器(Safari/Chrome)、Preview、办公软件等多场景:

核心步骤说明

  1. 确认辅助功能权限:你的应用必须在「系统设置 > 隐私与安全性 > 辅助功能」中被勾选,否则Accessibility API完全无法工作。
  2. 递归查找文本元素:遍历聚焦元素的子元素,找到包含选中文本属性的节点。
  3. 兼容多属性:不同应用可能使用kAXSelectedTextAttribute(直接返回文本)或kAXSelectedTextRangeAttribute(返回范围),需要同时处理。
  4. 安全的可选绑定:避免强制解包,处理所有可能的错误情况。

完整代码实现

import AppKit

class TextSelectionHelper {
    private let systemWideElement: AXUIElement = AXUIElementCreateSystemWide()
    
    func getSelectedText() -> String? {
        var focusedElement: AnyObject?
        let error = AXUIElementCopyAttributeValue(systemWideElement, kAXFocusedUIElementAttribute as CFString, &focusedElement)
        
        guard error == .success, let focused = focusedElement as? AXUIElement else {
            print("获取聚焦元素失败:\(error.rawValue)")
            return nil
        }
        
        // 先尝试直接从聚焦元素获取选中文本
        if let selectedText = getSelectedText(from: focused) {
            return selectedText
        }
        
        // 递归查找子元素中的选中文本
        return findSelectedTextInChildren(of: focused)
    }
    
    private func getSelectedText(from element: AXUIElement) -> String? {
        // 尝试直接获取选中文本属性
        var selectedText: AnyObject?
        let textError = AXUIElementCopyAttributeValue(element, kAXSelectedTextAttribute as CFString, &selectedText)
        if textError == .success, let text = selectedText as? String {
            return text
        }
        
        // 如果没有直接文本,尝试通过范围获取
        var selectedRangeValue: AnyObject?
        let rangeError = AXUIElementCopyAttributeValue(element, kAXSelectedTextRangeAttribute as CFString, &selectedRangeValue)
        guard rangeError == .success, let rangeValue = selectedRangeValue as? AXValue else {
            return nil
        }
        
        var range = CFRange()
        guard AXValueGetValue(rangeValue, AXValueType(rawValue: kAXValueCFRangeType)!, &range) else {
            return nil
        }
        
        // 获取完整文本后截取选中部分
        var fullText: AnyObject?
        let fullTextError = AXUIElementCopyAttributeValue(element, kAXValueAttribute as CFString, &fullText)
        guard fullTextError == .success, let text = fullText as? String else {
            return nil
        }
        
        return (text as NSString).substring(with: range)
    }
    
    private func findSelectedTextInChildren(of element: AXUIElement) -> String? {
        var children: AnyObject?
        let error = AXUIElementCopyAttributeValue(element, kAXChildrenAttribute as CFString, &children)
        guard error == .success, let childElements = children as? [AXUIElement] else {
            return nil
        }
        
        for child in childElements {
            if let selectedText = getSelectedText(from: child) {
                return selectedText
            }
            // 继续递归查找子元素的子元素
            if let nestedText = findSelectedTextInChildren(of: child) {
                return nestedText
            }
        }
        
        return nil
    }
}

// 使用示例
let helper = TextSelectionHelper()
if let text = helper.getSelectedText() {
    print("选中的文本:\(text)")
} else {
    print("未找到选中文本")
}

额外说明

  • 如果需要获取选中文本的边界矩形,可以在getSelectedText方法中添加kAXBoundsForRangeParameterizedAttribute的处理逻辑,但注意只有部分应用支持该属性。
  • 部分应用(比如Chrome)的Web内容需要等待页面加载完成才能正确获取Accessibility元素,可适当添加延迟重试逻辑。
  • 测试时确保目标应用处于前台且有文本被选中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 22:34:54