swiftxcodeforced-unwrapping

unable to set UILabelView.Text value


I'm getting this error and unable to resolve it...

I'm passing an object from UIViewController to another through a UISegmentedControl button changes. Below is the code I'm using to pass the object. It is working great and I'm able to print the object details in the console.

 @IBAction func switchViewAction(_ sender: UISegmentedControl) {
        let vc = DirectionsViewController()
        if let unwrappedRecipe = self.details
        {
            vc.customInit(recipes: unwrappedRecipe)
        } else

        {
            print("it has no value!")
        }
        self.viewContainer.bringSubviewToFront(views[sender.selectedSegmentIndex])

    }

However, the issue is when I'm trying to set a value to a label, I get the below error:

Unexpectedly found nil while implicitly unwrapping an Optional value

Below is the code I'm using inside DirectionsViewController

 @IBOutlet weak var lblDirections: UILabel!
var recipe: Recipe? = nil

override func viewDidLoad()
{
    super.viewDidLoad()

    // Do any additional setup after loading the view.
}

func customInit(recipes: Recipe)
{
    lblDirections.text =  recipes.name
}

I have researched about optional & forced variables, also tried to safely unwrap variables but with no luck. Can someone help pls?


Solution

  • If you have an IBOutlet that is load from storyboard or xib, it is nil until viewDidLoad is called. So when func customInit(recipes: Recipe) is called before viewDidLoad, the lblDirections is nil, and because it is force unwrapped it will crash. You can fix this in a two way:

    1. Ugly but easy. The viewDidLoad is called when you first get view of a view controller, so you can add _ = vc.view just before vc.customInit(recipes: unwrappedRecipe) in func switchViewAction(_ sender: UISegmentedControl). I'm not recommending using it production code, but you can use to test if everything is working fine.

    2. Because you are using xib, you can initialize a view controller and provide custom init (even name of your method indicates it: customInit). To have a proper view controller init you need to use:

    
    class DirectionsViewController: UIViewController {
       let recipe: Recipe
       @IBOutlet weak var lblDirections: UILabel!
    
       init(recipe: Recipe) {
           self.recipe = recipe
           let classType = type(of: self)
           let bundle = Bundle(for:classType)
           super.init(nibName: "DirectionsViewController", bundle: bundle) //provide your nib filename 
       }
    
       override func viewDidLoad(){
           super.viewDidLoad()
           //code from func customInit(recipes: Recipe)
           lblDirections.text =  recipe.name
       }
    
       @available(*, unavailable, message: "use init(recipe: Recipe)")
       override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
           fatalError("init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) has not been implemented")
       }
    
       @available(*, unavailable, message: "don't use sotryboard! use nib and init(recipe: Recipe)")
       required init?(coder: NSCoder) {
           fatalError("init(coder:) has not been implemented")
       }
    }
    

    You can't provide optional Recipe or update it after creating, but it makes code less complicated. It also assumes that your xib file is called DirectionsViewController.xib (if not you need to change it in line super.init(nibName: "DirectionsViewController", bundle: bundle)).


    You can also use my micro-library NibBased to have a little less code to write. When using the library your code should be:

    import NibBased
    
    class DirectionsViewController: NibBaseViewController {
        let recipe: Recipe
        @IBOutlet weak var lblDirections: UILabel!
    
        init(recipe: Recipe) {
            self.recipe = recipe
            super.init()
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            lblDirections.text =  recipe.name
        }
    }