如何在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
相关产品推荐
相关产品推荐

