Gralex
Gralex

Reputation: 4485

IBOutlet properties nil after custom view loaded from xib

Something strange going on with IBOutlets. enter image description here

In code I've try to access to this properties, but they are nil. Code:

class CustomKeyboard: UIView {

    @IBOutlet var aButt: UIButton!
    @IBOutlet var oButt: UIButton!

    class func keyboard() -> UIView {
        let nib = UINib(nibName: "CustomKeyboard", bundle: nil)
        return nib.instantiateWithOwner(self, options: nil).first as UIView
    }

    override init() {
        super.init()
        commonInit()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    // MARK: - Private
    private func commonInit() {
        println(aButt)
        // aButt is nil

        aButt = self.viewWithTag(1) as UIButton
        println(aButt)
        // aButt is not nil
    }
}

Upvotes: 36

Views: 27403

Answers (6)

fz.
fz.

Reputation: 3233

That's expected, because the IBOutlet(s) are not assigned by the time the initializer is called.

Instead of calling commonInit() in init(coder:), do that in an override of awakeFromNib as follows:

// ...

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

override func awakeFromNib() {
    super.awakeFromNib()

    commonInit()
}

// ...

Upvotes: 72

Abhishek B
Abhishek B

Reputation: 187

There is possibility that you not mentioned the FileOwner for xib. Mention its class in File owner not in views Identity Inspector .

Upvotes: -1

Chris Prince
Chris Prince

Reputation: 7584

Building on @ScottyBlades, I made this subclass:

class UIViewXib: UIView {
    // I'm finding this necessary when I name a Xib-based UIView in IB. Otherwise, the IBOutlets are not loaded in awakeFromNib.
    override func awakeAfter(using aDecoder: NSCoder) -> Any? {
        guard subviews.isEmpty else { return self }
        return Bundle.main.loadNibNamed(typeName(self), owner: nil, options: nil)?.first
    }
}

func typeName(_ some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(type(of: some))"
}

Upvotes: 1

ScottyBlades
ScottyBlades

Reputation: 14063

Assuming you tried the standard troubleshooting steps for connecting IBOutlets, try this:

Apparently, you need to disable awake from nib in certain runtime cases.

  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
      guard subviews.isEmpty else { return self }
      return Bundle.main.loadNibNamed("MainNavbar", owner: nil, options: nil)?.first
  }

Upvotes: 8

Josh O'Connor
Josh O'Connor

Reputation: 4962

Your nib may not be connected. My solution is quite simple. Somewhere in your project (I create a class called UIViewExtension.swift), add an extension of UIView with this handy connectNibUI method.

extension UIView {
    func connectNibUI() {
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: nil).instantiate(withOwner: self, options: nil)
        let nibView = nib.first as! UIView
        nibView.translatesAutoresizingMaskIntoConstraints = false

        self.addSubview(nibView)
        //I am using SnapKit cocoapod for this method, to update constraints.  You can use NSLayoutConstraints if you prefer.
        nibView.snp.makeConstraints { (make) -> Void in
            make.edges.equalTo(self)
        }
    }
}

Now you can call this method on any view, in your init method, do this:

override init(frame: CGRect) {
    super.init(frame: frame)
    connectNibUI()
}

Upvotes: 1

szpetip
szpetip

Reputation: 333

And how did you initiate your view from the controlller? Like this:

var view = CustomKeyboard.keyboard()
self.view.addSubview(view)

Upvotes: -3

Related Questions