Home > OS >  SwiftUI - How to perform action only while the button is tapped, and end it when the tap is released
SwiftUI - How to perform action only while the button is tapped, and end it when the tap is released

Time:07-26

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

CodePudding user response:

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

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

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

Test module on GitHub

  • Related