Search code examples
swiftuihittestswiftui-button

Button appears to have an inner and outer area with different actions, how can I make sure only one action is used for both tappable areas


Snapshot of Code and Preview in Xcode
Attached is an image with the button and code for reference. Here's also the code in case you'd like to test this out yourself:

struct ContentView: View {
    var body: some View {
        Button("Button"){
        print("Button pressed")
        }
        .frame(width: 200, height:25)
        .background(.red)
        .onTapGesture {
            print("Tap gesture")
        }
    }
}

Problem: If I tap on the inner area (the text), the console will print "Button pressed". If I tap on the outer area of the button, the red area, the console prints "Tap Gesture". Why is this happening? How can I fix it so that regardless of the area, the .onTapGesture block runs only?

I've tried removing the .onTapGesture block to see if the button reacts to any taps on the outer area but the issue I get is now the outer area doesn't react to any taps I input, it only registers taps on the inner area of the button.


Solution

  • You can prevent the button label from being receptive to taps by applying .allowsHitTesting(false):

    Button("Button"){
        print("Button pressed")
    }
    .allowsHitTesting(false) // 👈 HERE
    .frame(width: 200, height:25)
    .background(.red)
    .onTapGesture {
        print("Tap gesture")
    }
    

    However, it would probably make more sense to move the red background into the button. This combines the tap regions and you also get a "button press" effect when the red area is tapped:

    Button {
        print("Button pressed")
    } label: {
        Text("Button")
            .frame(width: 200, height:25)
            .background(.red)
    }
    

    If you plan on re-using this style in other places then you might like to create a ButtonStyle for it. This also lets you choose your own styling for when the button is pressed:

    struct RedButton: ButtonStyle {
        func makeBody(configuration: Configuration) -> some View {
            configuration.label
                .frame(width: 200, height:25)
                .background {
                    Color.red
                        .overlay(
                            Color.white
                                .opacity(configuration.isPressed ? 0.5 : 0)
                        )
                }
        }
    }
    
    Button("Button") {
        print("Button pressed")
    }
    .buttonStyle(RedButton())
    

    See How to add SwiftUI custom ButtonStyle as a static extension if you like using extensions for custom styles like this.