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

如何在SwiftUI的Text()视图中添加可执行自定义函数的可点击文本?

SwiftUI实现支持自定义点击的富文本(自动换行+可点击文本)

要实现类似HTML的可点击文本效果(支持自动换行,点击指定文本段执行自定义函数),用HStack嵌套Button的方案会破坏文本流排版,推荐用以下两种更优雅的方式:

方案一:利用自定义URL Scheme(最简单)

通过给AttributedString的可点击段落设置自定义URL Scheme,再通过onOpenURL拦截并执行自定义逻辑,完全保留SwiftUI Text的自然排版:

struct ClickableTextDemo: View {
    var body: some View {
        Text(buildAttributedContent())
            .onOpenURL { url in
                switch url.scheme {
                case "terms":
                    // 这里写服务条款的自定义逻辑
                    print("触发服务条款操作")
                case "privacy":
                    // 这里写隐私政策的自定义逻辑
                    print("触发隐私政策操作")
                default:
                    break
                }
            }
            .padding()
    }
    
    private func buildAttributedContent() -> AttributedString {
        var baseText = AttributedString("点击继续即表示您同意我们的")
        
        // 构建可点击的「服务条款」文本
        var termsText = AttributedString("服务条款")
        termsText.link = URL(string: "terms://")!
        termsText.foregroundColor = .blue
        termsText.underlineStyle = .single
        
        baseText += " " + termsText
        baseText += " 和我们的"
        
        // 构建可点击的「隐私政策」文本
        var privacyText = AttributedString("隐私政策")
        privacyText.link = URL(string: "privacy://")!
        privacyText.foregroundColor = .blue
        privacyText.underlineStyle = .single
        
        baseText += " " + privacyText
        
        return baseText
    }
}

这种方案的优势:

  • 完全沿用SwiftUI Text的排版逻辑,自动换行、自适应布局都和普通文本一致
  • 点击响应精准,只有标记的文本段会触发逻辑
  • 代码简洁,不需要依赖UIKit组件

方案二:自定义属性+点击位置识别(更灵活)

如果需要更复杂的自定义标记(比如传递额外参数),可以给AttributedString添加自定义属性,再通过点击位置识别触发对应逻辑:

// 定义自定义属性
extension AttributeScopes {
    struct CustomAttrs: AttributeScope {
        let clickAction: ClickActionAttribute
    }
}

extension AttributeDynamicLookup {
    subscript<T: AttributedStringAttribute>(dynamicMember keyPath: KeyPath<CustomAttrs, T>) -> T {
        self[T.self]
    }
}

struct ClickActionAttribute: AttributedStringKey {
    typealias Value = () -> Void
    static let name = "ClickActionAttribute"
}

// 使用示例
struct CustomClickableView: View {
    private let attributedText: AttributedString = buildCustomAttributedText()
    
    var body: some View {
        Text(attributedText)
            .onTapGesture { location in
                // 获取底层UITextView以识别点击位置
                guard let textView = findTextView() else { return }
                let layoutManager = textView.layoutManager
                let textContainer = textView.textContainer
                
                let charIndex = layoutManager.characterIndex(
                    for: location,
                    in: textContainer,
                    fractionOfDistanceBetweenInsertionPoints: nil
                )
                
                guard charIndex < attributedText.characters.count else { return }
                
                // 取出对应位置的点击动作并执行
                let nsAttrString = NSAttributedString(attributedText)
                if let action = nsAttrString.attribute(
                    ClickActionAttribute.name,
                    at: charIndex,
                    effectiveRange: nil
                ) as? () -> Void {
                    action()
                }
            }
            .padding()
    }
    
    private func buildCustomAttributedText() -> AttributedString {
        var baseText = AttributedString("点击继续即表示您同意我们的")
        
        var termsText = AttributedString("服务条款")
        termsText.clickAction = {
            print("执行服务条款自定义逻辑")
        }
        termsText.foregroundColor = .blue
        termsText.underlineStyle = .single
        
        baseText += " " + termsText
        baseText += " 和我们的"
        
        var privacyText = AttributedString("隐私政策")
        privacyText.clickAction = {
            print("执行隐私政策自定义逻辑")
        }
        privacyText.foregroundColor = .blue
        privacyText.underlineStyle = .single
        
        baseText += " " + privacyText
        
        return baseText
    }
    
    private func findTextView() -> UITextView? {
        // 遍历视图层级找到SwiftUI Text对应的UITextView
        func traverse(_ view: UIView) -> UITextView? {
            if let textView = view as? UITextView {
                return textView
            }
            for subview in view.subviews {
                if let textView = traverse(subview) {
                    return textView
                }
            }
            return nil
        }
        
        guard let rootView = UIApplication.shared.windows.first?.rootViewController?.view else {
            return nil
        }
        return traverse(rootView)
    }
}

这种方案适合需要给可点击文本绑定更复杂逻辑的场景,比如传递参数、区分多种点击类型。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 18:43:08