Exploring the New UISegmentedControl Initializer Available in iOS 14
The first beta SDK for iOS 14 was released on Monday, 22 June 2020 alongside the
first developer beta build of the OS, bringing with it numerous new APIs and
enhancements. One such enhancement is the new UIAction
s-based
init(frame:actions:)
initializer.
Before we take a look at how the new initializer works, let's go through the
process of setting up a UISegmentedControl
in iOS 13. A segmented control can
be initialized with an array of strings that will become the segment titles in
the UI.
let control = UISegmentedControl(items: ["Red", "Green", "Blue"])
Next we need to add a target to the segmented control with a selector to be executed when the control's primary action is triggered.
control.addTarget(
self,
action: #selector(didTapSegmentedControl),
for: .primaryActionTriggered
)
Finally, we need to define the @objc
-marked function that we passed to the
selector above.
@objc private func didTapSegmentedControl(_ sender: UISegmentedControl) {
let textColor: UIColor
switch sender.selectedSegmentIndex {
case 0: textColor = .systemRed
case 1: textColor = .systemGreen
case 2: textColor = .systemBlue
default: textColor = .label
}
label.textColor = textColor
}
To the developer who has been writing UIKit code since they started development, this seems routine. However, this three step dance provides a large surface area for bugs, e.g. the manually configured binding between the control titles and the switching over the selected segment index. I could change the order of the strings in the segmented control initializer and forget to update the switch case in the selector function and this will lead to inconsistency in the UI.
The new initializer in iOS 14 SDK is based on the UIAction
type introduced
last year in iOS 13. An UIAction
has an initializer that takes a title string
and a closure to be run when the action is invoked. Here is a sample action:
let action = UIAction(title: "Title") { (action: UIAction) -> Void in
<#Code#>
}
UISegmentedControl
's new initializer takes an array of such actions and
generates segments with the given title and executes the actions' closures when
their corresponding segments are selected.
enum Color: String, CaseIterable {
case red = "Red"
case green = "Green"
case blue = "Blue"
var uiColor: UIColor {
switch self {
case .red: return .systemRed
case .green: return .systemGreen
case .blue: return .systemBlue
}
}
}
let segmentedControl = UISegmentedControl(
frame: <#CGRect#>,
actions: Color.allCases.map { [unowned label] color in
UIAction(title: color.rawValue) { _ in
label.textColor = color.uiColor
}
}
)
This new initializer configures everything about the segmented control in one
place, the number of segments, their titles, and the code to run when a segment
is tapped, making your code more organised and readable. In addition to this
initializer, UISegmentedControl
has new APIs to access an action at a segment
, set an action at a segment, and insert an action at a segment.
In conclusion, this move away from target-actions to closure-based UIAction
s
is one more step in the direction of the Swiftification of UIKit. A
UIAction
-based initializer are also available for UIControl
and all of its
subclasses, including your custom subclasses not predefined by UIKit.
A demo app with both the old target-action as well as the new closure-based
UIAction
implementations is available on GitHub.