How to create a button that changes opacity and triggers action when touched?

I wanna create a button with SwiftUI that fires the moment my finger touches it (like UIKit's touch down). I also want the opacity of the button to become 0.7 when my finger is pressing the button. When my finger leaves the button, I want the opacity of the button to change back to 1


I've tried 2 different types of button styles to achieve this but both of them failed:


struct ContentView: View {
    var body: some View {
        Button(action: {
            print("action triggered")
        }){
            Text("Button").padding()
        }
            .buttonStyle(SomeButtonStyle())
    }
}

struct SomeButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .background(Color.green)
            .opacity(configuration.isPressed ? 0.7 : 1)
            .onLongPressGesture(
                minimumDuration: 0,
                perform: configuration.trigger//Value of type 'SomeButtonStyle.Configuration' (aka 'ButtonStyleConfiguration') has no member 'trigger'
            )
    }
}

struct SomePrimativeButtonStyle: PrimitiveButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
      configuration.label
        .background(Color.green)
        .opacity(configuration.isPressed ? 0.7 : 1)//Value of type 'SomePrimativeButtonStyle.Configuration' (aka 'PrimitiveButtonStyleConfiguration') has no member 'isPressed'
        .onLongPressGesture(
          minimumDuration: 0,
          perform: configuration.trigger
        )
    }
}

How should I configure my button style to create such a button?

Answered by Claude31 in 401051022

I do understand your question. I do understand as well present SwiftUI limits !


I defined another button type.

Which seems to get close to what you want.

The key is that a view display must be managed by some state


struct MyPrimitiveButtonStyle: PrimitiveButtonStyle {
    var color: Color

    func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View {
        MyButton(configuration: configuration, color: color)
    }
   
    struct MyButton: View {
        @GestureState private var pressed = false

        let configuration: PrimitiveButtonStyle.Configuration
        let color: Color

        var body: some View {
            let longPress = LongPressGesture(minimumDuration: .infinity, maximumDistance: .infinity)
                .updating($pressed) { value, state, _ in state = value }
                .onEnded { _ in
                   self.configuration.trigger()
                 }

            return configuration.label
                .foregroundColor(.white)
                .padding(15)
                .background(RoundedRectangle(cornerRadius: 5).fill(color))
                .compositingGroup()
                .shadow(color: .black, radius: 3)
                .opacity(pressed ? 0.5 : 1.0)
                .scaleEffect(pressed ? 0.8 : 1.0)
                .gesture(longPress)
        }
    }
}


And another smoother version, with animation (see: https://stackoverflow.com/questions/57252706/animations-triggered-by-events-in-swiftui for details):


struct MyNewPrimitiveButtonStyle: PrimitiveButtonStyle {
    var color: Color

    func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View {
        MyButton(configuration: configuration, color: color)
    }
   
    struct MyButton: View {
        @State private var pressed = false

        let configuration: PrimitiveButtonStyle.Configuration
        let color: Color

        var body: some View {

            return configuration.label
                .foregroundColor(.white)
                .padding(15)
                .background(RoundedRectangle(cornerRadius: 5).fill(color))
                .compositingGroup()
                .shadow(color: .black, radius: 3)
                .opacity(self.pressed ? 0.5 : 1.0)
                .scaleEffect(self.pressed ? 0.8 : 1.0)
            .onLongPressGesture(minimumDuration: .infinity, maximumDistance: .infinity, pressing: { pressing in
                withAnimation(.easeInOut(duration: 1.0)) {
                    self.pressed = pressing
                }
                if pressing {
                    print("My long pressed starts")
                } else {
                    print("My long pressed ends")
                }
            }, perform: { })
        }
    }
}



Thanks to tell if that works for you ; if so, thanks to mark the thread as closed.

ButtonStyle and PrimitiveButtonStyle have not the same properties are actions.


Cannot trigger with ButtonStyle (it is handled by system).

No isPressed property in PrimitiveButtonStyle, because it is handled by gesture


Have a look here for details:

h ttps://swiftui-lab.com/custom-styling/

Yes I am aware that ButtonStyle and PrimitiveButtonStyle don't share the same properties. Which is why I'm asking for a solution. I have read that SwiftUI Lab blog post you linked and I can't find the solution there either. Neither of the button styles he used in that blog post actually has the touch down effect. Also, the PrimitiveButtonStyle he created actually goes back to its original size and opacity before the user's finger leaves the button which is also not something I want.

The point is that for ButtonStyle you don't need to trigger.


And in PrimitiveButtonStyle, you have to implement in the gesture. That's what is shown in the link.


And consider that SwiftUI has not (yet ?) all the flexibiliy you may wish.

I don't think you understand my question. I want the touch down effect, meaning I want the button to fire the moment my finger touches the button. What ButtonStyle does by default is like UIKit's touch up inside meaning the button fires when the user's finger LEAVES the button. As for PrimitiveButtonStyle, I know I have to implement gesture, it's what I did, I copied and pasted that blog post's code into Xcode, I did exactly what he did. And the problem is if I hold down the button for a few seconds, it actually reverts back to its original size and opacity while my finger is still pressing it.

Accepted Answer

I do understand your question. I do understand as well present SwiftUI limits !


I defined another button type.

Which seems to get close to what you want.

The key is that a view display must be managed by some state


struct MyPrimitiveButtonStyle: PrimitiveButtonStyle {
    var color: Color

    func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View {
        MyButton(configuration: configuration, color: color)
    }
   
    struct MyButton: View {
        @GestureState private var pressed = false

        let configuration: PrimitiveButtonStyle.Configuration
        let color: Color

        var body: some View {
            let longPress = LongPressGesture(minimumDuration: .infinity, maximumDistance: .infinity)
                .updating($pressed) { value, state, _ in state = value }
                .onEnded { _ in
                   self.configuration.trigger()
                 }

            return configuration.label
                .foregroundColor(.white)
                .padding(15)
                .background(RoundedRectangle(cornerRadius: 5).fill(color))
                .compositingGroup()
                .shadow(color: .black, radius: 3)
                .opacity(pressed ? 0.5 : 1.0)
                .scaleEffect(pressed ? 0.8 : 1.0)
                .gesture(longPress)
        }
    }
}


And another smoother version, with animation (see: https://stackoverflow.com/questions/57252706/animations-triggered-by-events-in-swiftui for details):


struct MyNewPrimitiveButtonStyle: PrimitiveButtonStyle {
    var color: Color

    func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View {
        MyButton(configuration: configuration, color: color)
    }
   
    struct MyButton: View {
        @State private var pressed = false

        let configuration: PrimitiveButtonStyle.Configuration
        let color: Color

        var body: some View {

            return configuration.label
                .foregroundColor(.white)
                .padding(15)
                .background(RoundedRectangle(cornerRadius: 5).fill(color))
                .compositingGroup()
                .shadow(color: .black, radius: 3)
                .opacity(self.pressed ? 0.5 : 1.0)
                .scaleEffect(self.pressed ? 0.8 : 1.0)
            .onLongPressGesture(minimumDuration: .infinity, maximumDistance: .infinity, pressing: { pressing in
                withAnimation(.easeInOut(duration: 1.0)) {
                    self.pressed = pressing
                }
                if pressing {
                    print("My long pressed starts")
                } else {
                    print("My long pressed ends")
                }
            }, perform: { })
        }
    }
}



Thanks to tell if that works for you ; if so, thanks to mark the thread as closed.

Oh my God thank you! I've been trying to figure this out for weeks.

How to create a button that changes opacity and triggers action when touched?
 
 
Q