iosswiftinterface-builderxibnib

Xib's IBOutlets returning nil when using Xib in storyboard


I've created a view using Interface Builder as a xib. I've done this as I want to give the user the option to increment any number of these views to the view controller. I've created an Xib view (named "TimeRangeView.xib", created a custom class (named "TimeRangeView.swift") and set the xib's class to this custom class, connected the storyboard elements to the TimeRangeView.swift's IBOutlets, set the xib's file owner to TimeRangeView.swift, and added the view with its class set to my TimeRangeView.swift in the view controller.enter image description here enter image description here

TimeRangeView.swift:

import UIKit

class TimeRangeView: UIView {
    @IBOutlet var startTimeLabel: UILabel!
    @IBOutlet var startDatePicker: UITextField!
    @IBOutlet var endTime: UILabel!
    @IBOutlet var endDatePicker: UITextField!
    @IBOutlet var addButton: UIButton!
    @IBOutlet var maxTimeLabel: UILabel!



    @IBAction func addButtonPressed(_ sender: UIButton) {

    }
}

The storyboard UIViewController I am using this in has an Interface Builder view as a subview; this view's class is assigned as TimeRangeView.swift. This view is connected to the view controller as an IBOutlet:

@IBOutlet var timeRangeView: TimeRangeView!

Within the view controller's viewDidLoad() method, I am wanting to change some of TimeRangeView's subviews (which, as we've covered, are connected as IBOutlets), but they are returning nil when I try to change some of their properties:

override func viewDidLoad() {
    super.viewDidLoad()
    timeRangeView.maxTimeLabel.text = "Max duration: \(timeString ?? "n/a")"

    //Change the text fields' input views
    let startTimeDatePickerToolBar = UIToolbar().ToolbarPicker(mySelect: #selector(dismissPicker))

    let endTimeDatePickerToolBar = UIToolbar().ToolbarPicker(mySelect: #selector(dismissPicker))

    startTimeDatePicker.setDate(Date(timeIntervalSince1970: NSDate().timeIntervalSince1970 + 60), animated: false)

    endTimeDatePicker.setDate(Date(timeIntervalSince1970: NSDate().timeIntervalSince1970 + 120), animated: false)

    timeRangeView.startDatePicker.inputView = startTimeDatePicker
    timeRangeView.startDatePicker.inputAccessoryView = startTimeDatePickerToolBar
    timeRangeView.startDatePicker.delegate = self

    timeRangeView.endDatePicker.inputView = endTimeDatePicker
    timeRangeView.endDatePicker.inputAccessoryView = endTimeDatePickerToolBar
    timeRangeView.endDatePicker.delegate = self
}

timeRangeView is not returning nil; it is loading. But when trying to access timeRangeView's subviews (startDatePicker, endDatePicker, maxTimeLabel), they are all returning nil.

The error I am receiving is:

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

Solution

  • This is happening Because the xib was never loaded

    So what you can do here is, First add a simple UIView in the viewController rather than TimeRangeView

    @IBOutlet var timeRangeContainerView: UIView!
    

    Now in viewDidLoad add Fetch the xib by using

    let timeRangeView = Bundle.main.loadNibNamed("TimeRangeView", owner: self, options: nil)?[0] as? TimeRangeView
    

    And add this as a subview of timeRangeContainerView

    override func viewDidLoad() {
        super.viewDidLoad()
        let timeRangeView = Bundle.main.loadNibNamed("TimeRangeView", owner: self, options: nil)?[0] as? TimeRangeView
    
        timeRangeView.frame = CGRect(origin: CGPoint.zero, size: self.timeRangeContainerView.frame.size)
    
        self.timeRangeContainerView.insertSubview(timeRangeView, at: 0)
    
        //  Add constraints
        timeRangeView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
        timeRangeView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
        timeRangeView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
        timeRangeView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
        timeRangeView.translatesAutoresizingMaskIntoConstraints = false  
    
    
        //Add the rest of your code here
    }
    
    

    The constraints are optional