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

SwiftUI中如何检测文本选择事件并触发ContextMenu

在SwiftUI中检测文本选择并延迟显示ContextMenu的方案

SwiftUI原生Text视图无法直接监听文本选择事件并获取选中内容,最可靠的方式是通过UIViewRepresentable包装UITextView,利用UIKit的代理机制实现需求。以下是具体实现步骤:

1. 实现UITextView的SwiftUI包装器

这个包装器会处理文本选择监听、选中内容传递,并支持延迟显示ContextMenu:

import SwiftUI
import UIKit

struct SelectableText: UIViewRepresentable {
    let text: String
    let font: UIFont
    let textColor: UIColor
    @Binding var selectedText: String
    var onSelectionChanged: (String) -> Void
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.text = text
        textView.font = font
        textView.textColor = textColor
        textView.isEditable = false
        textView.isSelectable = true
        textView.delegate = context.coordinator
        // 移除默认菜单,自定义延迟显示逻辑
        textView.inputAssistantItem.leadingBarButtonGroups = []
        textView.inputAssistantItem.trailingBarButtonGroups = []
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
        uiView.font = font
        uiView.textColor = textColor
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
        let parent: SelectableText
        private var selectionTimer: Timer?
        
        init(parent: SelectableText) {
            self.parent = parent
        }
        
        func textViewDidChangeSelection(_ textView: UITextView) {
            // 清除之前的定时器,避免重复触发
            selectionTimer?.invalidate()
            
            guard let selectedRange = textView.selectedTextRange,
                  let selectedText = textView.text(in: selectedRange),
                  !selectedText.isEmpty else {
                parent.selectedText = ""
                return
            }
            
            // 延迟500毫秒触发菜单显示和内容回调
            selectionTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
                guard let self = self else { return }
                self.parent.selectedText = selectedText
                self.parent.onSelectionChanged(selectedText)
                
                // 显示自定义ContextMenu
                let menu = UIMenu(title: "", children: [
                    UIAction(title: "复制", handler: { _ in
                        UIPasteboard.general.string = selectedText
                    }),
                    UIAction(title: "分享", handler: { _ in
                        // 在这里添加自定义分享逻辑
                    })
                ])
                textView.showContextMenu(menu, at: textView.caretRect(for: selectedRange.end), in: textView)
            }
        }
    }
}

2. 在SwiftUI视图中使用包装器

将SelectableText嵌入你的SwiftUI界面,绑定选中内容并处理回调:

struct ContentView: View {
    @State private var selectedText = ""
    
    var body: some View {
        VStack(spacing: 20) {
            SelectableText(
                text: "这是一段可以选中的文本,选中文本后会延迟显示自定义菜单,并获取选中内容。",
                font: .systemFont(ofSize: 16),
                textColor: .label,
                selectedText: $selectedText,
                onSelectionChanged: { text in
                    print("选中的内容:\(text)")
                    // 在这里添加你的自定义处理逻辑
                }
            )
            .padding()
            
            Text("当前选中:\(selectedText.isEmpty ? "无" : selectedText)")
                .foregroundColor(.secondary)
        }
        .padding()
    }
}

关键说明

  • 原生选择支持:通过UITextView的isSelectable = true启用原生文本选择交互,完全无需自定义点击逻辑。
  • 选中内容监听:利用textViewDidChangeSelection代理方法实时获取选中范围和内容,通过@Binding或回调传递给SwiftUI视图。
  • 延迟显示菜单:使用Timer实现延迟触发,避免用户拖动选择时频繁弹出菜单,提升交互流畅度。
  • 自定义ContextMenu:替换系统默认菜单,你可以根据需求添加任意自定义操作,若想保留系统菜单,只需移除清空inputAssistantItem的代码即可。

这个方案基于Apple原生组件实现,稳定性和交互体验都有保障,完美解决SwiftUI原生Text无法监听选择事件的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 03:57:32