-1

I am trying to add a label to a picture which will indicate how much time the chose picture was shown. I've added a label to a detailView which contains picture and linked the label to DetailViewController class. I have set a name to this label but i get the "Unexpectedly found nil" crash while choosing a picture from the list. This is a DetailViewController:

class DetailViewController: UIViewController {
    @IBOutlet var imageView: UIImageView!
    @IBOutlet var name: UILabel!
    
    var selectedImage : String?
    var detailVCTitle: String?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = detailVCTitle
        navigationItem.largeTitleDisplayMode = .never
        if let imageToLoad = selectedImage {
            imageView.image = UIImage(named: imageToLoad)
        }
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.hidesBarsOnTap = true
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        navigationController?.hidesBarsOnTap = false
    }
    
}

And this is a function i am trying to modify:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as?
        DetailViewController {
        counter += 1
        vc.selectedImage = pictures[indexPath.row]
        navigationController?.pushViewController(vc, animated: true)
        vc.detailVCTitle = "Picture \(indexPath.row + 1) of \(pictures.count)"
        vc.name.text = "It was shown \(counter) times"
    }
}
  • Please show the full error and which line of code causes it. That will show exactly what is happening. I suspect that your nib is not correctly configured so that the outlets with the ! Are nil. – Fogmeister Jun 21 '22 at 21:01
  • Sure! The error is: Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value And the string is: vc.name.text = "It was shown \(counter) times" – Nikita Chechnev Jun 21 '22 at 21:25
  • So, the `name` outlet is not hooked up. Check your storyboard. The error is telling you precisely where the problem is. – Rob Napier Jun 21 '22 at 21:33
  • 2
    The problem occurs because you are accessing the `name` outlet before the view has been loaded. You can force a view load, but I think a better approach is to assign `counter` to a property on your vc and have the vc set the label text in `viewDidLoad` or `viewWillAppear`. Your current code is tightly coupled to your vc's views. Note that the other two assignments do not assign directly to views – Paulw11 Jun 21 '22 at 21:35
  • I may be heavily wrong but it seems to be hooked correctly. Could you check the screenshots below, please? Also, i do not quite get the nil error as both the VCtitle and text for my label being set in that func. However, only label text returns a fatal error https://imgur.com/a/qyXf703 – Nikita Chechnev Jun 21 '22 at 21:56

1 Answers1

1

The short, and obvious, answer is that vc.name is nil and it is an implicitly unwrapped optional.

The question is "Why is it nil?, assuming that everything else is correct and you have connected the outlet?"

The reason is that views are instantiated and assigned to the outlet properties when the view is loaded. This occurs when loadView is called explicitly or it occurs implicitly if there is a reference to a view controller's view property. Neither of these things have occurred before you say vc.name.text=... so you get a nil crash.

Referencing a view controller's views directly from another object is an encapsulation violation; it tightly couples your table view controller with an implementation detail of the detail view controller.

You will note that your other properties, selectedImage and detailVCTitle are just that; simple properties. The view controller does something with them in viewDidLoad.

You should do the same thing with your count:

class DetailViewController: UIViewController {
    @IBOutlet var imageView: UIImageView!
    @IBOutlet var name: UILabel!
    
    var selectedImage : String?
    var detailVCTitle: String?
    var counter = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = detailVCTitle
        navigationItem.largeTitleDisplayMode = .never
        if let imageToLoad = selectedImage {
            imageView.image = UIImage(named: imageToLoad)
        }
        name.text = "It was shown \(counter) times"
    }
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as?
        DetailViewController {
        counter += 1
        vc.selectedImage = pictures[indexPath.row]
        navigationController?.pushViewController(vc, animated: true)
        vc.detailVCTitle = "Picture \(indexPath.row + 1) of \(pictures.count)"
        vc.counter = counter
    }
}

Now, your table view simply knows that your detail view wants a counter. It doesn't know, nor need to know, how that counter is used. Your detail view controller can use it in multiple places or in different ways depending on its value or whatever...

Paulw11
  • 108,386
  • 14
  • 159
  • 186