简体   繁体   English

SwiftUI - 在 iPadOS 和 Catalyst 上使用带有鼠标/触控板的 onTapGesture 出现意外行为

[英]SwiftUI - unexpected behaviour using onTapGesture with mouse/trackpad on iPadOS and Catalyst

I have in my app a layout showing a list of rectangular cards - each one should be tappable (once) to reveal a set of action buttons and more information, etc.我在我的应用程序中有一个显示矩形卡片列表的布局 - 每个卡片都应该是可点击的(一次)以显示一组操作按钮和更多信息等。

I have implemented this using .onTapGesture() and I have also put .contentShape(Rectangle() to enforce the tappable area. However, while my implementation works fine for touchscreen interface, when I'm using it with the iPadOS mouse support, and on Catalyst for that matter, I see some very unexpected behaviour.我已经使用.onTapGesture()实现了这一点,并且我还使用了.contentShape(Rectangle()来强制执行可点击区域。但是,虽然我的实现适用于触摸屏界面,但当我将它与 iPadOS 鼠标支持一起使用时,并且在 Catalyst 上,我看到了一些非常出乎意料的行为。

I've made a minimal reproducible example below that you can copy to recreate the problem.我在下面做了一个最小的可重现示例,您可以复制它来重新创建问题。

Where the problems are when using mouse/trackpad input:使用鼠标/触控板输入时问题出在哪里:

  • Not every click of the mouse is recorded as a tap gesture.并非每次单击鼠标都会记录为轻击手势。 This is happening mostly arbitrary except from in a few cases:除了在少数情况下,这主要是任意发生的:
  • It seems to click either only in very specific areas, or when clicking multiple times in the same spot.它似乎只在非常特定的区域单击,或者在同一个位置单击多次。
  • It seems that in a lot of cases only every other click is recorded.似乎在很多情况下,只有每隔一次的点击才会被记录下来。 So I double click to get only one tap gesture.所以我双击只得到一个点击手势。
  • It isn't evident in this example code, but in my main app the tappable areas are seemingly arbitrary - you can usually click near text or in alignment with it to record a tap gesture, but not always .在此示例代码中并不明显,但在我的主应用程序中,可点击区域似乎是任意的 - 您通常可以单击文本附近或在 alignment 中使用它来记录点击手势,但并非总是如此

If you are running the example code you should be able to see the problem by repeatedly moving the mouse and attempting one click.如果您正在运行示例代码,您应该能够通过反复移动鼠标并尝试单击来查看问题。 It doesn't work unless you click multiple times in the same spot.除非您在同一位置多次单击,否则它不起作用。

What does work as expected :什么按预期工作

  • All input using touch instead of mouse;所有输入使用触摸而不是鼠标; regardless of where you tap it records a tap gesture.无论您在哪里点击它都会记录一个点击手势。
  • Mouse input when running as a native Mac target.作为本机 Mac 目标运行时的鼠标输入。 The issues mentioned above are only for mouse/trackpad when running the example under iPadOS and Mac Catalyst.上述问题仅适用于在 iPadOS 和 Mac Catalyst 下运行示例时的鼠标/触控板。

Code I used to recreate this problem (has a counter to count every time a tap gesture is recorded):我用来重新创建此问题的代码(每次记录点击手势时都有一个计数器来计数):

struct WidgetCompactTaskItemView: View {
    
    let title: String
    let description: String
    
    var body: some View {
        HStack {
            Rectangle()
                .fill(Color.purple)
                .frame(maxWidth: 14, maxHeight: .infinity)
            VStack(alignment: .leading) {
                Text(title).font(.system(size: 14, weight: .bold, design: .rounded))
                Text(description).font(.system(.footnote, design: .rounded))
                    .frame(maxHeight: .infinity)
                    .fixedSize(horizontal: false, vertical: true)
                    .lineLimit(1)
                    .padding(.vertical, 0.1)
                Spacer()
            }
            .padding(.horizontal, 6)
            .padding(.top, 12)
        }
        .frame(maxWidth: .infinity, maxHeight: 100, alignment: .leading)
        .background(Color.black)
        .cornerRadius(16)
        .overlay(
                RoundedRectangle(cornerRadius: 16)
                    .stroke(Color.green, lineWidth: 0.5)
            )
    }
}

struct ContentView: View {
    @State var tapCounter = 0
    var body: some View {
        VStack {
            Text("Button tapped \(tapCounter) times.")
            WidgetCompactTaskItemView(title: "Example", description: "Description")
                .contentShape(Rectangle())
                .onTapGesture(count: 1) {
                    tapCounter += 1
                }
            Spacer()
        }
     }
}

I have tried several things including moving modifiers around, setting eoFill to true on the contentShape modifier (which didn't fix the problem but simply made different unexpected behaviour).我尝试了几件事,包括移动修饰符,在contentShape修饰符eoFill设置为 true(这并没有解决问题,只是产生了不同的意外行为)。

Any help to find a solution that works as expected and works consistently whether mouse or touch would be much appreciated.任何帮助找到一个按预期工作并且无论鼠标还是触摸都能始终如一地工作的解决方案将不胜感激。 I am not sure if I am doing something wrong or if there is a bug here, so please try and recreate this example yourself using the code to see if you can reproduce the problem.我不确定我是否做错了什么或者这里是否有错误,所以请尝试使用代码自己重新创建此示例,看看是否可以重现问题。

So I realised that there was a much better solution that could bypass all the oddities that .onTapGesture had for me with mouse input.所以我意识到有一个更好的解决方案可以通过鼠标输入绕过.onTapGesture对我的所有奇怪之处。 It was to encapsulate the whole view in a Button instead.而是将整个视图封装在一个Button中。

I made this into a modifier similar to onTapGesture so that it's much more practical.我把它做成了一个类似于 onTapGesture 的修饰符,这样它就更实用了。


import Foundation
import SwiftUI

public struct UltraPlainButtonStyle: ButtonStyle {
    public func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
    }
}

struct Tappable: ViewModifier {
    let action: () -> ()
    func body(content: Content) -> some View {
        Button(action: self.action) {
            content
        }
        .buttonStyle(UltraPlainButtonStyle())
    }
}

extension View {
    func tappable(do action: @escaping () -> ()) -> some View {
        self.modifier(Tappable(action: action))
    }
}

Going through this: I first have a button style which simply returns the label as is.经历这个:我首先有一个按钮样式,它只是按原样返回 label。 This is necessary because the default PlainButtonStyle() still has a visible effect when clicked.这是必要的,因为默认的PlainButtonStyle()在单击时仍然具有可见效果。 I then create a modifier that encapsulates the content given in a Button with this button style, then add that as an extension to View .然后,我创建一个修饰符,用这种按钮样式封装Button中给出的内容,然后将其作为扩展添加到View

Usage example使用示例

WidgetCompactTaskItemView(title: "Example", description: "Description")
                .tappable {
                    tapCounter += 1
                }

This has solved all problems I've been having with clickable area using a mouse.这解决了我在使用鼠标可点击区域时遇到的所有问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM