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

如何实现可点击子文本并触发下拉菜单?(SwiftUI方案优先)

SwiftUI实现可点击子文本(时间戳)并弹出下拉菜单的方案

方案一:AttributedString + 原生交互(推荐)

利用SwiftUI的AttributedString标记可点击的时间戳,结合原生交互实现点击高亮+下拉菜单效果,完全贴合SwiftUI生态。

代码实现

struct TimestampInteractiveView: View {
    @State private var highlightedRange: NSRange? = nil
    
    var body: some View {
        let attributedContent = buildAttributedContent()
        
        Text(attributedContent)
            .environment(\.openURL, OpenURLAction { url in
                guard url.scheme == "timestamp", let timestamp = url.host() else {
                    return .ignored
                }
                // 定位点击的时间戳范围,用于高亮
                highlightedRange = findTimestampRange(in: String(attributedContent), timestamp: timestamp)
                return .handled
            })
            .overlay(
                // 绘制高亮背景
                GeometryReader { proxy in
                    if let range = highlightedRange {
                        let textRect = proxy.boundingRect(forCharacterRange: range, in: attributedContent)
                        Rectangle()
                            .fill(Color.accentColor.opacity(0.2))
                            .cornerRadius(4)
                            .frame(width: textRect.width, height: textRect.height)
                            .position(x: textRect.midX, y: textRect.midY)
                            .animation(.easeInOut(duration: 0.2), value: highlightedRange)
                    }
                }
            )
            .contextMenu {
                // 动态生成菜单选项(可根据点击的时间戳调整)
                Button("添加到书签") {
                    // 执行书签添加逻辑
                    highlightedRange = nil
                }
                Button("分享当前时间点") {
                    // 执行分享逻辑
                    highlightedRange = nil
                }
            }
    }
    
    // 构建带交互的富文本
    private func buildAttributedContent() -> AttributedString {
        let rawText = "节目内容:00:15 开场介绍,00:30 核心话题讨论,01:20 听众问答"
        var attributedText = AttributedString(rawText)
        
        // 匹配时间戳格式(HH:mm)
        let timestampRegex = try! NSRegularExpression(pattern: #"(\d{2}:\d{2})"#)
        let matches = timestampRegex.matches(in: rawText, range: NSRange(rawText.startIndex..., in: rawText))
        
        for match in matches {
            guard let range = Range(match.range, in: attributedText) else { continue }
            // 给时间戳添加自定义链接,用于捕获点击事件
            attributedText[range].link = URL(string: "timestamp://\(attributedText[range])")
            attributedText[range].foregroundColor = .accentColor
            attributedText[range].underlineStyle = .single
        }
        
        return attributedText
    }
    
    // 查找时间戳在原文本中的NSRange
    private func findTimestampRange(in fullText: String, timestamp: String) -> NSRange? {
        guard let stringRange = fullText.range(of: timestamp) else { return nil }
        return NSRange(stringRange, in: fullText)
    }
}

核心逻辑说明

  • 用AttributedString给时间戳添加自定义link,通过OpenURLAction捕获点击事件
  • 借助GeometryReader获取点击文本的位置,绘制高亮背景
  • 结合系统contextMenu实现下拉菜单,点击菜单选项后清除高亮状态

方案二:拆分文本为独立组件

如果对样式控制有更高要求,可以将原字符串拆分为普通文本和时间戳片段,对每个时间戳单独绑定交互和菜单。

代码实现

struct SplitTextInteractiveView: View {
    @State private var highlightedTimestamp: String? = nil
    
    // 预拆分的文本片段(可通过正则自动拆分,这里为示例手动拆分)
    private let textSegments: [(content: String, isTimestamp: Bool)] = [
        ("节目内容:", false),
        ("00:15", true),
        (" 开场介绍,", false),
        ("00:30", true),
        (" 核心话题讨论,", false),
        ("01:20", true),
        (" 听众问答", false)
    ]
    
    var body: some View {
        HStack(alignment: .firstTextBaseline, spacing: 0) {
            ForEach(textSegments.indices, id: \.self) { index in
                let segment = textSegments[index]
                if segment.isTimestamp {
                    Text(segment.content)
                        .foregroundColor(highlightedTimestamp == segment.content ? .white : .accentColor)
                        .background(highlightedTimestamp == segment.content ? Color.accentColor : Color.clear)
                        .padding(.horizontal, 3)
                        .cornerRadius(4)
                        .animation(.easeInOut(duration: 0.2), value: highlightedTimestamp)
                        .onTapGesture {
                            highlightedTimestamp = segment.content
                        }
                        .menu {
                            Button("添加到书签") {
                                print("添加书签:\(segment.content)")
                                highlightedTimestamp = nil
                            }
                            Button("分享时间点") {
                                print("分享时间点:\(segment.content)")
                                highlightedTimestamp = nil
                            }
                        }
                } else {
                    Text(segment.content)
                }
            }
        }
    }
}

优势与不足

  • 优势:样式自定义程度更高,可轻松调整时间戳的高亮样式、间距等
  • 不足:需要手动或通过正则拆分字符串,文本内容动态变化时需要额外处理拆分逻辑

关于WebView的建议

不推荐使用WebView实现该需求,原因如下:

  • 额外的性能开销,且与SwiftUI原生交互不够流畅
  • 难以实现和原生UI一致的高亮效果与菜单样式
  • 需要处理JS与原生的通信逻辑,增加复杂度

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 02:59:58