3

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

KANAYO AUGUSTIN UG
  • 2,078
  • 3
  • 17
  • 31
  • Can you elaborate on *'URLs associated with the text are not real URLs'*? – Ranoiaetep Jan 21 '23 at 12:16
  • 1
    Does this answer your question? [How to add onTapGesture AND underline to specific word in a SwiftUI Text view?](https://stackoverflow.com/questions/75112090/how-to-add-ontapgesture-and-underline-to-specific-word-in-a-swiftui-text-view) – lorem ipsum Jan 21 '23 at 13:38
  • Hi, Kanayo, I miss the part when you say that the link itself is constructed using part of the text into the Text/Link. Again, simple is better... I would do a Custom struct with the part that you will use to concatenate (a suppose) in the link and make the Text use this part within the custom View. Instead of the opposite and try to extract the text to for the link after. – Allan Garcia Jan 21 '23 at 13:39
  • 1
    See this link for how to use the `.onOpenURL` modifier which sounds like it will do what you want https://stackoverflow.com/a/75112555/123632 – Ashley Mills Jan 21 '23 at 13:45
  • @loremipsum... No it doesn't. Infact, when I use that method, and I use a valid URL, it opens up in the browser, and when I use an invalid URL, for example "friend-1-4", it throws an error in the console, complaining about the URL – KANAYO AUGUSTIN UG Jan 22 '23 at 09:16
  • URLs won’t work period if it isn’t in url format. You can’t just put a word there and expect it to open. That link shows you how to make a bundle url, you can add your word at the end of that and then parse it out. – lorem ipsum Jan 22 '23 at 10:22
  • @loremipsum... Even if I use a valid URL, I still don't get the URL printed in the console. It just opens up in the browser. And then I try to create a custom URL to my app like this `myapp://friend-3-6` I still get the same error. So I wld be glad if u can elaborate or show me with code the way it's done – KANAYO AUGUSTIN UG Jan 22 '23 at 11:49
  • 1
    edit your question to make it clear you only want answers for `iOS 14`, and show the full code in text, not a bit of a picture. – workingdog support Ukraine Jan 22 '23 at 12:47

2 Answers2

3

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)")
                    }
                    
                }
        }
    }
}
lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
-2
//
//  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)")
                }
        )
    }
    
}
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Allan Garcia
  • 550
  • 3
  • 16