Search code examples
swiftuicontextmenudraggablelong-press

Dragging with onMove is not working when LongPressGesture is on view


I want to achieve behavior similar to the Apple Podcast app on the queue screen in iOS. Specifically, I need to:

  1. Display a context menu after a long press on every row, except for the view with the image "line.3.horizontal" (hamburger).
  2. Enable dragging of rows both by pressing the row itself and, more importantly, by pressing the "line.3.horizontal" image. enter image description here I found a solution to prevent the ContextMenu from appearing on long press on the hamburger image using LongPressGesture and a StateGesture variable. Unfortunately, after implementing this solution, I can no longer drag rows by pressing the hamburger image using onMove.
struct ContentView: View {

    @State var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
    @GestureState var isDraggingImageView = false

    var body: some View {
        List {
            ForEach(items, id: \.self) { item in
                HStack {
                    Text(item)
                    Spacer()
                    Image(systemName: "line.3.horizontal")
                        .gesture(
                            LongPressGesture()
                                .updating($isDraggingImageView, body: { value, state, _ in
                                    state = value
                                })
                        )
                }
                .contextMenu(ContextMenu(menuItems: {
                    if isDraggingImageView {
                        EmptyView()
                    } else {
                        Text("Menu Item 1")
                        Text("Menu Item 2")
                    }
                }))
            }
            .onMove(perform: move)
        }
    }

    func move(from source: IndexSet, to destination: Int) {
        items.move(fromOffsets: source, toOffset: destination)
    }
}

How to make onMove and LongPressGesture to work together?


Solution

  • I found a solution: Instead of contextMenu, I am just using Menu that allows me to include the views I want for the long press. Menu has a primaryAction parameter which can be used as a simple row item tap. So dragging with hamburger image is working (no context menu after long press), single tapping in row is working, and long press on row shows a context menu.

    Here is a working solution:

    struct ContentView: View {
    
        @State var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
    
        var body: some View {
            List {
                ForEach(items, id: \.self) { item in
                    HStack {
    
                        // Long press gesture displays menu
                        Menu {
                            Text("Menu item 1")
                            Text("Menu item 2")
                        } label: {
                            HStack {
                                Text(item)
                                Spacer()
                            }
                            .frame(maxWidth: .infinity)
                        } primaryAction: {
                            // Tap gesture action
                            print("Primary action \(item)")
                        }
    
                        Image(systemName: "line.3.horizontal")
                    }
                }
                .onMove(perform: move)
            }
        }
    
        func move(from source: IndexSet, to destination: Int) {
            items.move(fromOffsets: source, toOffset: destination)
        }
    }