Reputation: 1134
I have a string with URLs inside and I show it like this:
Text(LocalizedStringKey(
"Multiple URLs here, such as https://stackoverflow.com"
+ " or https://www.google.com"
))
The URLs are highlighted correctly and I can tap to open them, but I can't long press a single one to e.g. copy the URL or ideally provide some custom actions.
Is that possible and if so how do I do that?
Edit:
I also tried splitting the text into tokens and combining Text
Views, but adding a .contextMenu
doesn't work there:
Text("Multiple URLs here, such as ")
+ Text(LocalizedStringKey("https://stackoverflow.com"))
.contextMenu { } // < Cannot convert value of type 'some View' to expected argument type 'Text'
+ Text(" or ...")
Upvotes: 2
Views: 187
Reputation: 556
import SwiftUI
import UIKit
struct ReadOnlyDetectableLinkTextView: UIViewRepresentable {
var text: String
var dataDetectorTypes: UIDataDetectorTypes = .all
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.linkTextAttributes = [.foregroundColor: UIColor.link]
//act like SwiftUI.Text()
textView.isSelectable = true
textView.isEditable = false
textView.isUserInteractionEnabled = true
textView.dataDetectorTypes = .link
//Avoid destroying pop-up animations
textView.clipsToBounds = false
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
}
struct ContentView: View {
var body: some View {
ReadOnlyDetectableLinkTextView(text: "请访问 https://www.apple.com 以获取更多信息 https://google.com")
.frame(height: 40) // 可根据实际文本内容调整高度
.padding()
}
}
You can use
Text("ABCD")
.overylay { ReadOnlyDetectableLinkTextView(text: "ABCD") }
to make right、dynamic frame size.
Upvotes: 0
Reputation: 2093
Maybe you could try the following. I've thought of a tokenizer that splits every URL in the string you pass to the Text
:
struct ContentView: View {
let textWithUrls = "Visit https://stackoverflow1.com and https://stackoverflow2.com https://stackoverflow3.com - https://stackoverflow2.com https://stackoverflow4.com"
var body: some View {
VStack {
Text(textWithUrls)
.padding()
.contextMenu(menuItems: {
// Loop through URL strings
ForEach(getUrls(from: textWithUrls), id: \.self) { url in
// A View of your chosing here. I've used Button.
Button(action: {
if let url = URL(string: url) {
UIApplication.shared.open(url)
}
}) {
Text(url)
.padding()
}
}
})
}
}
// Function to extract URLs from the text using regular expression
private func getUrls(from text: String) -> [String] {
do {
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))
return matches.compactMap { $0.url?.absoluteString }
} catch {
print("Error: \(error)")
return []
}
}
}
I've tried separating the string using spaces, words and dashes and it has always worked. The getUrls
function uses a regular expression to dectect URLs and returns them as an array of String.
Here's the result:
Edit: If your URLs are way to many to be handled by the contextMenu you could try something like this:
ForEach(getUrls(from: textWithUrls), id: \.self) { url in
Button(action: {
if let url = URL(string: url) {
UIApplication.shared.open(url)
}
}) {
Text(url)
.padding(4)
}
}
Let me know if you like this solution!
Upvotes: 0