Mastering SwiftUI Buttons: How to Create and Customise Button

Buttons play a crucial role in mobile applications, serving as the primary method for users to interact with and navigate the app. SwiftUI Buttons provide you with a straightforward and efficient way to create and customize them. Additionally you can create menu buttons with drop-down lists and pickers with menu styles. In this post, I will talk about buttons and how to add custom stylings like background colors and click animations. These customisations also work for menu buttons.

In this comprehensive guide, you will explore the powerful features and advantages of SwiftUI buttons, covering everything from basic creation to advanced customization.

How to create a button in SwiftUI?

A button in SwiftUI is initialized with an action, which is a closure or a method that gets executed when the user interacts with the button. The action defines the functionality that the button provides. Here’s an example:

Button(action: {
    // Your code to perform the action
}) {
    // Label for the button
}

The label for a button is a SwiftUI View that represents the button’s appearance. You can use a simple String or a more complex Label view, where the Label view can display both text and an icon.

You can also use an Image view as a button label to display a custom icon or image. When the user taps on the button, it becomes transparent. This helps to user to understand that the button was pressed and something should happen.

import SwiftUI
struct ContentView: View {
    var body: some View {
        VStack(spacing: 40) {

            Button("Do something") {
                // action happens here
            }

            Button {
                print("Add something")
            } label: {
                Label("Add New Note", systemImage: "plus")
            }
            Button {
                print("show more about this image")
            } label: {
                Image("candies")
                    .resizable()
                    .scaledToFit()
                    .frame(height: 150)
            }
            #if os(macOS)
           // .buttonStyle(.plain)
            #endif
        }
        .padding()
    }
}
Examples implementation for SwiftUI Button with text, label and images.

System Buttons are customised to fit the platform they are used in. On macOS, buttons appear as “push buttons”. You can see the buttons in the following images. On iOS, per default buttons don´t have a border. 

Another important difference is that on iOS user use their fingers to tap the button. In order for the user to correctly hit the button, it needs to be big enough. Apple recommends at least a size of  44×44 pixel (human interface guidelines for buttons).

SwiftUI button examples for macOS
On macOS, buttons are displayed per default as push buttons. If you want to show images, you can set the buttonStyle to plain.

The way users activate buttons depends on the platform they’re using:

  • In iOS and watchOS, users tap the button.
  • In macOS, users click the button.
  • In tvOS, users press “select” on an external remote, like the Siri Remote, while focusing on the button.

Understanding these activation methods ensures that you design your buttons with the appropriate interaction behavior for each platform.

Free SwiftUI Layout Book

Get Hands-on with SwiftUI Layout.

Master SwiftUI layouts with real-world examples and step-by-step guide. Building Complex and Responsive Interfaces.

What is the difference between Button and onTapGesture in SwiftUI?

In SwiftUI, both `Button` and `onTapGesture` are used to capture user interactions; however, they have distinct functionalities and use cases. Understanding the differences between these two methods is essential to effectively design and implement user interfaces.

`Button` is a dedicated control for initiating actions in SwiftUI. It is designed specifically for handling user interactions and comes with built-in support for accessibility and user interface styling. When using a `Button`, you provide an action (a closure or method) that gets executed when the user interacts with it. Additionally, you can apply predefined or custom button styles to modify its appearance and behavior. Here’s an example:

Button(action: performAction) {
    Text("Submit")
}

On the other hand, `onTapGesture` is a gesture modifier that can be added to any SwiftUI view to detect tap events. It does not have the built-in accessibility support or styling options that `Button` provides. Instead, `onTapGesture` allows you to capture user interactions and perform actions without modifying the appearance of the view. For example, you will not see the tap animation that the button has. Here’s an example:

Text("Submit")
.onTapGesture {
   performAction()
}

