如何实现可点击子文本并触发下拉菜单?(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
相关产品推荐
相关产品推荐

