SwiftUI:在列表单元格中添加菜单,同时使用onTapGesture手势触发

7

我在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")
    }
}

这是iOS还是macOS项目? - Asperi
哦,抱歉,它在iOS14.3上使用Xcode 12.3。 - Stitch
我现在也在 macOS 上进行了测试,发现打开菜单时不会触发单元格手势。尽管在 Mac 上菜单的外观非常不同(实际上是一个下拉菜单),但我希望两个平台上的行为相同。因此,我刚刚向 Apple 报告了一个错误。 - Stitch
2个回答

10

或者,您可以覆盖菜单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())

谢谢,虽然我认为@Asperi的答案更优雅,也可能更具有未来性,但我会将其标记为接受的答案。实际上,在我的真实示例中,单元格要复杂得多,菜单按钮不是右侧最后一个子视图。主要问题是,我在应用程序的不同位置重用此单元格,并希望根据使用位置有不同的onTapGestures,因此我将在单元格外应用该手势。在这些情况下,您的解决方案更好。 - Stitch
1
除此之外,感谢您提供的提示,使用contentShape而不是background使得间隔可点击。我不知道这一点,我还在使用SwiftUI进行我的第一个较大的应用程序开发时。 - Stitch
1
这个答案似乎在iOS 15.5上不再适用。更糟糕的是,结果是随机的(有时你会得到双击,有时你不会)。还有其他人也遇到这个问题吗?有没有替代方案? - Arshia
非常感谢,覆盖 .onTapGesture 的菜单功能正常工作了,真是个奇迹! - Oleg991
我有与@Arshia提到的相同问题。我认为问题在于菜单被呈现,当你点击一个“空白”区域来取消菜单时,触发了单元格的.onTapGesture,而无法在已经显示在屏幕上的菜单之上呈现新视图。我不知道如何解决这个问题。 - Luca

2

我不确定这是一个错误还是期望的行为,但是我建议避免这种重叠(因为即使今天成功了,在不同的系统/更新上可能会停止工作)。

以下是可能的解决方案(已经在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()
            }
        }
    }

谢谢,这当然也解决了问题,而且可能是更优雅和未来可靠的解决方案,但我接受了@ pawello2222的答案。请查看我的评论以了解原因。 - Stitch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接