In Swift 5.5 SwiftUI it's now possible to have links in markdown text. Is it possible to use this to add a tap handler for subtext in a Text view? For example, I'm imagining doing something like the following, but I haven't figured out how to construct a link that would work for this (I just get URL errors each time). Would setting up some kind of custom url schema work? Is there a better solution that doing that, like something I can add to the Text view that acts similar to UITextView's shouldInteractWith? The goal is to solve similar problems to what is mentioned here but without having to fall back to UITextView or non-wrapping HStacks or GeometryReaders with ZStacks.
let baseText = "apple banana pear orange lemon"
let clickableText = baseText.split(separator: " ")
.map{ "[\($0)](domainThatImAllowedToCapture.../\($0))" }
Text(.init(clickableText)).onOpenLink { link in
print(link.split(separator: "/").last) // prints "pear" if word pear is tapped.
}
CodePudding user response:
you could try something like this example code. It loops over your baseText, creates the appropriate links, and when the link is tapped/actioned you can put some more code to deal with it.
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{ "[\($0)](\(baseUrl)\($0))" }
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
})
}
}
}
CodePudding user response:
Using the method described by @workingdog, I've cleaned this up into the following working solution:
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: " ")
}
}
