I'm trying to recreate a Game Boy like game pad in SwiftUI. Graphically it looks good, but I can't make the actions work. I would like it to perform the action (move in the selected direction) while the arrow is tapped, and to stop moving once the arrow isn't tapped anymore (just like a real game pad would). The code I tried so far is this one:
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
})
)
}
)
}
}
}
}
What am I doing wrong? Thanks
It can be achieved with custom button style, because it has isPressed state in configuration.
Here is a demo of possible solution. Tested with 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
}
}
}
and updated button with it
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"
}
})