fer0n
fer0n

Reputation: 1134

Enable menu when long pressing on URL

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

Answers (2)

闪电狮
闪电狮

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

MatBuompy
MatBuompy

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:

Context menu tokenized URLs

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

Related Questions