SwiftUI:带有 onTapGesture 的列表单元格内的菜单触发手势

SwiftUI: Menu inside list cell with onTapGesture triggers gesture

我在 iOS14.3 (Xcode12.3) 上有一个 SwiftUI List,在它的单元格中我想在用户使用时弹出 Menu正在点击一个按钮。此外,这些单元格有一个 onTapGesture,问题是按下菜单按钮时也会触发此手势。

示例:

List {
    HStack {
        Text("Cell content")
        Spacer()
        // Triggers also onTapGesture of cell
        Menu(content: {
            Button(action: { }) {
                Text("Menu Item 1")
                Image(systemName: "command")
            }
            Button(action: { }) {
                Text("Menu Item 2")
                Image(systemName: "option")
            }
            Button(action: { }) {
                Text("Menu Item 3")
                Image(systemName: "shift")
            }
        }) {
            Image(systemName: "ellipsis")
                .imageScale(.large)
                .padding()
        }
    }
    .background(Color(.systemBackground))
    .onTapGesture {
        print("Cell tapped")
    }
}

如果点击省略号图像,菜单会打开,但 "Cell tapped" 也会打印到控制台。这对我来说是个问题,因为在我的真实世界示例中,我在轻击手势内折叠单元格,当然我不希望在用户按下菜单按钮时发生这种情况。

我发现,当我长按按钮时,手势不会被触发。但在我看来,这是不可接受的用户体验,我花了一段时间才找到答案。

我还注意到,当我用常规 Button 替换 Menu 时,按下时不会触发单元格手势(仅在 BorderlessButtonStylePlainButtonStyle 时触发,否则他根本不活跃)。但是我不知道如何通过它的操作打开菜单。

List {
    HStack {
        Text("Cell content")
        Spacer()
        // Does not trigger cells onTapGesture, but how to open Menu from action?
        Button(action: { print("Button tapped") }) {
            Image(systemName: "ellipsis")
                .imageScale(.large)
                .padding()
        }
        .buttonStyle(BorderlessButtonStyle())
    }
    .background(Color(.systemBackground))
    .onTapGesture {
        print("Cell tapped")
    }
}

我不确定这是一个错误还是预期的行为,但与其对抗它,我建议避免这种重叠(因为即使今天成功,它也可能停止在不同的地方工作 systems/updates) .

这是可能的解决方案(使用 Xcode 12.1 / iOS 14.1 测试)

    List {
        HStack {

            // row content area  
            HStack {
                Text("Cell content")
                Spacer()
            }
            .background(Color(.systemBackground)) // for tap on spacer area
            .onTapGesture {
                print("Cell tapped")
            }

            // menu area
            Menu(content: {
                Button(action: { }) {
                    Text("Menu Item 1")
                    Image(systemName: "command")
                }
                Button(action: { }) {
                    Text("Menu Item 2")
                    Image(systemName: "option")
                }
                Button(action: { }) {
                    Text("Menu Item 3")
                    Image(systemName: "shift")
                }
            }) {
                Image(systemName: "ellipsis")
                    .imageScale(.large)
                    .padding()
            }
        }
    }

或者,您可以覆盖菜单 onTapGesture:

struct ContentView: View {
    var body: some View {
        List {
            HStack {
                Text("Cell content")
                Spacer()
                Menu(content: {
                    Button(action: {}) {
                        Text("Menu Item 1")
                        Image(systemName: "command")
                    }
                    Button(action: {}) {
                        Text("Menu Item 2")
                        Image(systemName: "option")
                    }
                    Button(action: {}) {
                        Text("Menu Item 3")
                        Image(systemName: "shift")
                    }
                }) {
                    Image(systemName: "ellipsis")
                        .imageScale(.large)
                        .padding()
                }
                .onTapGesture {} // override here
            }
            .contentShape(Rectangle())
            .onTapGesture {
                print("Cell tapped")
            }
        }
    }
}

此外,无需使用 .background(Color(.systemBackground)) 敲击垫片。这不是很灵活 - 如果您想更改背景颜色怎么办?

您可以使用 contentShape 代替:

.contentShape(Rectangle())