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()
}
}
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).
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.
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)
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 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+.
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)
}
}
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)
}
}
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.
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.
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
- Learn more about enums in this blog post “Understanding Swift Enumeration: Enum with Raw Value and Associated Values”
- How to create a drop-down picker with Menu