I'm getting a Can't unwrap Optional.None
error when running a slightly modified version of the Master-Detail App in Swift.
All I did was add a second UILabel
to the DetailViewController, right under the pre-existing detailDescriptionLabel
, and when I navigate to the DetailViewController from the MasterViewController I crash on the statement which sets my new Label:
secondLabel.text = "This is the Second Label"
I declare this label is as followed:
@IBOutlet var secondLabel : UILabel
What's really interesting is that the pre-existing code for setting the detailDescriptionLabel
includes the new optional let
syntax:
if let label = self.detailDescriptionLabel {
label.text = "This is the Detail View"
}
So why is it that we need a let
statement here for detailDescriptionLabel
? It was never declared as an Optional Label, it was declared like any regular Label IBOutlet property, like so:
@IBOutlet var detailDescriptionLabel: UILabel
so why is it being treated as an Optional?
And does this mean that from now on any object I add as an IBOutlet will also have to go through this sort of let
statement if I want to set it through code?
EDIT:
I'm crashing in the following method, on the line anotherLabel.text = "Second Label"
:
func configureView() {
// Update the user interface for the detail item.
if let theCar: CarObject = self.detailItem as? CarObject {
if let label = self.detailDescriptionLabel {
label.text = theCar.make
}
anotherLabel.text = "Second Label"
}
}
but when I treat anotherLabel
with the whole if let
business, as follows, it works perfectly well:
if let label2 = self.anotherLabel {
label2.text = "Second Label"
}
Properties declared with @IBOutlet
are always implicitly unwrapped optional variables. Apple's documentation explains it this way:
When you declare an outlet in Swift, the compiler automatically converts the type to a weak implicitly unwrapped optional and assigns it an initial value of nil. In effect, the compiler replaces
@IBOutlet var name: Type
with@IBOutlet weak var name: Type! = nil
. The compiler converts the type to an implicitly unwrapped optional so that you aren’t required to assign a value in an initializer. It is implicitly unwrapped because after your class is initialized from a storyboard or xib file, you can assume that the outlet has been connected. Outlets are weak by default because the outlets you create usually have weak relationships.
Since it's implicitly unwrapped, you don't have to go through if let label = something
every time, just know that if your label is nil
and you try to work with it, you'll end up with a runtime error. I'm guessing your second label isn't hooked up in Interface Builder -- is that the case? [OP: Nope!]
Okay, what's happening in this specific case is that the configureView()
method can get called from the master view controller's prepareForSegue()
, since the detailItem
property on the detail view controller has a didSet
handler. When that happens, the detail view controller hasn't been loaded yet, so no labels have been created. Since the labels will get set up at the same time, you could put both initializations under that one if statement (and make it a bit more clear, even):
func configureView() {
// Update the user interface for the detail item.
if let theCar: CarObject = self.detailItem as? CarObject {
if self.detailDescriptionLabel != nil {
self.detailDescriptionLabel.text = theCar.make
self.secondLabel.text = "Second Label"
}
}
}