Button Configuration in iOS 15

Apple gave buttons a big upgrade in iOS 15. You now create and update button configurations much like the changes Apple introduced for collection and table view cells in iOS 14.

Four Basic Styles

Starting in Xcode 13, there are four basic pre-defined styles for a button. When you drag a button from the object library onto the Interface Builder canvas it has a plain style. Use the attributes inspector to select one of the other styles:

Four button styles - plain, gray, tinted and filled

If you’re supporting iOS 14 or earlier you’ll need to change the button style to “Default”. You can then further customize the button configuration in the attributes inspector or in code.

Button Configurations

If you’re creating your buttons in code the, new in iOS 15, button configuration replaces many of the old methods and properties of UIButton: You can set the configuration directly on the button:

let button = UIButton(type: .system)
button.configuration = .plain()
button.configuration = .gray()
button.configuration = .tinted()
button.configuration = .filled()      

To customize one of the predefined configurations, first make a copy:

var config = UIButton.Configuration.filled()
config.title = "Custom Filled Button"
...
button.configuration = config

If you’ve adopted the UIAction closure based actions from iOS 13, pass the configuration when creating the button:

let button = UIButton(configuration: config,
  primaryAction: UIAction() { _ in
    print("Go")
   })

Customizing The Button

When customizing the button you change the configuration rather than setting properties directly on the button. Let’s look at some of the options:

Titles and Subtitles

The button title and subtitle can be plain text or attributed strings:

var config = UIButton.Configuration.filled()
config.title = "Start"
config.subtitle = "Both Engines"

Blue button with title and subtitle, leading alignment

If you’re using a subtitle you can change the alignment with and the padding to the title:

config.titleAlignment = .center
config.titlePadding = 4.0

Blue button with title and subtitle, center aligned with increased padding

Button Colors

You can set the base background and foreground color of the button. The button may change these base colors when in different states (for example when highlighted):

config.baseBackgroundColor = .green
config.baseForegroundColor = .black

Green button with black label text

For greater control of the background, UIButton supports the UIBackgroundConfiguration introduced in iOS 14 for table and collection view cells:

config.background.backgroundColor = .systemYellow
config.background.strokeColor = .systemRed
config.background.strokeWidth = 4.0

Yellow button with red outline

Corner Style

The default corner style is dynamic which adjust the corner radius for the dynamic type size. You can also choose fixed, small, medium, large and capsule:

config.cornerStyle = .capsule

Capsule corner style

Image Placement

When you add a foreground image to the button you can control the padding to the title, the placement (top, trailing, bottom, leading) and the symbol configuration:

config.image = UIImage(systemName: "car",
  withConfiguration: UIImage.SymbolConfiguration(scale: .large))
config.imagePlacement = .trailing
config.imagePadding = 8.0

Blue start button with car image on right

Activity Indicator

Setting the showsActivityIndicator property replaces the image with an activity indicator:

config.showsActivityIndicator = true

Start button with activity indicator

Button Size

You can request a preferred size for the button. Interface Builder hides this in the size inspector:

config.buttonSize = .large

Large button size

Content Insets

The content insets give you padding between the bounds of the button and the content (title and image):

config.contentInsets = NSDirectionalEdgeInsets(top: 10,
  leading: 20, bottom: 10, trailing: 20)

Content insets

Configuration Update Handler

To change the appearance of the button in response to a change in state register a configuration update handler. For example, to switch between a filled and outline image when the button is in a highlighted state:

button.configurationUpdateHandler = { button in
  var config = button.configuration
  config?.image = button.isHighlighted ?
    UIImage(systemName: "car.fill") :
    UIImage(systemName: "car")
  button.configuration = config
}

Button with filled car image when highlighted

To extend the example, suppose I have a property holding the range of my car that I want to show in the button subtitle:

private var range = Measurement(value: 100,
                                 unit: UnitLength.miles)
private lazy var formatter = MeasurementFormatter()

Adding the subtitle to my configuration update handler:

button.configurationUpdateHandler = { [unowned self] button in
  var config = button.configuration
  ...            
  config?.subtitle = self.formatter.string(from: self.range)
  button.configuration = config
}

Then we call setNeedsUpdateConfiguration on the button in the didSet property observer to update the subtitle whenever the range changes:

private var range = Measurement(value: 100,
  unit: UnitLength.miles) {
  didSet {
    button.setNeedsUpdateConfiguration()
  }
}

Tinted Start button with car image and 100 miles subtitle

Further Details