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

Posted

技术标签:

【中文标题】SwiftUI:带有onTapGesture的列表单元格内的菜单触发手势【英文标题】:SwiftUI: Menu inside list cell with onTapGesture triggers gesture 【发布时间】:2020-12-29 13:06:49 【问题描述】:

我在 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" 也会打印到控制台。这对我来说是个问题,因为在我的真实示例中,我在点击手势中折叠单元格,当然我不希望在用户按下菜单按钮时发生这种情况。

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

我还注意到,当我将Menu 替换为常规Button 时,按下时不会触发单元格手势(仅使用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 项目? 抱歉,它在 iOS14.3 和 Xcode 12.3 上 我现在也在 macOS 上对其进行了测试,打开菜单时不会触发单元格手势。尽管 Mac 上的菜单看起来非常不同(实际上是 DropDown),但我希望两个平台上的行为相同。所以我刚刚向 Apple 报告了一个错误。 【参考方案1】:

或者,您可以覆盖菜单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,所以我将在单元格主体之外应用那个手势。在这种情况下,您的解决方案要好得多。 除此之外,感谢使用 contentShape 而不是背景的提示,以使垫片可点击。不知道,我还是 SwftUI 的新手,正在使用它开发我的第一个更大的应用程序。【参考方案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 的回答。原因见我对他的回答的评论。

以上是关于SwiftUI:带有onTapGesture的列表单元格内的菜单触发手势的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI .onTapGesture 从未调用过

SwiftUI:将链接与 onTapGesture 一起使用

SwiftUI 使用 onTapGesture 和 onLongPressGesture 处理按钮/视图以及释放操作

SwiftUI onTapGesture 块 NavigationLink 动作

SwiftUI onTapGesture 仅调用一次

LazyVGrid onTapGesture 导航到下一个屏幕 swiftUI