One of the most often quoted upsides of SwiftUI is its declarative nature. However, a pretty similar declarative UIKit approach can be achieved by introducing only a single generic extension. If you are in the process of introducing SwiftUI to an existing UIKit codebase, you may consider updating your views to use a more declarative UIKit approach by arranging UI code into stack view, spacer, and modifier based hierarchies.

TL;DR
This article uses a pretty simple generic extension that helps you to create inline builders for UIKit classes (and more). You can just grab the project code at GitHub, and take a look at the source. Even better, you can start using it by grabbing the Swift package at Withable.
However, being introduced to the considerations behind, and inspecting the variety of the possible use cases can leave you with a more reusable knowledge, applicable across a wider range of potential use cases.
Declarative UIKit using self-executing closures
Efforts to escape the imperative nature of UIKit are around for a while. Amongst the first steps in that direction is using lazy evaluated self-executing closures. So instead of instantiating and configuring everything in viewDidLoad, you can split your subviews into variables, and instantiate and configure them at the declaration part of your view controller like below.
class ContentViewController: UIViewController {
...
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = viewModel.title
label.textColor = .label
label.font = .preferredFont(forTextStyle: .largeTitle)
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(titleLabel)
...
}
}
You can think of it as a two-step process, where first you create a closure variable (that takes no parameters and returns a label), then a variable that calls that closure upon access and stores its resulting label. The corresponding imperative counterpart for the code above would look almost the same, yet with much less separation.
💡 You could define the titleLabel property as a let variable as well, however, in that case the closure will be evaluated before the declaring class is initialized. That means that inside the closure the self reference won’t refer to the instance of the declaring class. Instead, it holds a reference to self method of NSObject. For more details, see Jesse Squires‘ exhaustive deep dive at What type is self in a Swift self-executing anonymous closure used to initialize a stored property? So if you need a self reference in the closure body, then make sure to declare the property as a lazy var (yet be aware that the closure will only be evaluated only on first access).
class ContentViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
let titleLabel = UILabel()
titleLabel.text = viewModel.title
titleLabel.textColor = .label
titleLabel.font = .preferredFont(forTextStyle: .largeTitle)
view.addSubview(titleLabel)
...
}
}
UILabel extension to configure instances inline
The method above already helps a lot with clarity, yet it still carries over some extra boilerplate. In addition, without defining an explicit type (either for the variable or for the closure) the compiler can’t figure out the resulting type, which makes the self-calling closure method pretty cumbersome to compose. However, using a similar closure based approach, we can create an extension on UILabel called with { } that enables us to build labels with less code.
extension UILabel {
func with(_ closure: (UILabel) -> Void) -> UILabel {
closure(self)
return self
}
}
Having that extension, we can now instantiate and configure UILabel instances right at the view declaration part of our views. Besides sparing the temporary variable boilerplate, you can see how the compiler can now infer the type information of the return type of the closure from the context.
class ContentViewController: UIViewController {
...
lazy var titleLabel = UILabel()
.with {
$0.text = viewModel.title
$0.textColor = .label
$0.font = .preferredFont(forTextStyle: .largeTitle)
}
...
}
💡 The method implements pretty classic patterns. You can think of it as something between an unspecialized/parametric builder, or a decorator with customizable/pluggable decorating behaviour.
While this alone can help a lot in clearing up initialization methods, the true power lies where we are free to use these inline configurators for any class.
NSObject extension to configure any instance inline
In order to create an extension that works with any type (any class), we can create a generic protocol Withable that basically does the same as the UILabel extension above.
protocol Withable {
associatedtype T
@discardableResult func with(_ closure: (_ instance: T) -> Void) -> T
}
extension Withable {
@discardableResult func with(_ closure: (_ instance: Self) -> Void) -> Self {
closure(self)
return self
}
}
extension NSObject: Withable { }
💡In order to let the compiler inferring the corresponding type, the protocol has to be generic, then specialized in the default implementation plugging in Self as the associated type. Having that, every subclass of NSObject (or any conforming class) can inherit the extension with the correct Self type.
With this single extension in place we can now call with { } on any UIKit or Foundation types, which can transform a myriad of APIs into more usable, declarative interfaces (not to mention all the temporary variables that can be removed along the way). May think of it as some kind of map { } yet only for a single element.
🔄 Not long after I released the article, I discovered Nick Lockwood‘s Withable implementation on GitHub. Besides taking my breath away with the name match, it contains a pretty convenient variant to make the initializers inlineable themselves. Also, it gives a nice description of the benefits, referencing Point-free coding style amongst others.
Declarative UIKit examples
Buttons can be declared and configured upfront with their target actions.
lazy var submitButton = UIButton()
.with {
$0.setTitle("Submit", for: .normal)
$0.addTarget(self, action: #selector(didTapSubmitButton), for: .touchUpInside)
}
View controller presentation style can be configured inline.
present(
DetailViewController()
.with {
$0.modalTransitionStyle = .crossDissolve
$0.modalPresentationStyle = .overCurrentContext
},
animated: true
)
Alert view actions can be declared without temporary variables.
present(
UIAlertController(title: title, message: message, preferredStyle: .alert)
.with {
$0.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
},
animated: true
)
Date formatting can be inline yet fully configurable.
let today = DateFormatter()
.with {
$0.dateStyle = .medium
$0.locale = Locale(identifier: "en_US")
}
.string(from: Date())
Any class can benefit really.
lazy var displayLink = CADisplayLink(target: self, selector: #selector(update))
.with {
$0.isPaused = true
$0.preferredFramesPerSecond = 120
$0.add(to: RunLoop.main, forMode: .common)
}
Even value types as well (after conforming to Withable).
extension PersonNameComponents: Withable { }
let name = PersonNameComponents()
.with {
$0.givenName = "Geri"
$0.familyName = "Borbás"
}
Also, can be nicely composed to create more complex compound statements with a single return value that can be inlined into any context. Presenting an action sheet that presents a configured modal view for each action selected all can be composed into a single statement.
present(UIAlertController(title: "Select file", message: nil, preferredStyle: .actionSheet)
.with {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
$0.addAction(
UIAlertAction(title: "From camera", style: .default) { _ in
self.present(UIImagePickerController()
.with {
$0.delegate = self
$0.sourceType = .camera
$0.cameraCaptureMode = .photo
},
animated: true
)
}
)
}
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
$0.addAction(
UIAlertAction(title: "From library", style: .default) { _ in
self.present(UIImagePickerController()
.with {
$0.delegate = self
$0.sourceType = .photoLibrary
},
animated: true
)
}
)
}
$0.addAction(
UIAlertAction(title: "Cancel", style: .cancel) { _ in }
)
},
animated: true
)
While you may think the above is already a great quality of life improvement, I found Withable the most useful for cutting down the boilerplate and enabling composition when declaring UIStackView hierarchies in UIKit code.
Stack view hierarchy in declarative UIKit
Being stack view based layouts a huge part of the SwiftUI mindset, auto-layout heavy UIKit codebases can benefit by slowly rebuilding their views using stack views (and spacers). Having that they can be replaced with SwiftUI views much easier later on. Yet building stack views in code using vanilla UIKit is still pretty cumbersome.
💡Using UIStackView has various benefits that makes it an ideal target to introduce them to your codebase (in case you have not done already). Besides creating the auto-layout constraints for us for free, stack views are became the principal layout tool in flagship UX design tools. For example, Figma released significant updates on how it manages stack view layout and content hugging (see Behind the feature: the making of the new Auto Layout for more).
There are a couple of frameworks out there that aims to solve this problem, yet I think introducing a third-party framework dependency in any project can always be a double-edged sword to say at least. This article focuses on providing a pretty cheap alternative to libraries, where you can introduce only a single piece of code to the codebase, and to your team (both present and future). The cognitive load to start using Withable is pretty negligible compared to the shift that it can set in motion regarding readability, maintainability, and composability. Take this example below.

