Button
One of the most ubiquitous views, using custom styling for a button can greatly affect the look and feel of your app.
Discussion
- Must use a style if you want an
isPressed
appearance - Inherit from
ButtonStyle
in almost all cases - Only inherit from
PrimitiveButtonStyle
to define your own interactions - Applies to
NavigationLink
unless it is in aList
orForm
Link
looks like aButton
but you cannot change the style
System Styles
- Use the snippet below to see all of the built-in button styles
1Button("Auto Button Style") { }
2 .buttonStyle(.automatic)
3
4Button("Plain Button Style") { }
5 .buttonStyle(.plain)
6
7Button("Bordered Button Style") { }
8 .buttonStyle(.bordered)
9
10Button("Bordered Button Style Prominent") { }
11 .buttonStyle(.borderedProminent)
12
13Button("Borderless Button Style") { }
14 .buttonStyle(.borderless)
ButtonStyle
- You should prefer
ButtonStyle
overPrimitiveButtonStyle
with few exceptions - No need to add gestures to work, everything is included
Create your style struct and have it conform to ButtonStyle
. It is customary for all button styles to be named ...ButtonStyle
. The Configuration
type contains .label
, .isPressed
, and .role
for you to use as you wish to style the button.
Here is a simple example of a custom button style.
1struct MyButtonStyle: ButtonStyle {
2 func makeBody(configuration: Configuration) -> some View {
3 HStack(alignment: .firstTextBaseline) {
4 image(for: configuration.role)
5
6 configuration.label
7 }
8 .overlay {
9 if configuration.isPressed {
10 border
11 }
12 }
13 }
14
15 var border: some View {
16 RoundedRectangle(cornerRadius: 5)
17 .stroke(lineWidth: 3)
18 }
19
20 func image(for role: ButtonRole?) -> Image {
21 let systemName = switch role {
22 case .cancel: "xmark.circle"
23 case .destructive: "trash"
24 default: "face.smiling"
25 }
26
27 return Image(systemName: systemName)
28 }
29}
In order to use the nice .buttonStyle(.myButton)
syntax, we must extend the ButtonStyle
protocol. Without this, we would have to use .buttonStyle(MyButtonStyle())
, which is now considered an old way of using styles.
1extension ButtonStyle where Self == MyButtonStyle {
2 static var myButton: Self { .init() }
3}
See the button style in action by using the following code in your view.
1Button("My Button Style") { }
2 .buttonStyle(.myButton)
3
4Button("My Button Style", role: .cancel) { }
5 .buttonStyle(.myButton)
6
7Button("My Button Style", role: .destructive) { }
8 .buttonStyle(.myButton)
PrimitiveButtonStyle
- Only inherit from
PrimitiveButtonStyle
whenButtonStyle
is not sufficient - You must add a gesture to run the button's action
The Configuration type contains .label
, .trigger
, and .role
for you to use as you wish to style the button.
Here is the setup.
1struct MyPrimitiveButtonStyle: PrimitiveButtonStyle {
2 func makeBody(configuration: Configuration) -> some View {
3 configuration.label
4 .foregroundStyle(color(for: configuration.role))
5 .onTapGesture(perform: configuration.trigger)
6 }
7
8 func color(for role: ButtonRole?) -> Color {
9 switch role {
10 case .cancel: .orange
11 case .destructive: .purple
12 default: .green
13 }
14 }
15}
16
17extension PrimitiveButtonStyle where Self == MyPrimitiveButtonStyle {
18 static var myPrimitiveButton: Self { .init() }
19}
Here is an example usage.
1Button("My Primitive Button Style") { }
2 .buttonStyle(.myPrimitiveButton)
3
4Button("My Primitive Button Style", role: .cancel) { }
5 .buttonStyle(.myPrimitiveButton)
6
7Button("My Primitive Button Style", role: .destructive) { }
8 .buttonStyle(.myPrimitiveButton)
Note that this button does not have any kind of pressed appearance. If you want that, you will need to use a gesture that can run a closure when a gesture starts and when it ends in order to change a @State
that affects the appearance. Have fun!
Design Considerations
Keep in mind that the default button styling applies a transparency and saturation effect to a button while it is being pressed. If you exclude something like this then your custom button style may not "feel" right to your users. Also, there is an effect applied when a button when it is disabled and you would likely want to make it visually apparent that the button cannot be pressed. ButtonStyle
will already disable hit testing on your button if the environment's isEnabled
variable is false
.
Explore
- Under what contexts does the automatic style change?
Xcode Version: 16.1 beta 3
Official Apple Documentation