While both `Button` and `onTapGesture` can be used to respond to user interactions, they serve different purposes. `Button` should be used when you need a dedicated control with built-in accessibility features and styling options. In contrast, `onTapGesture` is suitable for capturing tap events on any SwiftUI view without the need for additional built-in functionality. A SwiftUI Button view will look like a button and the user easier understand that they can press it. In contrast using a text and tap gesture make it harder to discover the feature.

Also Button adjusts to the platform and will look like a typical button on Mac and in your iOS app., including the sizing.

Assigning a Role to Buttons

Button roles are an essential feature in SwiftUI that enables developers to characterize a button’s purpose, which in turn affects its appearance and styling. To assign a role to a button, utilize the ButtonRole enumeration when creating the button.

There are several predefined roles available, such as .destructive and .cancel, which have specific styling associated with them. The .destructive role, for example, often results in a button with a red foreground color to indicate a potentially harmful action, such as deletion. The .cancel role, on the other hand, can be used for buttons that dismiss or abort an operation.

Consider the following example, which demonstrates how to create buttons with different roles:

struct ContentView: View {
    var body: some View {
        VStack(spacing: 30) {
            Button {
            } label: {
                Label("default", systemImage: "circle")
            }

            Button(role: .destructive) {
            } label: {
                Label("destructive button", systemImage: "trash")
            }

            Button(role: .cancel) {
            } label: {
                Label("cancel button", systemImage: "plus.circle")
            }

            Menu("menu button") {
                Button {
                } label: {
                    Label("default", systemImage: "plus")
                }
                Button(role: .destructive) {
                } label: {
                    Label("destructive", systemImage: "trash")
                }
            }
        }
    }
}

I used four different buttons: a default button, a destructive button, a cancel button, and a menu button. The destructive and cancel buttons have their respective roles assigned using the role parameter, which adjusts their styling accordingly. The button appearance changes for a destructive role and the forground color changes to red. Per default buttons use the accent color, which is in my case blue.

By assigning roles to buttons, developers can ensure a consistent visual language throughout the app, making it easier for users to understand the purpose of each button and improving overall user experience.

How do I customize a button in SwiftUI?

SwiftUI provides various built-in button styles that developers can apply to their buttons, allowing for quick customization of button appearance and behavior. Additionally, SwiftUI enables developers to create custom button styles to achieve unique designs and interactions. 

System buttons provide various styles that allow for easy customization. When you use them you will get built-in interaction states (pressed animations, and disabled state), accessibility features, and adaptive appearance. 

Applying standard button styles with buttonStyle(_:) modifier

To apply a built-in button style, use the buttonStyle(_:) modifier. For example, you can apply the .bordered button style to a button, which results in a button with a border around it:

Button("Sign In", action: signIn)
     .buttonStyle(.bordered)
examples for SwiftUI buttons with different stylings on iOS and macOS.
Examples for SwiftUI buttons with different stylings on iOS and macOS.

For a better user experience, try to use a prominent button style only once per screen. This is the button you want the users to notice the most and make them press on that button. Think of a login screen with to actions: “sign in” and “forgot password”. The sign in action is the most important actions. “forgot password” is only a secondary action, users will perform only rarely.  

Adjusting the size of the button with controlSize(_:) modifier

SwiftUI allows you to easily modify the size of buttons by using the controlSize(_:) modifier. This modifier can be applied to a button to override the system default size with one of the control sizes specified in the ControlSize enumeration. The controlSize(_:) modifier enables developers to create buttons with sizes such as .mini, .small, and .regular, ensuring that the button size is suitable for the specific context within the app. For example, to create a small button with a bordered style, you can use the following code:

Button {

} label: {
    Label(String(describing: size), systemImage: "plus.circle")
}
.buttonStyle(.bordered)
.controlSize(.small)

By applying the controlSize(_:) modifier, you can customize the size of your buttons to create a more tailored user interface and improve the overall user experience. 

controlSize(_:) is avaliable for iOS 15+ and macOS 11+.

Changing the border shape of a button with buttonBorderShape(_:) modifier

With the buttonBorderShape modifier you can change the shape that is used for the button´s border. You can choose between capsule, roundedRectangle and roundedRectangle(radius:). The following example shows how to set custom stylings:

Button("capsule", action: {
   // do something
})
.controlSize(.regular)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
struct ContentView: View {
    var body: some View {
        VStack {
            Text("buttonBorderShape")

            Button("capsule", action: {

            })
            .buttonBorderShape(.capsule)

            Button("roundedRectangle(radius: 12)", action: {

            })
            .buttonBorderShape(.roundedRectangle(radius: 12))

            Button("border roundedRectangle", action: {

            })
            .buttonBorderShape(.roundedRectangle)

            Button("automatic", action: {

            })
            .buttonBorderShape(.automatic)
        }
        .controlSize(.regular)
        .buttonStyle(.bordered)
    }
}
Changing the border shape of a button with buttonBorderShape modifier

Changing the text color and background color of a button with tint and accentColor modifiers

In SwiftUI, you can easily change the text color and background color of a button using the tint and accentColor modifiers. For example, if you want to create a button with a green tint applied to its text and background, you can use the following code:

Button("capsule", action: {
   // do something
})
.buttonStyle(.bordered)
.tint(.green)

In this example, the .tint(.green) modifier is applied to a bordered button, which results in the text and background colors being changed to green. Using the tint and accentColor modifiers, you can easily customize the appearance of your buttons and create unique designs that match your app’s theme and enhance the overall user experience.

tint is avaliable for iOS 15+ and macOS 12+.

Depending on which button style you set, you can change the text color and background color of a button with the tint modifier or accentColor.
Depending on which button style you set, you can change the text color and background color of a button with the tint modifier or accentColor.

How to creating a flexible button that spans the width of the screen

In SwiftUI, you can create a flexible button that spans the entire width of the screen by using a frame modifier inside the button. This technique enables you to create a button that automatically adapts to different screen sizes and orientations, ensuring a consistent user experience across various devices.

Here’s an example of how to create a button that spans the width of the screen:

Button {
} label: {
    Text("Sign Up")
        .frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)

As a more interesting example, I used these buttons for a login screen. There are 2 buttons with different priorities. The more prominent button is “log in” and I use the borderedProminentStyle for it:

struct LoginView: View {

    @State private var email = ""
    @State private var password = ""

    var body: some View {
        VStack(alignment: .leading, spacing: 20) {
            Text("Join Now!")
                .font(.largeTitle)
                .padding(.bottom, 60)

            TextField("enter your email", text: $email)
                .textFieldStyle(.roundedBorder)
            TextField("enter your pasword", text: $password)
                .textFieldStyle(.roundedBorder)
                .padding(.bottom, 30)

            Button {
            } label: {
                Text("Sign Up")
                    .frame(maxWidth: .infinity)
            }
            .buttonStyle(.borderedProminent)
            .controlSize(.large)

            Button {
            } label: {
                Text("Log In")
                    .frame(maxWidth: .infinity)
            }
            .buttonStyle(.bordered)
            .controlSize(.large)
        }
        .padding(30)
    }
}
example implemenation for swiftui buttons inside a login screen

Creating custom button styles conforming to ButtonStyle protocol

For more fine-grained control over button appearance, you can create custom button styles by conforming to the ButtonStyle protocol. This requires implementing the makeBody(configuration:) method, which defines the appearance and behavior of buttons using the custom style:

struct CustomButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            .opacity(configuration.isPressed ? 0.5 : 1)
    }
}

To apply the custom button style to a button, use the buttonStyle(_:) modifier:

Button("Custom Button", action: customAction)
   .buttonStyle(CustomButtonStyle())

The button configuration gives you a lot of control. Here is what you can use:

  • configuration.label: the view that is specified in Button(label:)
  • configuration.isPressed: button pressed animation e.g. make the button more transparent when the button is pressed
  • configuration.role: show a different styling for the given role of destructive, cancel or none

