36

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
    }
}
Gralex
  • 4,285
  • 7
  • 26
  • 47
  • Where are you accessing them besides commonInit? – Ashraf Tawfeeq Feb 07 '15 at 17:34
  • @AshrafTawfeeq now nowhere. I can't normal init this class. Also clean project doesn't help – Gralex Feb 07 '15 at 18:17
  • Outlets are not yet set at the time of init because the view has not been loaded. Your second log works because accessing the view causes it to be loaded. – rdelmar Feb 07 '15 at 18:18
  • @rdelmar view already loaded, I can work with him. But it seems that outlet not connected. If I call `commonInit` directly from `ViewController` immediately after init, `aButt` not nill. – Gralex Feb 07 '15 at 18:34
  • 1
    I spend hours trying to fix the similar problem. Before I understood that I attached the outlet to the wrong view :) – kelin Mar 17 '18 at 22:05

6 Answers6

72

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()
}

// ...
Top-Master
  • 7,611
  • 5
  • 39
  • 71
fz.
  • 3,233
  • 22
  • 20
  • 4
    Already have that...Still getting the error. Also, my outlets are showing as connected, the class is matching in the xib. Even made the reference type strong, still nothing. – ScottyBlades Sep 19 '18 at 07:44
  • @ScottyBlades did you figure this out? – Marcelo Ribeiro Sep 20 '18 at 01:42
  • @MarceloRibeiro, no...I haven't. But I can tell you the are nil when called from `awakefromnib` but not `init(frame: CGRect)` – ScottyBlades Sep 20 '18 at 03:53
  • 5
    @MarceloRibeiro, you have to do this: ```override func awakeAfter(using aDecoder: NSCoder) -> Any? { guard subviews.isEmpty else { return self } return Bundle.main.loadNibNamed("MainNavbar", owner: nil, options: nil)?.first }``` – ScottyBlades Sep 20 '18 at 04:05
  • In my case I set `didSet` param for `IBInspectable` property and it fired earler than `awakeFromNib`. The solution is to use ? instead ! for outlets. – pirogtm Dec 30 '22 at 08:37
8

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
  }
ScottyBlades
  • 12,189
  • 5
  • 77
  • 85
1

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()
}
Josh O'Connor
  • 4,694
  • 7
  • 54
  • 98
1

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))"
}
Chris Prince
  • 7,288
  • 2
  • 48
  • 66
  • Though there is one problem with this approach. All the settings done through xib is lost since the view reinitializes itself. But at least outlets of subviews are intact. – atulkhatri Apr 05 '20 at 07:54
-1

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

Abhishek B
  • 157
  • 8
-3

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

var view = CustomKeyboard.keyboard()
self.view.addSubview(view)
szpetip
  • 333
  • 6
  • 12
  • Like this: `textField.inputView = CustomKeyboard.keyboard()` It's doest matter. – Gralex Feb 07 '15 at 18:18
  • You didn't post your code and if you use `var view = CustomKeyboard();` then the result will like in your case: your outlets are getting nil. This is why I asked. – szpetip Feb 07 '15 at 18:26