任何等效于 UIKit 的 "touch down" 的 SwiftUI 按钮,即当您的手指触摸时激活按钮?

Any SwiftUI Button equivalent to UIKit's "touch down", i.e. activate button when your finger touches?

对于 SwiftUI,默认按钮行为等同于 UIKit 的 "touch up inside",它会在您的手指触摸按钮然后在按钮范围内抬起时激活。

有什么方法可以将其更改为 "touch down",以便在您的手指触摸按钮时立即关闭操作 运行?

您可以使用最小距离为零的 DragGesture 并为向下 (onChanged()) 或向上 (onEnded()) 定义闭包:

struct ContentView: View {
    @State private var idx = 0

    var body: some View {
        let g = DragGesture(minimumDistance: 0, coordinateSpace: .local).onChanged({
            print("DOWN: \([=10=])")
        }).onEnded({
            print("UP: \([=10=])")
        })

        return Rectangle().frame(width: 100, height: 50).gesture(g)
    }
}

您可以创建自定义视图修饰符:

extension View {
    func onTouchDownGesture(callback: @escaping () -> Void) -> some View {
        modifier(OnTouchDownGestureModifier(callback: callback))
    }
}

private struct OnTouchDownGestureModifier: ViewModifier {
    @State private var tapped = false
    let callback: () -> Void

    func body(content: Content) -> some View {
        content
            .simultaneousGesture(DragGesture(minimumDistance: 0)
                .onChanged { _ in
                    if !self.tapped {
                        self.tapped = true
                        self.callback()
                    }
                }
                .onEnded { _ in
                    self.tapped = false
                })
    }
}

struct MyView: View {
    var body: some View {
        Text("Hello World")
            .onTouchDownGesture {
                print("View did tap!")
            }
    }
}

我设法通过一个简单的按钮修改器实现了这一点:

struct TouchedButtonStyle: PrimitiveButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration
            .label
            .onTapGesture(perform: configuration.trigger)
    }
}

现在您只需将修饰符分配给您的按钮即可:

YourButton()
    .buttonStyle(TouchedButtonStyle())

您可以在 View 上使用隐藏的 _onButtonGesture 方法,即 public。它甚至不需要附加到 Button,但它看起来更好,因为你看到了按下效果。

代码:

struct ContentView: View {
    @State private var counter = 0
    @State private var pressing = false

    var body: some View {
        VStack(spacing: 30) {
            Text("Count: \(counter)")

            Button("Increment") {
                counter += 1
            }
            ._onButtonGesture { pressing in
                self.pressing = pressing
            } perform: {}

            Text("Pressing button: \(pressing ? "yes" : "no")")
        }
    }
}

结果:

由于帧速率的原因,作为 GIF 看起来不太好,但只要按下 pressing 就会转到 true,然后在释放时 false .

虽然 DragGesture 在许多情况下效果很好,但它可能会产生一些不需要的副作用,例如在滚动视图中使用时,它将优先于滚动视图的 built-in 手势处理。

在 SwiftUI 中,Buttons 已经跟踪按下状态,因此解决此问题的方法是使用自定义 ButtonStyle 以允许挂钩 isPressed状态。

这是一个可行的解决方案:

定义自定义 ButtonStyle:

struct CustomButtonStyle: ButtonStyle {
    
    var onPressed: () -> Void
    
    var onReleased: () -> Void
    
    // Wrapper for isPressed where we can run custom logic via didSet (or willSet)
    @State private var isPressedWrapper: Bool = false {
        didSet {
            // new value is pressed, old value is not pressed -> switching to pressed state
            if (isPressedWrapper && !oldValue) {
                onPressed()
            } 
            // new value is not pressed, old value is not pressed -> switching to unpressed state
            else if (oldValue && !isPressedWrapper) {
                onReleased()
            }
        }
    }
    
    // return the label unaltered, but add a hook to watch changes in configuration.isPressed
    func makeBody(configuration: Self.Configuration) -> some View {
        return configuration.label
            .onChange(of: configuration.isPressed, perform: { newValue in isPressedWrapper = newValue })
    }
}

您也可以直接在 onChange 修饰符的 perform 块中编写 didSet 逻辑,但我认为这使它看起来很干净。

Button

包装您的可点击视图

struct ExampleView: View {

    @State private var text: String = "Unpressed"

    var body: some View {
        Text(self.text)
        Button(action: { ... }, label: {
            // your content here
        }).buttonStyle(CustomButtonStyle(onPressed: { 
            self.text = "Pressed!"
        }, onReleased: { 
            self.text = "Unpressed" 
        }))
    }
}

然后你可以将任何你想要的逻辑传递给 onPressedonReleased 参数给 CustomButtonStyle.

我用它来允许自定义 onPressed 处理 ScrollView 中的可点击行。