14

I have a list in my macOS app’s sidebar. When I add the .onDrag modifier, dragging NSItemProvider works correctly, but clicking/selecting the item to trigger the NavigationLink gets blocked.

struct ContentView: View {
    var body: some View {
        NavigationView {
            List(Data.items) { item in
                NavigationLink(
                    destination: Text(item.text),
                    label: {
                        Text(item.text)
                          .lineLimit(5)
                           // Enabling dragging with .onDrag {} disables click and selection:
                          .onDrag { return NSItemProvider(object: item.url as NSURL) }
                    })
            }
            Text("Placeholder")
        }
    }
}

How can I add the drag behavior while keeping the normal list selection behavior intact?

Here ist the rest of the Minimal Reproducible Example:

import SwiftUI

@main
struct ListDragExampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct Data {
    struct Item: Identifiable {
        let id = UUID()
        let text = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
        let url = URL(string: "http://example.com")!
    }
    static let items = [
        Item(),
        Item(),
        Item()
    ]
}
Frank R
  • 841
  • 5
  • 13
  • Without a [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) it is impossible to help you troubleshoot. – lorem ipsum Jan 31 '21 at 18:59
  • Thanks @loremipsum! I’ve added a Minimal Reproducible Example. – Frank R Feb 01 '21 at 12:44
  • I am experiencing the same behavior, a Dragable View inside a NavigationLink disrupts the normal NavigationLink behavior. At times one can tap on the link, other times no. Looking for a solution as well. The arrow keys work fine and you can move up and down the list, but using a mouse seems to be buggy. – cgiacomi Jun 14 '21 at 21:08
  • 1
    What is happening is that when you click on the Text the cell doesn't get selected. When you click outside the Text the cell gets selected. The workaround is to use a list where the selection is controlled via State var and add a TapGesture to your Text and update the State var. The challenge is to differentiate between a command click vs a normal click as the selection behaviour should behave differently – user1046037 Jan 06 '22 at 02:32
  • This is still a significant bug 2 years later. If you haven't already, please file a bug so that Apple fixes this soon. The more bug reports, the better the chances that it gets fixed. And by the way, the same code works fine on iPadOS. – Daniel Mar 11 '23 at 17:35

1 Answers1

6

I am having the same issue, and from what I could find, it's actually a bug in SwiftUI. I may be able to contribute some more details to the discussion:

  • It's not directly related to a NavigationLink. I have a plain List with custom item views and it's presenting the same erratic behavior;
  • In my custom item view, there are areas that don't have an actual View in them, such as the blank spaces beside Text views. When I click on those areas (the "background", see the image below), I can still select the item, but the dragging mechanism doesn't work;
  • When dropping a dragged an item on another, the area that accepts the drop is comprised only of the frames of the destination item's subviews. If I try to drop on the "background" area of the List item, it won't accept it.

Since I don't have enough reputation yet, please click this link to see the image.

Overall, it seems to me that whenever the item that accepts either drags or drops has subviews—being a simple Text or a more complex layout, such as the one I showed in the picture—the dragging mechanism disables selection whenever the mouse cursor hits inside one of those subviews, while only allowing drags and drops to occur by starting or ending inside their frames.

On the code below, the selection mechanism will work when clicking on the area to the right of the "Short" item, but the drag will only be triggered if it starts inside the Text view's frame.

struct ExampleList: View {
  private let data = ["Short", "Average", "Loooooooooooong"]
  @State private var selected = Set<String>()

  var body: some View {
    List(data, id: \.self, selection: $selected) { text in
      Text(text) // Clicking outside the frame selects it...
                 // ...but clicking inside starts the drag mechanism.
        .onDrag { NSItemProvider(object: text as NSString) }
    }
  }
}

Hope this helps. Cheers from Brazil!

  • 2
    Your observation is correct, tapping on the Text will not select the cell, tapping on the background will select the cell. The workaround is to add a tap gesture on the Text and update your `@State private var selected` in the tap gesture. The challenge is to differentiate between a command click vs a normal click as selection behaviour should be different – user1046037 Jan 06 '22 at 02:27
  • You can check the pressed modifier keys with `NSEvent.modifierFlags.contains(.command)`. `.command` should add a row to the selection. `.shift` should select everything between taped row and the last row that has been added to the selection. – Daniel Mar 11 '23 at 17:52