SwiftUI 处理程序在降价文本视图中捕获点击的 link

SwiftUI handler to capture tapped link in markdown Text view

在 Swift 5.5 SwiftUI 中,现在可以在降价文本中包含 link。是否可以使用它在 Text 视图中为子文本添加点击处理程序?例如,我正在想象做类似下面的事情,但我还没有想出如何构建一个适用于此的 link (我每次都会收到 URL 错误)。设置某种自定义 url 模式是否可行?有没有更好的解决方案,比如我可以添加到 Text 视图中,其作用类似于 UITextViewshouldInteractWith?目标是解决与提到的问题类似的问题 但不必回退到 UITextView 或非包装 HStacks 或带 ZStacks 的 GeometryReaders。

let baseText = "apple banana pear orange lemon"
let clickableText = baseText.split(separator: " ")
                            .map{ "[\([=11=])](domainThatImAllowedToCapture.../\([=11=]))" }
Text(.init(clickableText)).onOpenLink { link in
  print(link.split(separator: "/").last) // prints "pear" if word pear is tapped.
}

您可以尝试类似此示例代码的操作。它遍历你的 baseText,创建适当的 links,当 link 是 tapped/actioned 时,你可以添加更多代码来处理它。

struct ContentView: View {
    let baseText = "apple banana pear orange lemon"
    let baseUrl = "https://api.github.com/search/repositories?q="
    
    var body: some View {
        let clickableText = baseText.split(separator: " ").map{ "[\([=10=])](\(baseUrl)\([=10=]))" }
        ForEach(clickableText, id: \.self) { txt in
            let attributedString = try! AttributedString(markdown: txt)
            Text(attributedString)
                .environment(\.openURL, OpenURLAction { url in
                    print("---> link actioned: \(txt.split(separator: "=").last)" )
                    return .systemAction
                })
        }
    }
}

使用@workingdog 描述的方法,我将其整理成以下工作解决方案:

import SwiftUI

struct ClickableText: View {
  @Environment(\.colorScheme) var colorScheme: ColorScheme
  
  private var text: String
  private var onClick: (_ : String) -> Void
  
  init(text: String, _ onClick: @escaping (_ : String) -> Void) {
    self.text = text
    self.onClick = onClick
  }

  var body: some View {
    Text(.init(toClickable(text)))
      .foregroundColor(colorScheme == .dark ? .white : .black)
      .accentColor(colorScheme == .dark ? .white : .black)
      .environment(\.openURL, OpenURLAction { url in
        let trimmed = url.path
          .replacingOccurrences(of: "/", with: "")
          .trimmingCharacters(in: .letters.inverted)
        withAnimation {
          onClick(trimmed)
        }
        return .discarded
      })
  }
  
  private func toClickable(_ text: String) -> String {
    // Needs to be a valid URL, but otherwise doesn't matter.
    let baseUrl = "https://a.com/"
    return text.split(separator: " ").map{ word in
      var cleaned = String(word)
      for keyword in ["(", ")", "[", "]"] {
        cleaned = String(cleaned.replacingOccurrences(of: keyword, with: "\\(keyword)"))
      }
      return "[\(cleaned)](\(baseUrl)\(cleaned))"
    }.joined(separator: " ")
  }
}