SwiftUI - 如何在按钮被点击时执行操作,并在释放时结束它?

3

我正在尝试在SwiftUI中重新创建类似Game Boy的游戏手柄。从视觉上看,它看起来不错,但是我无法使动作生效。我希望它能够在箭头被轻敲时执行动作(移动到所选方向),并且一旦箭头不再被轻敲(就像真正的游戏手柄一样),停止移动。我到目前为止尝试的代码如下:

import SwiftUI

struct GamePad: View {
    
    @State var direction = "Empty"
    @State var animate = false
    
    var body: some View {
        ZStack {
            VStack {
                Text("\(direction) + \(String(describing: animate))")
                    .padding()
                Spacer()
            }
            VStack(spacing: 0) {
                Rectangle()
                    .frame(width: 35, height: 60)
                    .foregroundColor(.gray.opacity(0.3))
                    .overlay(
                        Button {
                            direction = "Up"
                            animate = true
                        } label: {
                            VStack {
                                Image(systemName: "arrowtriangle.up.fill")
                                    .foregroundColor(.black.opacity(0.4))
                                Spacer()
                            }
                            .padding(.top, 10)
                            .gesture(
                                TapGesture()
                                    .onEnded({ () in
                                        direction = "Ended"
                                        animate = false
                                    })
                            )
                        }
                    )
                
                Rectangle()
                    .frame(width: 35, height: 60)
                    .foregroundColor(.gray.opacity(0.3))
                    .overlay(
                        Button {
                            direction = "Down"
                            animate = true

                        } label: {
                            VStack {
                                Spacer()
                                Image(systemName: "arrowtriangle.down.fill")
                                    .foregroundColor(.black.opacity(0.4))
                            }
                                .padding(.bottom, 10)
                                .gesture(
                                    TapGesture()
                                        .onEnded({ () in
                                            direction = "Ended"
                                            animate = false
                                        })
                                )
                        }
                    )
            }
            HStack(spacing: 35) {
                Rectangle()
                    .frame(width: 43, height: 35)
                    .foregroundColor(.gray.opacity(0.3))
                    .overlay(
                        Button {
                            direction = "Left"
                            animate = true

                        } label: {
                            VStack {
                                Image(systemName: "arrowtriangle.left.fill")
                                    .foregroundColor(.black.opacity(0.4))
                                Spacer()
                            }
                                .padding(.top, 10)
                                .gesture(
                                    TapGesture()
                                        .onEnded({ () in
                                            direction = "Ended"
                                            animate = false
                                        })
                                )
                        }
                    )
                Rectangle()
                    .frame(width: 43, height: 35)
                    .foregroundColor(.gray.opacity(0.3))
                    .overlay(
                        Button {
                            direction = "Right"
                            animate = true

                        } label: {
                            VStack {
                                Spacer()
                                Image(systemName: "arrowtriangle.right.fill")
                                    .foregroundColor(.black.opacity(0.4))
                            }
                                .padding(.bottom, 10)
                                .gesture(
                                    TapGesture()
                                        .onEnded({ () in
                                            direction = "Ended"
                                            animate = false
                                        })
                                )
                        }
                    )
            }
        }
    }
}

我做错了什么?谢谢。

你尝试过使用 DragGesture 吗? - Ptit Xav
你的意思是用什么代替TapGesture()吗? - Pandruz
你可能会发现这个文档非常有用:https://developer.apple.com/documentation/swiftui/adding-interactivity-with-gestures,特别是 LongPressGesture.updating@GestureState - workingdog support Ukraine
2个回答

3

可以通过自定义按钮样式来实现,因为它在配置中具有isPressed状态。

以下是可能解决方案的演示。已在Xcode 13.4 / iOS 15.5上测试

demo

struct StateButtonStyle: ButtonStyle {
    var onStateChanged: (Bool) -> Void
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .opacity(configuration.isPressed ? 0.5 : 1)  // << press effect
            .onChange(of: configuration.isPressed) {
                onStateChanged($0)  // << report if pressed externally
            }
    }
}

并将其与更新后的按钮一起使用

    Button {
        direction = "Ended" // action on touchUP
    } label: {
        VStack {
            Image(systemName: "arrowtriangle.up.fill")
                .foregroundColor(.black.opacity(0.4))
            Spacer()
        }
        .padding(.top, 10)
    }
    .buttonStyle(StateButtonStyle { // << press state is here !!
        animate = $0
        if $0 {
            direction = "Up"
        }
    })

Test module on GitHub


1
这个想法与Asperi的解决方案是相同的,但为了更方便使用,您可以将代码进行重构,如下所示:
fileprivate struct PressableButtonStyle: ButtonStyle {
    var action: (Bool) -> Void
    init(perform action: @escaping (Bool) -> Void) {
        self.action = action
    }
    func makeBody(configuration: Configuration) -> some View {
        configuration.label.onChange(of: configuration.isPressed, perform: action)
    }
}

extension View {
    func onHold(perform action: @escaping (Bool) -> Void) -> some View {
        Button (action: {}, label: { self })
            .buttonStyle(PressableButtonStyle(perform: action))
    }
}

使用方法

struct TestView: View {
    @State var isHolding = false
    var body: some View {
        Text("")
            .blur(radius: isHolding ? 0 : 10)
            .animation(.easeIn, value: isHolding)
            .onHold(perform: { isHolding = $0 })
    }
}

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