Listen to link click in SwiftUI TextView

2k Views Asked by At

I have this code below

import SwiftUI

struct MyView: View {

    let text = "If you want to know more, ***[Click Here](http://example.com)*** to continue"
    @Environment(\.openURL) private var openURL
    
    var body: some View {
        Text(LocalizedString(text))
            .environment(\.openURL, OpenURLAction{ url in
                print(url)
                return .systemAction
            })
    }
    
}

I saw this in OpenURLAction Documentation and other SO questions like This answer from workingdog support Ukraine

But I always get a runtime error from Xcode saying:

Key path value type 'WritableKeyPath<EnvironmentValues, OpenURLAction>' cannot be converted to contextual type 'KeyPath<EnvironmentValues, OpenURLAction>'

Please can some one tell me what's going wrong? I'm using Xcode 14.0.1 if needed

Note: My reason for this is that the URLs associated with the text are not valid URLs, for example, I have a URL like this "[John Doe](friend-4-2)", so I want to open another ViewController in my app depending on the particular link the user clicked

Below is an image showing how my code looks like and the error I get enter image description here

2

There are 2 best solutions below

0
lorem ipsum On BEST ANSWER

This was an odd one...

The error "Key path value type 'WritableKeyPath<EnvironmentValues, OpenURLAction>' cannot be converted to contextual type 'KeyPath<EnvironmentValues, OpenURLAction>'" only happens in when supporting anything less than iOS 15.

That is because OpenURLAction(handler:) is only available starting iOS 15.

https://developer.apple.com/documentation/swiftui/openurlaction/init(handler:)

If you add @available(iOS 15, *) above the struct the error should go away.

After that you should be able to intercept any URL

import SwiftUI

@available(iOS 15, *)
struct LinkView: View {
    @State var clickedCount: Int = 0

    let text = "If you want to know more, ***[Click Here](yourApp://click-here)*** to continue"
    @Environment(\.openURL) private var openURL
    
    var body: some View {
        VStack{
            Text("\(clickedCount)")
            Text(LocalizedStringKey(text))
                .environment(\.openURL, OpenURLAction { url in
                    clickedCount += 1
                    return .systemAction
                })
        }
    }
}
@available(iOS 15, *)
struct LinkView_Previews: PreviewProvider {
    static var previews: some View {
        LinkView()
    }
}

iOS 14 and below are not supported by this method.

But if you want to support the lower versions you can use URL Scheme with onOpenURL(perform:)

First add the Scheme "Target > Info > URL Types"

enter image description here

then use the code below.

struct LinkView: View {
    @State var clickedCount: Int = 0
    
    let text = "If you want to know more, ***[Click Here](yourApp://clickHere)*** to continue"
    @Environment(\.openURL) private var openURL
    
    var body: some View {
        VStack{
            Text("\(clickedCount)")
            Link("Click-here", destination: URL(string: "yourApp://clickHere")!)
            Text(LocalizedStringKey(text))
                .onOpenURL { url in
                    //Check the URL
                    if url.absoluteString.hasSuffix("clickHere") {
                        clickedCount += 1
                    }else{
                        print("Tried to open \n\t\(url.absoluteString)")
                    }
                    
                }
        }
    }
}
0
Allan Garcia On
//
//  ContentView.swift
//  OnClickExample
//
//  Created by Allan Garcia on 21/01/23.
//

import SwiftUI
import Foundation
import UIKit

struct ContentView: View {
    var body: some View {
        VStack {
            // VStack is not really necessary, i'm using to centralize the content, but on newer versions of iOS this is not necessary.
            Spacer()
            // 1. Best Swifty way to open a link is by using a Link struct
            Link("Open Google Search", destination: URL(string: "https://www.google.com/")!)
            Spacer()
            // 2. If you need more customization link a link on your text of simple a different formatting of the text you can use a different initilizaer using Link(destionation:view), like so:
            Link(destination: URL(string: "https://www.google.com/")!) {
                Label("Open Google Search", systemImage: "link")
            }
            Spacer()
            // 3. If you need more customization on the action itself to open an url, that I thinks is your case, use a simple personalized struct (simple code is always better them complex code)
            TheWayIDoIt(
                urlText: "Open Google Search",
                urlLink: "https://www.google.com/",
                urlSystemImage: "magnifyingglass.circle.fill"
            )
            Spacer()
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct TheWayIDoIt: View {
    
    let urlText: String
    let urlLink: String
    let urlSystemImage: String
    
    var body: some View {
        Link(destination: URL(string: urlLink)!) {
            Label(urlText, systemImage: urlSystemImage)
        }
        .simultaneousGesture(
            TapGesture()
                .onEnded { _ in
                    print("Going to link \(urlLink)")
                }
        )
    }
    
}