Greg Greg - 10 days ago 8
iOS Question

Why is my UILabel nil after creating UIView programmatically from XIB? (code attached)

Question: Why is UILabel nil here (code attached) in init after creating UIView programmatically from XIB? (refer to code below) That is the line

label1.text = "TBC - It was updated"
throws an error

Background: I want to programmatically create multiple custom views, multiple GCDateView's in this case. I want to use a
XIB
file to layout the custom view with an associated class to finalise customisations programmatically too, hence here I have a GCDateView.swift and a GCDateView.xib file.

Aside: As a 2nd aside question I note the view I create within the GCDateView from the xib file can't be directly allocated to be the main view (e.g. at the end of init I can't say
self = gcDateViewView
). Perhaps I need a separate question for this.

From within parent controller/view:

let dv = GCDateView()


GCDateView:

import UIKit

class GCDateView : UIView {

@IBOutlet weak var label1: UILabel!

func commonInit() {
// Programmtically use XIB file
if self.subviews.count == 0 {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "GCDateView", bundle: bundle)
let gcDateViewView : UIView = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
gcDateViewView.frame = self.bounds
gcDateViewView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(gcDateViewView)
}
**label1.text = "TBC - It was updated" // ** ERROR: label1 was Nil ****
}

override init(frame: CGRect) {
NSLog("GCDateView: Init frame")
super.init(frame: frame)
commonInit()
}

required init?(coder aDecoder: NSCoder) {
NSLog("GCDateView: Init decoder")
super.init(coder: aDecoder)
commonInit()
}

}

Answer

IBOutlet instances are not initialized in init?(coder aDecoder: NSCoder).
This process is done in separate step.

There is method awakeFromNib where it's guaranteed that all IBOutlet instances are initialized:

override func awakeFromNib() {
    super.awakeFromNib()
    label1.text = "TBC - It was updated" // Won't crash.
}

To solve your second problem (i.e. avoid adding another instance of self type as self subview) I recommend to create class method that will create new instance of GCDateViewby loading it from xib.
Here is updated code:

import UIKit

class GCDateView : UIView {

    @IBOutlet weak var label1: UILabel!

    class func loadFromXIB() -> GCDateView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: "GCDateView", bundle: bundle)
        let gcDateView = nib.instantiate(withOwner: self, options: nil)[0] as! GCDateView
        gcDateView.frame = self.bounds
        gcDateView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        gcDateView.label1.text = "TBC - It was updated"

        return gcDateView
    }
}

Usage:

let dateView = GCDateView.loadFromXIB()
Comments