Search code examples
listswiftuireorderlist

Adding `.onTapGesture` modifier to part of the row view in List makes "Drag to reorder" not working in some area of the row view


Here is my code (Running on macOS 11.7.2,so it needs old version macOS compatibility >= 11.0):

struct ContentView: View {
    @State private var users = ["Paul", "Taylor", "Adele"]
    
    var body: some View {
        List {
            ForEach(users, id: \.self) { user in
                Text(user)
                    .onTapGesture(count: 2) {
                        
                    }
            }
            .onMove { source, destination in
                users.move(fromOffsets: source, toOffset: destination)
            }
        }
        .frame(width: 200,height: 200)
        .background(Color.white)
        .cornerRadius(10)
        .position(x: 500, y:300)
    }
}

if I comment out the .onTapGesture modifier,I can drag on any area of the view to reorder the list, but if I enable the .onTapGesture modifier, I can't do that anymore unless I drag outside the area of Text() (while still inside the row view of course).

Can I keep the .onTapGesture modifier while keeping it draggable to reorder the List?


Solution

  • I think I just found a perfect solution, which comes from this so

    Here is my code:

    class TapHandlerView: NSView {
        var doubleClickAction: () -> Void
        
        init(_ block: @escaping () -> Void) {
            self.doubleClickAction = block
            super.init(frame: .zero)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override func mouseDown(with event: NSEvent) {
            if event.clickCount == 2 {
                doubleClickAction()
            }
            super.mouseDown(with: event)   // keep this to allow drag !!
        }
    }
    
    struct TapHandler: NSViewRepresentable {
        let doubleClickAction: () -> Void
        
        func makeNSView(context: Context) -> TapHandlerView {
            TapHandlerView(doubleClickAction)
        }
        
        func updateNSView(_ nsView: TapHandlerView, context: Context) {
            nsView.doubleClickAction = doubleClickAction
        }
    }
    

    And I use it like this:

                ForEach(users, id: \.self) { user in
                    Text(user)
                        .overlay(
                        TapHandler(doubleClickAction: {
                            //Put your code here.
                        })
                }
    

    Now, my list supports double click and drag reordering, at the same time!