Creating UI programmatically gives better control over various UI components and helps in writing modular, reusable UI components. Also this makes it easier when working as a team were multiple developers can work on same UI components, resolve code conflicts quickly as opposed to fixing storyboard XML file conflicts, which are harder to understand. There are some repeating patterns one will encounter when working with view and view controllers. We can make these modular as shown in the sample code.

class ViewController: UIViewController {
    private lazy var submitBtn: UIButton = {
        let b = UIButton(type: .system)
        b.translatesAutoresizingMaskIntoConstraints = false
        b.setTitle("Submit", for: .normal)
        return b
    }()

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    override func loadView() {
        super.loadView()
        initUI()
        initConstraints()
    }

    override func viewDidLayoutSubviews() {
        initStyle()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        initDelegates()
        initEvents()
        initNotifications()
        initData()
    }

    func initUI() {
        self.view = UI.view()  // set the view for the view controller
        self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.view.backgroundColor = UIColor.white  // set the default view color to white
        // add sub views...
    }
}
class UI {
    static func view() -> UIView {
        let screenSize = UIScreen.main.bounds
        let v = UIView(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
        v.backgroundColor = UIColor.white
        return v
    }
}

Here in loadView() method, we initialize the UI using the initUI() method, where we write any UI related code to display the view controller. We can use a static class UI.swift for common UI related methods, which can be used from any view controller. We can then build the UI by adding subviews, with independent components as lazy variables, which can be accessed from other parts of the code for getting values, adding events to it and such. We can further customize this by moving all UI related code to a separate view class if the view controller has a lot of functionalities.

Once the UI is set up, next we need to add constraints. Constraints for each UI component will be added separately using the NSLayoutConstraint.activate([]) method. Make sure that the UI components have translatesAutoresizingMaskIntoConstraints set to false.

NSLayoutConstraint.activate([
    submitBtn.topAnchor.constraint(equalTo: self.formView.bottomAnchor, constant: 8),
    submitBtn.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 16),
    submitBtn.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -16),
    submitBtn.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -8)
])

Here, the difference between programmatic constraint and storyboard is that the bottom and trailing units need to be in negative where as the same constraint when added in Interface Builder, we use positive values. In the viewDidLayoutSubviews() method, we call initStyles() to add any style to UI elements, at which point we will have the UI initialized properly with constraints and adding properties like border, color etc will work because the UI component's frame is not of CGRect.zero. Once the constraints are setup, we will set the delegates of any UI components and objects. Then we add target-action for UI elements in initEvents() method. If the view controller needs to listen to certain notifications, we add that in the initNotifications() method. Next we call initData() where we populate any model variables which is required for displaying, like in case of table view as well as making calls to service layer to fetch or update data.