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

问题描述 投票:0回答:2

我正在尝试在 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
                                        })
                                )
                        }
                    )
            }
        }
    }
}

我做错了什么?谢谢

button swiftui gesture tap gamepad
2个回答
3
投票

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

这是可能解决方案的演示。使用 Xcode 13.4 / iOS 15.5 进行测试

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

GitHub 上的测试模块


1
投票

Asperi的解决方案是一样的想法,
但为了使其使用起来更简洁, 你可以像这样重构代码:

fileprivate struct HoldableButtonStyle: 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(HoldableButtonStyle(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 })
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.