我正在尝试在 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
})
)
}
)
}
}
}
}
我做错了什么?谢谢
可以通过自定义按钮样式来实现,因为配置中有 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"
}
})
和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 })
}
}