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 best solutions below

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)
    }
}