You should also consider the disabled state. This is set when you use:

Button(...).disabled(true)

To use this property call the Environment property for isEnabled:

struct CustomButtonStyle: ButtonStyle {
     @Environment(\.isEnabled) var isEnabled
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            .saturation(isEnabled ? 1 : 0)
    }
}
example for custom button style in swiftui
A custom button style with 3 states for normal (top), pressed (middle), and disabled (bottom).

Customizing interaction behavior using PrimitiveButtonStyle protocol

In cases where you need more control over both the appearance and interaction behavior of a button, create a custom button style that conforms to the PrimitiveButtonStyle protocol. This protocol requires implementing the makeBody(configuration:) method, which provides greater flexibility in defining custom interactions:

struct CustomButtonStyle: PrimitiveButtonStyle {
     @Environment(\.isEnabled) var isEnabled
    let accentGradient = LinearGradient(gradient: Gradient(colors: [Color.blue, Color.green]),
                                        startPoint: .topLeading, endPoint: .bottomTrailing)
     func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .bold()
            .foregroundColor(Color.white)
            .padding()
            .background(accentGradient.cornerRadius(10).shadow(radius: showPressed  ? 5 : 10))
            .onTapGesture {
               if isEnabled {
                  configuration.trigger()
               }
            }
    }
}

The button configuration gives you a lot of control. Here is what you can use:

  • configuration.label: the view that is specified in Button(label:)
  • configuration.trigger: call this closure when you want the button action to happen
  • configuration.role: show a different styling for the given role of destructive, cancel, or none

By leveraging built-in button styles and creating custom button styles, you can achieve a wide range of appearances and interactions for your SwiftUI buttons, ensuring that your app’s design is both functional and visually appealing.

Free SwiftUI Layout Book

Get Hands-on with SwiftUI Layout.

Master SwiftUI layouts with real-world examples and step-by-step guide. Building Complex and Responsive Interfaces.

Style Guide: Reusable Styling Components

When developing an application, it’s crucial to maintain a consistent visual style throughout the app. Creating a style guide with reusable styling components can greatly improve the efficiency of the development process and ensure a cohesive user interface. By defining a set of standard colors, fonts, button styles, and other UI elements, developers can easily apply these components to various parts of the app. This not only saves time during development but also helps maintain a unified appearance, which is essential for creating a polished and professional-looking application. Additionally, using reusable styling components makes it easier to update the app’s appearance in the future, as making changes in one central location will automatically propagate to all instances where the component is used.

Buttons are an essential part of the design. You can see 2 examples in the following pictures. The Hello Fresh app has a more natural green color. This color is used also for the buttons. You can see that the same styling for the buttons is used on all 3 screens making the appearance of the app very consistent. 

button styling example for iOS with the hello fresh app
Screenshots from the Hello Fresh app.
button styling example for iOS with the apple fitness app
Screenshots from the Apple Fitness app.

The Fitness app is shown in dark mode, which works better with less color. Some of the buttons like the “Set Move Goal” button on the right are very subtle. Some like the “Try it Free” button on the right are very strong and prominent. Make sure not to use a lot of prominent buttons as it may overwhelm and confuse the user. Filled background buttons typically appear more prominent. 

Conclusion

This guide covered a wide range of topics related to SwiftUI buttons, from the basics to more advanced customization techniques. Now that you’re equipped with this knowledge, it’s time to put it into practice and create some amazing buttons for your apps. Remember, experimenting with different styles and functionalities can lead to some truly unique and engaging user experiences. So go ahead, let your creativity flow, and have fun exploring the world of SwiftUI buttons!

Further Reading

Leave a Comment

Subscribe to My Newsletter

Want the latest iOS development trends and insights delivered to your inbox? Subscribe to our newsletter now!

Newsletter Form