Search code examples
iosswiftuikitviewcontrollerlazy-initialization

Having to add subview to view controller twice with lazily instantiated UI views


When programmatically adding a lazily instantiated text field to my viewcontroller, I only got it to work when calling view.addSubview(field) twice, in the initiation of the textview, and in viewDidLoad(). Removing the one in the initializer causes a crash, and removing the one in viewDidLoad() doesn't let the text field appear.

class VC: UIViewController {
    override func viewDidLoad() {
        view.addSubview(textField)
        view.setNeedsUpdatedConstraints)
    }

    lazy var textField: UITextField! = {
        let field = UITextField()
        field.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(field)

        field.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        field.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        field.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true
        field.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1).isActive = true

        return field
     } ()
}

Removing the view.addSubview(textField) in viewDidLoad() does not cause an error, but nothing shows up. Removing the view.addSubview(field) in the initializer causes a crash with the following error: Unable to activate constraint with anchors <NSLayoutXAxisAnchor:0x600000469380 "UITextField:0x7fc931023600.centerX"> and <NSLayoutXAxisAnchor:0x600000469480 "UIView:0x7fc92f60c690.centerX"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.

Why are both calls necessary? Does this cause problems? What would be a better way of doing this? Thanks.


Solution

  • You need to add the text field as a subview in the textField initializer because of the constraints. You can't setup the constraints until the view has been added.

    You seem to need the call to add the text field in viewDidLoad because without it, the lazy initializer is never called. So you don't actually need to call addSubview(textField). You just need any reference to textField to trigger the initializer.

    Unrelated but there is no reason for textField to be declared as implicitly unwrapped.