Reputation:
I'm trying to extract the earliest (and latest, but since the two methods are going to be nearly identical, I'll concentrate on "earliest" for this question) date held in a global DataSource
object, theData
, and return it as a NON-OPTIONAL value.
DataSource
is a "souped up Array" object holding an arbitrary number of DataEntry
objects. Here's the bare-bones of the definition of DataEntry
:
class DataEntry: Codable, Comparable {
var theDate: Date = Date() // To avoid using an optional but still silence compiler
// complaints about no initializer
// Lots of other properties and support code irrelevant to the question snipped for brevity.
}
Retrieving the needed date(s) is done as a method of my DataSource
class:
class DataSource: Codable {
var theArray: [DataEntry] = Array()
// Several hundred lines of non-relevant support and convenience code chopped
// Return either .theDate from the earliest DataEntry held in theArray, or Date()
// if theArray.first hands back a nil (indicating theArray is unpopulated).
// Either way, I specifically want the returned value to *NOT* be an optional!
func earliest() -> Date {
// First, make certain theArray is sorted ascending by .theDate
theArray.sort {
$0.theDate < $1.theDate
}
// Now that we know theArray is in order, theArray.first?.theDate is the earliest stored date
// - If it exists.
// But since .first hands back an optional, and I specifically DO NOT WANT an optional return from
// .earliest(), I nil-coalesce to set firstDate to Date() as needed.
let firstDate: Date = theArray.first?.theDate ?? Date()
print("firstDate = \(firstDate)")
return firstDate
}
func latest() -> Date {
// gutted for brevity - mostly identical to .earliest()
return lastDate
}
}
Note that I take pains to make sure what gets returned is not an optional. And, in fact, later code blows up if I try to treat it like it could be:
class SelectDateRangeViewController: UIViewController {
@IBOutlet weak var startPicker: UIDatePicker!
@IBOutlet weak var endPicker: UIDatePicker!
// Irrelevant code elided
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let e = theData.earliest()
// Note: Changing the previous line to
// let e = theData.earliest()!
// gets a compile-time error: "Cannot force unwrap value of non-optional type 'Date'"
let l = theData.latest()
// We should now have the earliest and latest dates in theData held in e and l, respectively.
// Print them to the debugger console to verify they're actually what I expect...
print ("e = \(e)")
print ("l = \(l)")
// ...and carry on
startPicker.minimumDate = e // <--- Crashes here, claiming that I've tried to implicitly unwrap an optional value! See the console log dump below
startPicker.maximumDate = l
startPicker.date = e
endPicker.minimumDate = e
endPicker.maximumDate = l
endPicker.date = l
}
}
What follows is what I see in the debugger console when I try to run the "actual code" (rather than the gutted-to-save-space version presented here)
---start debugger log
firstDate = 2020-04-16 15:00:31 +0000 <---- This line and the next come from inside the .earliest()/.latest() methods of DataSource
lastDate = 2020-04-27 15:43:23 +0000
e = 2020-04-16 15:00:31 +0000 <---- And these two lines come from the viewWillAppear() method shown above.
l = 2020-04-27 15:43:23 +0000
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file /ActualPathRemoved/SelectDateRangeViewController.swift, line ##
--- end debugger log
Where the ## in that crash line is the actual line number occupied by the "startPicker.minimumDate = e" statement in the "all of the code is there" version.
The values displayed as "firstDate", "lastDate", "e", and "l" are ABSOLUTELY CORRECT for the test dataset I'm using.
But wait a minute! The compiler says that the return from .earliest() is not an optional when I try to force-unwrap it! And since when is a perfectly valid Date object "nil"??? And why is the code trying to "implicitly unwrap" a non-nil, not-optional value?!?!? Somebody make up my mind!
So what am I misunderstanding? Either my code is hosed, or what I understood while trying to write it is. Which is the case, and what's the problem???
Answer to @Paulw11's comment query:
@IBAction func reportRequestButtonAction(_ sender: Any) {
let theVC = SelectDateRangeViewController()
self.navigationController?.pushViewController(theVC, animated: true)
}
This bit of code lives in the viewController that offers the button as part of a scene that offers a menu of "let the user tinker with the dataset in various ways" buttons. When first added, I intended to bring up the SelectRangeScene with a segue, but for some reason, decided that pushing it onto the UINavigationController stack would be a better way of doing it, though I can't remember why it was I thought that now - the reason apparently got shaken loose and lost while I was beating my head against the problem of why trying to set the datepickers was crashing.
Upvotes: -1
Views: 103
Reputation: 114866
You are creating your new view controller instance with
let theVC = SelectDateRangeViewController()
Since you don't get the instance from the storyboard, none of the outlets are bound; they will be nil
. As your outlets are declared as implicitly unwrapped optionals, you get a crash when you refer to them.
You could instantiate the new view controller from your storyboard but it would probably be simpler to use a segue either directly from your button or by using performSegue
in your @IBAction
Upvotes: 2
Reputation: 285082
If the code crashes in this line then the startPicker
outlet is not connected.
It has nothing to do with the dates, they are definitely non-optional.
Upvotes: 3