class WithableViewController: UIViewController {
let viewModel = Planets().earth
private lazy var stackView = UIStackView().with {
$0.axis = .vertical
$0.spacing = CGFloat(10)
[
UILabel().with {
$0.text = viewModel.title
$0.textColor = .label
$0.font = .preferredFont(forTextStyle: .largeTitle)
},
UIImageView().with {
$0.image = UIImage(named: viewModel.imageAssetName)
},
UILabel().with {
$0.text = """
size: \(viewModel.properties.size)
distance: \(viewModel.properties.distance)
mass: \(viewModel.properties.mass)
"""
$0.textColor = .systemGray
$0.numberOfLines = 0
$0.font = .preferredFont(forTextStyle: .headline)
},
UILabel().with {
$0.text = viewModel.paragraphs.first
$0.textColor = .label
$0.numberOfLines = 0
$0.font = .preferredFont(forTextStyle: .footnote)
},
UILabel().with {
$0.text = viewModel.paragraphs.last
$0.textColor = .label
$0.numberOfLines = 0
$0.font = .preferredFont(forTextStyle: .footnote)
},
UIView()
].add(to: $0)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
view.addSubview(stackView)
stackView.with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 30).isActive = true
$0.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30).isActive = true
$0.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 30).isActive = true
$0.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -30).isActive = true
}
}
}
extension Array where Element: UIView {
func add(to stackView: UIStackView) {
forEach { stackView.addArrangedSubview($0) }
}
}
You can see how using Withable can declare the stack view hierarchy above in a single statement (besides pinning the edges to the view controller). Within each with { } closure, you can configure the instance inline without having to introduce temporary variables for each, or defining types for any of the closures. 🤯
🗒 In production you may want to use some framework for bindings, and making constraints, yet those considerations intentionally opted-out from the scope of this writing to focus clarity.
Bonus 1
🎁 Extarct repeating patterns to styles
Once you start to adjust to this way of thinking about the instances (and their configurations), probably you’ll also start to think about extracting repeating configurations into composable style definitions. Even better, the design system you work with may already have some styles defined that should be reused between UI components.
In the Figma file for this article, I created some basic styles, so they can be reused in various upcoming components, screens. Using Withable you may create fairly expressive style extensions to corresponding UIKit classes. Keeping the with naming convention around may communicate that the expressions keep returning the instance.
extension UILabel {
var withTitleStyle: Self {
with {
$0.textColor = .label
$0.font = .preferredFont(forTextStyle: .largeTitle)
}
}
var withPropertyStyle: Self {
with {
$0.textColor = .systemBackground
$0.font = .preferredFont(forTextStyle: .headline)
$0.setContentCompressionResistancePriority(.required, for: .vertical)
}
}
var withPropertyValueStyle: Self {
with {
$0.textColor = .systemGray
$0.font = .preferredFont(forTextStyle: .body)
}
}
var withParagraphStyle: Self {
with {
$0.textColor = .label
$0.numberOfLines = 0
$0.font = .preferredFont(forTextStyle: .footnote)
}
}
}
Using the same approach you can introduce various convenience extensions to further cut down boilerplate.
extension UILabel {
func with(text: String?) -> Self {
with {
$0.text = text
}
}
}
extension UIStackView {
func horizontal(spacing: CGFloat = 0) -> Self {
with {
$0.axis = .horizontal
$0.spacing = spacing
}
}
func vertical(spacing: CGFloat = 0) -> Self {
with {
$0.axis = .vertical
$0.spacing = spacing
}
}
func views(_ views: UIView ...) -> Self {
views.forEach { self.addArrangedSubview($0) }
return self
}
}
Having all the above, you can see how declarative UIKit is taking shape, as the code moves from setting properties on temporary variables all over the place to build compact view declarations with various components declared upfront instead of buried into viewDidLoad implementations. You can nicely break down even more complex layouts into nested stack views hierarchies. The entire code for this view fits into this ~60 lines of code below.
class ContentViewController: UIViewController {
let viewModel = Planets().earth
private lazy var body = UIStackView().vertical(spacing: 10).views(
UILabel()
.with(text: viewModel.title)
.withTitleStyle,
UIStackView().vertical(spacing: 5).views(
UIStackView().horizontal(spacing: 5).views(
UILabel()
.with(text: "size")
.withPropertyStyle
.withBox,
UILabel()
.with(text: viewModel.properties.size)
.withPropertyValueStyle,
UIView.spacer
),
UIStackView().horizontal(spacing: 5).views(
UILabel()
.with(text: "distance")
.withPropertyStyle
.withBox,
UILabel()
.with(text: viewModel.properties.distance)
.withPropertyValueStyle,
UIView.spacer
),
UIStackView().horizontal(spacing: 5).views(
UILabel()
.with(text: "mass")
.withPropertyStyle
.withBox,
UILabel()
.with(text: viewModel.properties.mass)
.withPropertyValueStyle,
UIView.spacer
)
),
UIImageView()
.with(image: UIImage(named: viewModel.imageAssetName)),
UILabel()
.with(text: viewModel.paragraphs.first)
.withParagraphStyle,
UILabel()
.with(text: viewModel.paragraphs.last)
.withParagraphStyle,
UIView.spacer
)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(body)
view.backgroundColor = .systemBackground
body.pin(
to: view.safeAreaLayoutGuide,
insets: UIEdgeInsets(top: 30, left: 30, bottom: 30, right: 30)
)
}
}
💡 Besides stack views, these chainable style builders are relatively similar to a SwiftUI protocol called ViewModifier, while also sharing similar considerations regarding the declared order of the modifiers. You can also see how the layout above uses spacer views (similar to SwiftUI Spacer views) to squeeze content within a stack view.
The code above uses some more complex style that wraps a label into a box with padding, by modifying a UILabel into a UIView while setting the appropriate styles and constraints. I encourage you to take a closer look at the source in the corresponding project repository. You can see there how Withable extensions can be used to implement more nuanced component layouts.
Used by Apple
Later on, I found out that on occasions Apple uses the very same pattern to enable decorating objects inline. These decorator functions are even uses the same with naming convention. These examples below are in vanilla UIKit. 🍦
let arrow = UIImage(named: "Arrow").withTintColor(.blue) let mail = UIImage(systemName: "envelope").withRenderingMode(.alwaysTemplate) let color = UIColor.label.withAlphaComponent(0.5)
See UIImage.withTintColor(_:), UIImage.withAlphaComponent(_:), UIImage.Configuration.withTraitCollection(_:), and even more examples at UIImage.Configuration to mention a few.
Bonus 2
📦 Using SwiftUI Previews for UIKit views
Once you start breaking down views into various self-contained components, you may often find you may want quicker iteration loops on some layout. One way to fight that is to use SwiftUI PreviewProvider type to be able to provision your views without build and run the project every time.
PreviewProvider is built to preview SwiftUI views primilarly, yet thanks to :UIKit: interoperability, we may create a generic SwiftUI view that can be initialized with any UIKit UIViewController.
struct PreviewView<ViewControllerType: UIViewController>: UIViewControllerRepresentable {
let `for`: ViewControllerType
func makeUIViewController(context: Context) -> ViewControllerType {
`for`
}
func updateUIViewController(_ viewController: ViewControllerType, context: Context) {
}
}
🗒 The backticks above around for may be confusing at first. They are there to enable the usage of the reserved for keyword in a project API. It makes way more sense when you are looking at how the synthesized initializer for this struct looks like at the call site below.
Having that, with the following code Xcode will populate the Canvas with a preview for the view controller injected via PreviewView(for:) initializer. You can create multiple previews in the same file for various color schemes, devices, and more. It worth a research on its own if you are planning to use it in your day to day work.
struct DetailViewController_Previews: PreviewProvider {
static var previews: some View {
PreviewView(for: ContentViewController())
.environment(\.colorScheme, .dark)
.edgesIgnoringSafeArea(.all)
}
}
Conclusion
Writing declarative code, using stack views and spacers are not unique to SwiftUI in any way. The opportunity to use a considerably similar approach in existing UIKit projects is wide open. Using similar minor extensions discussed in this article you can start “soft-applying” these principles without committing/locking-in your project to third-party libraries.
If you are at the stage of thinking about migrating existing UIKit projects to SwiftUI, you may consider a more granular approach, where you first adopt the SwiftUI mindset and apply it to existing and/or upcoming UIKit view controllers. Starting to deal with spacers, various layout priorities in stack views right in UIKit will be near 1-to-1 applicable when reasoning about SwiftUI layouts later down the line.
DISCLAIMER. THE INFORMATION ON THIS BLOG (INCLUDING BUT NOT LIMITED TO ARTICLES, IMAGES, CODE SNIPPETS, SOURCE CODES, EXAMPLE PROJECTS, ETC.) IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE INFORMATION ON THIS BLOG (INCLUDING BUT NOT LIMITED TO ARTICLES, IMAGES, CODE SNIPPETS, SOURCE CODES, EXAMPLE PROJECTS, ETC.).