SwiftUI Button Tutorial: Customization

Learn how to easily customize your app buttons style with the minimum effort by leveraging the latest SwiftUI button modifiers. By Andy Pereira.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Setting Sizes

Shapes and colors are helpful, but sometimes you might want to set sizes to indicate the button’s value. Luckily, applying a size is as easy as shapes and borders.

Replace all of the buttons in buttonSizesView with the following:

Button {
} label: {
  Text("Mini")
}
.controlSize(.mini)

Button {
} label: {
  Text("Small")
}
.controlSize(.small)

Button {
} label: {
  Text("Regular")
}
.controlSize(.regular)

Button {
} label: {
  Text("Large")
}
.controlSize(.large)

Here, you’ve applied the four sizes available for buttons:

  1. mini: This is the smallest size provided.
  2. small: Suitable for views where space is a concern.
  3. regular: This is the default buttons size. You might want to use this to force a default size to a specific button while allowing other buttons to inherit from upper level size.
  4. large: This is the “prominent” or largest size button available.

Finally, add the border style modifier to the Section containing the buttons you just modified:

.buttonStyle(.bordered)

Build and run. Each button will have a different size for its text and borders.

Button sizes applied to all buttons

Customizing Buttons

So far, you have been using built-in styles. While it’s a time-saver, you might need to create a custom, reusable button rendered with your own style. In SwiftUI, thankfully, it’s fairly easy. :]

Apple has provided the protocol ButtonStyle to give you the flexibility to create a button that will appear the way you want. In a minute, you’ll make your own style that sets a gradient background to a button.

Open ButtonStyle.swift. Add the following code to GradientStyle:

@Environment(\.isEnabled) private var isEnabled
private let colors: [Color]

init(
  colors: [Color] = [.mint.opacity(0.6), .mint, .mint.opacity(0.6), .mint]
) {
  self.colors = colors
}

This code provides a way to set the colors of your button’s background. You also added a property to reference the isEnabled state of your button. You’ll need that in a moment.

Add the following properties to GradientStyle:

// 1
private var enabledBackground: some View {
  LinearGradient(
    colors: colors,
    startPoint: .topLeading,
    endPoint: .bottomTrailing)
}

// 2
private var disabledBackground: some View {
  LinearGradient(
    colors: [.gray],
    startPoint: .topLeading,
    endPoint: .bottomTrailing)
}

// 3
private var pressedBackground: some View {
  LinearGradient(
    colors: colors,
    startPoint: .topLeading,
    endPoint: .bottomTrailing)
  .opacity(0.4)
}

Let’s break down what you did:

  1. This will provide the default, linear-gradient view for the button’s background.
  2. When creating a button style, consider what the button will look like when disabled or pressed. This property will provide the disabled state, which will make the button look gray.
  3. Finally, this provides a gradient view for the background of the button when pressed. It applies a 40% opacity to the button colors, which provides a visual change to reflect a press action on the button.

Next, add the following method to the struct:

@ViewBuilder private func backgroundView(
  configuration: Configuration
) -> some View {
  if !isEnabled { // 1
    disabledBackground
  } else if configuration.isPressed { // 2
    pressedBackground
  } else {
    enabledBackground
  }
}

While this is a fairly small block of code, a few important matters are happening here.

  1. SwiftUI provides a view’s disabled state through the environment. You added the environment property isEnabled earlier. Now, you’re using that value to determine whether you should return the disabled background view.
  2. The configuration parameter contains certain properties of your button.

The button’s Configuration contains three properties:

  1. role: This is the same role value you used earlier. If you want the custom button to pick up an appearance based on role, use that within your custom style.
  2. label: This represents the actual “label” or view provided by the button — it would be the text, image or any other view you provided.
  3. isPressed: The property storing the state of the button.

Next, build the body of your button. Replace the makeBody(configuration:) method with the following:

func makeBody(configuration: Configuration) -> some View {
  // 1
  HStack {
    // 2
    configuration.label
  }
  .font(.body.bold())
  // 3
  .foregroundColor(isEnabled ? .white : .black)
  .padding()
  .frame(height: 44)
  // 4
  .background(backgroundView(configuration: configuration))
  .cornerRadius(10)
}

Here’s an explanation of what you added:

  1. You can make the button’s body be almost any view. Here, you use a HStack to act as a container view for the button.
  2. Next, you add the actual view, or label, of the button. Whatever view the button provided will now always wrap inside a HStack
  3. Then you set the foreground, or text color, based on the enabled state.
  4. You finally set the background based on the provided configuration. This was the method you implemented in the previous step.

To make using your custom button style a bit easier, add the following extension with static member lookup at the end of ButtonStyle.swift.

extension ButtonStyle where Self == GradientStyle {
  static var gradient: GradientStyle { .init() }
}

This block of code makes a property available on ButtonStyle. The property creates a default version of GradientStyle. You’ll use this in the next step.

Finally, open KitchenSinkView.swift, find the customButtonsView property and add the following two modifiers to the first button:

// 1
.buttonStyle(.gradient)
// 2
.disabled(isDisabled)

Here’s what the two modifiers do:

  1. This is how you apply any default or custom style to a button. Because you made a static variable in the ButtonStyle earlier, you were able to use it by calling .gradient instead of explicitly writing GradientStyle.()
  2. While the disabled state will automatically be provided by the environment, you’re explicitly binding the disabled state to a local property. You’ll see how this works when you run the app.

Build and run. You should see the button with a nice mint gradient.

Enabled custom button

Turn on the toggle for Disable Buttons and you’ll see how the button shows with different colors to reflect the “disable” state.

Disabled custom button

Now, modify the second button with the same style. This time, you’ll provide your own colors. Still in customButtonsView, add the following modifiers to the second button:

.buttonStyle(GradientStyle(colors: [.red, .yellow, .green]))
.disabled(isDisabled)

Here, you used the initializer provided by GradientStyle to pass your own colors.

Build and run. You’ll see your new button with a rainbow-like gradient.

Custom colors in custom button style