Bad practice: not using a ButtonStyle


You’re more of a video kind of person? I’ve got you covered! Here’s a video with the same content than this article 🍿


Can you guess what’s wrong with this code?

At first glance, everything seems to be fine: the Button we’ve implemented looks alright and we can successfully interact with it:

But if we take a closer look, it turns out that this Button actually has quite a big issue!

You see, if we try to tap the Button outside of the space occupied by the Text, nothing happens:

This behavior might be quite surprising, because when you look at the code every thing seems to be correct.

But this is definitely a problem that needs to be fixed, because this behavior of the Button will be very confusing for our users.

So let’s try to understand what’s happening.

This way of creating a Button by passing a String is actually equivalent to using this other initializer, where we explicitly pass the content of the Button through the argument label:

And the way a Button works is that only the views that are part of its label can be tapped to interact with the Button.

However, in our code we’ve applied the blue .padding() to the Button itself rather than to its label!

So that’s why when we tap on the blue .padding() nothing happens!

Fortunately, fixing this issue is very simple: we just need to move the code inside the label of the button…

…and now the Button is working as you would expect: you can interact with it by tapping either on the Text or the blue .padding():

We can take things even one step further, by encapsulating the code into a custom ButtonStyle:

This way it will be easier to re-use the code in several places and it will also allow us to go back to using the previous initializer to create the Button:

Just make sure to explicitly define how the Button should look like when it’s pressed, because you will no longer get that behavior for free!

That’s all for this article, I hope you’ve enjoyed learning about this mistake that’s very easy to make.

Here’s the code if you want to experiment with it:

// Before
import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Click me!") {
            // ...
        }
        .foregroundStyle(.white)
        .padding(32)
        .background(.blue)
        .clipShape(
            RoundedRectangle(
                cornerRadius: 20,
                style: .continuous
            )
        )
    }
}

// After
import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Click me!") {
            // ...
        }
        .buttonStyle(BlueRoundedButtonStyle())
    }
}

struct BlueRoundedButtonStyle: ButtonStyle {
    func makeBody(
        configuration: Configuration
    ) -> some View {
        configuration.label
            .foregroundStyle(.white)
            .padding(32)
            .background(
                configuration.isPressed ? .blue.opacity(0.5) : .blue
            )
            .clipShape(
                RoundedRectangle(
                    cornerRadius: 20,
                    style: .continuous
                )
            )
    }
}
Previous
Previous

Previews in Xcode also work with UIKit!

Next
Next

Did you know that a few popular iOS apps are open-source? 🤨