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

244 views Asked by At

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?

2

There are 2 answers

0
Li Fumin On BEST ANSWER

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!

7
workingdog support Ukraine On

you could try this approach using simultaneousGesture. Note on ios devices, you need to pause a bit on the user to action the move.

struct ContentView: View {
    @State private var users = ["Paul", "Taylor", "Adele"]
    
    var body: some View {
        List {
            ForEach(users, id: \.self) { user in
                Text(user)
                    .simultaneousGesture(
                        TapGesture()
                            .onEnded { _ in
                                print("----> tapped \(user)")
                            }
                    )
            }
            .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)
    }
}

EDIT-1: for MacOS(13+) only App, try this:

struct ContentView: View {
    @State var users = ["Paul", "Taylor", "Adele"]
    
    @State var selectedItem: String?
    
    var body: some View {
        List(selection: $selectedItem) {
            ForEach(users, id: \.self) { user in
                Text(user)
            }
            .onMove { source, destination in
                users.move(fromOffsets: source, toOffset: destination)
            }
        }
        // double click on the user
        .contextMenu(forSelectionType: String.self, menu: { _ in }) { usr in
            print("----> tapped \(usr)")
        }
        .frame(width: 333,height: 333)
        .background(Color.white)
        .cornerRadius(10)
  //      .position(x: 500, y:300)
    }
}