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 {
    @discardableResult
    func add<Subview: UIView>(subview: Subview, fn: (Subview, Self) -> Void) -> Subview {
        addSubview(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() {
        super.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
            NSLayoutConstraint.activate([
                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.startAnimating()

                spinnerView.translatesAutoresizingMaskIntoConstraints = false
                NSLayoutConstraint.activate([
                    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
                NSLayoutConstraint.activate([
                    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