A ruby-inspired Swift-specific UIKit-tip

~ 4 mins

You ever code views? I do. Not all of them, all the time, but some times. I’d like to share with you a little helper I’ve been using.

import Foundation
import UIKit

protocol SubviewAddable {}
extension UIView: SubviewAddable {}

extension SubviewAddable where Self: UIView {
    func add<Subview: UIView>(subview: Subview, fn: (Subview, Self) -> Void) -> Subview {
        fn(subview, self)
        return subview

It’s helpful in that it allows you to add a subview, and within the same place configure the subview (optionally using its superview). Usage is rather simple:

let subview = view.add(subview: MyAmazingView()) { amazingView, superview in
    // configure amazingView
    // maybe constrain it to superview?

While small, it brings us a few really nice benefits:

Answers to some things that might not be obvious:

A full, working example, might look like this:

class LoadingViewController: UIViewController {
    weak var loadingView: UIView!

    override func viewDidLoad() {

        view.backgroundColor = .blue

        // Using it here to to add and configure the subview before weakly
        // storing a reference to it for later
        loadingView = view.add(subview: UIView()) { loadingView, view in
            loadingView.backgroundColor = UIColor(white: 0, alpha: 0.6)

            loadingView.translatesAutoresizingMaskIntoConstraints = false
                loadingView.topAnchor.constraint(equalTo: view.topAnchor),
                loadingView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                loadingView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                loadingView.leadingAnchor.constraint(equalTo: view.leadingAnchor)

            // Using it here to configure a spinner, and save it temporarily for
            // constraining the label to it
            let spinnerView = loadingView.add(subview: UIActivityIndicatorView()) { spinnerView, contentView in
                spinnerView.hidesWhenStopped = false

                spinnerView.translatesAutoresizingMaskIntoConstraints = false
                    spinnerView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
                    spinnerView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)

            // Using it here mainly to configure the label
            loadingView.add(subview: UILabel()) { textView, contentView in
                textView.textColor = .white
                textView.text = "Loading…"

                textView.translatesAutoresizingMaskIntoConstraints = false
                    textView.topAnchor.constraint(equalTo: spinnerView.bottomAnchor, constant: 8),
                    textView.centerXAnchor.constraint(equalTo: spinnerView.centerXAnchor)

That’s all! Any questions, or feedback? Send me a tweet, or an e-mail.

rss facebook twitter github youtube mail spotify instagram linkedin google pinterest medium rubygems