Search code examples
swiftuivertical-scrollinguilongpressgesturerecognizer

Adding `onLongPressGesture` to SwiftUI Text view prevents scrolling?


In a Multiplatform SwiftUI app I'm developing, I have a Text view embedded in a scroll view. I want to add a long press gesture to the Text view that lets the user copy the contents to the clipboard.

When I do that, the iOS version no longer scrolls the text when you press on the contents and drag. Below is the code in question. Is there a way to support both scrolling and a long press gesture recognizer?

(In UIKit you can make a gesture recognizer only fire when others fail. I suspect the long press gesture recognizer is intercepting the touch event and preventing the ScrollView from getting it.)

ScrollView() {
    // MARK: Resuts word list
    Text(anagramsState.wordList)
        .lineLimit(nil)
        .multilineTextAlignment(.leading)
        .frame(minWidth: geometry.size.width - 2, minHeight: 20, alignment: .topLeading)
        .onLongPressGesture {
            if copyTextToClipboard(anagramsState.wordList) {
                anagramsState.statusMessage = "Results copied to clipboard."
            } else {
                anagramsState.statusMessage = "Long press ignored."
            }
        }
}

Solution

  • SwiftUI currently has very few modifiers that allows you to fiddle with the built-in gesture recognisers of scroll views. The only one I can think of off the top of my head is presentationContentInteraction, which is rather irrelevant here.

    If your goal is to just allow the user to copy the entirety of the Text, you can use textSelection:

    Text("some text")
        .textSelection(.enabled)
    

    Note that the behaviour on macOS is different from that on iOS:

    On macOS, people use the mouse or trackpad to select a range of text, which they can quickly copy by choosing Edit > Copy, or with the standard keyboard shortcut.

    On iOS, the person using the app touches and holds on a selectable Text view, which brings up a system menu with menu items appropriate for the current context. These menu items operate on the entire contents of the Text view; the person can’t select a range of text like they can on macOS.

    If the ScrollView contains only the Text, consider using a TextEditor instead. You can pass it a constant binding to disable editing:

    TextEditor(text: .constant("Some Text"))
    

    This allows selecting specific ranges of text.