Search code examples
macosswiftuitouchgesture

SwiftUI ForEach's onMove() modifier stop working if the content view has tap gesture action


I want to make a list view which contents could be dragged for reordering and tapped for detail. But when I add the onTapGesture() action in the content view, the onMove() action stop working.

Example code:

import SwiftUI

struct TestTapAndMove: View {
    @State private var users = ["Paul", "Taylor", "Adele"]

    var body: some View {
            List {
                ForEach(users, id: \.self) { user in
                    VStack {
                        Text("user: \(user)").font(.headline)
                        
                        Divider()
                    }.background(Color.gray)

                    // The TapGesture will result in onMove action not working.
                    .onTapGesture(perform: {
                        print("tap!")
                    })
                }
                .onMove(perform: move)
            }
    }

    func move(from source: IndexSet, to destination: Int) {
        print("onMove!")
        users.move(fromOffsets: source, toOffset: destination)
    }
}

Is there any solution that could make the tap and move action work together?


Solution

  • As workaround you can make some specific part of row clickable, like text in below example (or some added custom shape or plain button)

    VStack {
        Text("user: \(user)").font(.headline)
          .onTapGesture(perform: {
                print("tap!")
          })
        Divider()
    }.background(Color.gray)
    

    Alternate: you can add helper custom view as row overlay which would handle tap/mouseDown action for view (and does not break drug)

    class TapHandlerView: NSView {
        override func mouseDown(with event: NSEvent) {
            super.mouseDown(with: event)   // keep this to allow drug !!
            print("tap")
        }
    }
    
    struct TapHandler: NSViewRepresentable {
        func makeNSView(context: Context) -> TapHandlerView {
            TapHandlerView()
        }
    
        func updateNSView(_ nsView: TapHandlerView, context: Context) {
        }
    }
    

    and use it

    VStack {
        Text("user: \(user)").font(.headline)
        Divider()
    }.background(Color.gray)
    .overlay(TapHandler())         // << here !!
    

    Tested with Xcode 12.0 / macOS 10.15.6