Big improvements to UIButton in iOS 15

Predefined styles. Trailing images! Loading style and much more.

Published: June 9, 2021
See books

I think this is one quite a welcome surprise. The UIButton got a lot of improvements with iOS 15. There is a new API to allow configuration with predefined styles, unified way to update state, option to finally have image in the trailing position and even loading style.

UIButton also support Dynamic Type and multiline text by default!

Let's look at all this new goodness.

Predefined styles

Prior to iOS 15 we had to manually customize our buttons, unless we were satisfied with the default "blue label" style.

With iOS 15 we have three new styles and these are Tinted, Filled and Gray. To use them, we have the UIButton.Configuration as a configuration struct that we can pass to new initializer.

It may look like this:

var tinted = UIButton.Configuration.tinted()
tinted.title = "Tinted button"
tinted.buttonSize = .large

let tintedButton = UIButton(configuration: tinted, primaryAction: nil)

What is great about this approach is that we can predefine our own configurations and then use them thorough the app to have unified buttons without the needing of subclassing and all the issues that may bring.

As you can see above, we can specify the buttonSize. The .large option will get you height of 50pt. Other sizes are .medium which results in 34pt tall button and finally .small with the height of 28pt.

Below are all the three styles with .large sizing in a stack view:

iOS 15 Buttons - filled, tinted and gray style

I think this is pretty good improvement already. The buttons also have nice touch feedback effect that dims the background a bit.

The colors are not set in stone. You can use the properties baseBackgroundColor and baseForegroundColor on the configuration to customize this:

var tinted = UIButton.Configuration.tinted()
tinted.title = "Tinted button"
tinted.buttonSize = .large
tinted.baseBackgroundColor = UIColor.systemGreen
tinted.baseForegroundColor = UIColor.white

Subtitles and images

And we are just getting started. Buttons can now have subtitle. You can set it via the configuration. This applies also to images which were already supported, but now we can position them after the text label.

var filled = UIButton.Configuration.filled()
filled.title = "Filled button"
filled.buttonSize = .large
filled.subtitle = "With subtitle even"
filled.image = UIImage(systemName: "bubble.left.fill")
filled.imagePlacement = .trailing
filled.imagePadding = 5

let button = UIButton(configuration: filled, primaryAction: nil)

And the result:

iOS 15 Buttons - subtitle and trailing image

Updating buttons based on state

There is new and centralized way to update the button to reflect the current state of the app. You can also this to provide custom touch feedback.

For example the "pressed in" effect:

tintedButton.configurationUpdateHandler = { button in
    button.transform = button.isHighlighted ? CGAffineTransform(scaleX: 0.95, y: 0.95) : .identity

Above I used the button directly, because of the transform. In cases where the modifications can be done on the configuration, you should use something like this:

tintedButton.configurationUpdateHandler = { button in
        var config = button.configuration
        config?.title = button.isHighlighted ? "Pressed button" : "Tinted button"
        button.configuration = config

Also keep in mind to use unowned self when you need to access external state from this handler:

tintedButton.configurationUpdateHandler = { [unowned self] button in
        button.isEnabled = !self.inProgress

This will disable the button when our state indicates that something is in progress. It won't work just yet, because the UIButton has no idea that something changed.

In these cases we need to call the method setNeedsUpdateConfiguration() on the appropriate button to request update.

tintedButton.setNeedsUpdateConfiguration() // 🎉

Showing activity indicator

This is another small thing that will collectively save so much time 😃 We won't have to manually add UIActivityIndicator to buttons. Instead we can toggle single property on the configuration:

buttonConfig.showsActivityIndicator = true

Sure, we don't get absolute control, but I think this is huge win nonetheless. It also seems that the button will be smart enough to replace image (if it is in the leading position) or even an entire content if the size is too small to accommodate all the elements 🎉

I am really looking forward to using these new APIs!

See Meet the UIKit button system from WWDC21 for more details.

Uses: Xcode 13 & Swift 5.5

Filip Němeček profile photo


Filip Němeček

iOS blogger and developer with interest in Python/Django. Want to see most recent projects? 👀

iOS blogger and developer with interest in Python/Django. Want to see most recent projects? 👀