swiftuiswiftui-ontapgestureattributedstring

How to detect a tap on a link and tap coordinates at the same time in SwiftUI?


In my SwiftUI app, some parts of text need to be tappable. On taps, some custom actions should occur, not necessarily opening a web page. At the same time I need to detect tap coordinates. I was going to use a drag gesture handler for that.

I implemented tappable text as links using AttributedString. The problem is that I cannot detect tap coordinates because handlers for tap or drag gestures are not invoked when tapping on a link.

Any ideas on how to detect a tap on a link and tap coordinates at the same time in SwiftUI?

(I wouldn't like to use a webview for that because my app is a reading app that has a lot of text-related functionality.)

Below is a code sample. I defined a custom URL scheme for my app to be able to implement custom handlers for links.

import SwiftUI

struct TappableTextView: View {
    
    var body: some View {
        VStack {
            Text(makeAttributedString()).padding()
                .gesture(
                    DragGesture(minimumDistance: 0)
                        .onEnded({ (value) in
                            print("Text has been tapped at \(value.location)")
                        })
                )
            Spacer()
        }
        .onOpenURL { url in
            print("A link is tapped, url: \(url)")
        }
    }
    
    func makeAttributedString() -> AttributedString {
        var string = AttributedString("")
        
        let s1 = AttributedString("This is a long paragraph. Somewhere in the paragraph is ")

        var tappableText = AttributedString("tappable text")
        tappableText.link = URL(string: "customurlscheme:somedata")
        tappableText.foregroundColor = .green
        
        let s2 = AttributedString(". This is the rest of the paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
        
        string.append(s1)
        string.append(tappableText)
        string.append(s2)
        
        return string
    }
}

Solution

  • You need to use simultaneousGesture(_:including:). This is because you are clicking a link already so a normal gesture does not occur. Using simultaneousGesture means you both click the link and can grab the coordinates, at the same time.

    Code:

    Text(makeAttributedString()).padding()
        .simultaneousGesture(
            /* ... */
        )