I am having an issue with xcode where a variable shows as being optional when it’s not. First I get the error in xcode that "Value of optional type ‘Optional’ must be unwrapped to refer to member ‘timeFromDate’ of wrapped base type ‘Date’". So I add the ! operator and then xcode gives me the following error: "Cannot force unwrap value of non-optional type ‘Date’"
Here’s my code:
import Foundation
import SwiftUI
struct DayView: View {
@State var appointments: [Appointment] = []
@State var dates = [
Date(),
Calendar.current.date(byAdding: .minute, value: 30, to: Date()),
Calendar.current.date(byAdding: .hour, value: 60, to: Date()),
Calendar.current.date(byAdding: .hour, value: 90, to: Date()),
Calendar.current.date(byAdding: .hour, value: 120, to: Date())
]
@State var selectedDate: Date?
var date: Date
var body: some View {
ScrollView {
VStack {
Text(date.dayOfWeek())
Text(date.fullMonthDayYearFormat())
Divider().padding(.vertical)
Text("Select a Time")
.font(.largeTitle)
.bold()
Text("Duration: 30 Minutes")
ForEach(dates, id: .self) { date in
HStack {
Button {
withAnimation {
selectedDate = date
}
} label: {
Text(date!.timeFromDate())
.bold()
.padding()
.frame(maxWidth: .infinity)
.foregroundColor(selectedDate == date ? .white : .blue)
.background(
ZStack {
if selectedDate == date {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.gray)
} else {
RoundedRectangle(cornerRadius: 10).stroke()
}
}
)
}
if selectedDate == date {
NavigationLink {
BookingView(resident: Resident(firstName: "Andrew", middleName: "Warren Alexander", lastName: "Higbee", phoneNumber: "3852218786", address: "343 E 100 N Apt 12, Provo, UT, 84606", rentAmount: 800, pastDueRentOwed: 200, isPastDue: true, monthlyReminderScheduled: true, house: "Lion's Den", roomNumber: 3, bedNumber: 1, housePin: 7539, moveInDate: "2/9/2023"), date: date)
} label: {
Text("Next")
.bold()
.padding()
.frame( maxWidth: .infinity)
.foregroundColor(.white)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.blue)
)
}
}
}
}
.padding(.horizontal)
}
}
.navigationTitle("Saturday")
.navigationBarTitleDisplayMode(.inline)
.onAppear(perform: {
dates = timesWith30MinuteIncrements(for: date)
})
}
func timesWith30MinuteIncrements(for date: Date) -> [Date] {
let calendar = Calendar.current
let startDate = calendar.date(bySettingHour: 8, minute: 0, second: 0, of: date)!
let endDate = calendar.date(bySettingHour: 17, minute: 0, second: 0, of: date)!
var times: [Date] = []
var currentDate = startDate
while currentDate <= endDate {
times.append(currentDate)
guard let newDate = calendar.date(byAdding: .minute, value: 30, to: currentDate) else { break }
currentDate = newDate
}
return times
}
}
The problem line is Text(date.timeFromDate()).
Any ideas?
Edit: I rebuilt the entire app from scratch, copying and pasting. I pared the code down to isolate the issue. The same line, Text(date.timeFromDate()), date populates as an optional and the code is building that way. Why is my variable a optional? Here’s the code pared down:
import Foundation
import SwiftUI
struct DayView: View {
@State var dates = [
Date(),
Calendar.current.date(byAdding: .minute, value: 30, to: Date()),
Calendar.current.date(byAdding: .hour, value: 60, to: Date()),
Calendar.current.date(byAdding: .hour, value: 90, to: Date()),
Calendar.current.date(byAdding: .hour, value: 120, to: Date())
]
@State var date: Date
var body: some View {
ScrollView {
VStack {
Text("August 29, 2023")
Divider()
.padding(.vertical)
Text("Select a Time")
.font(.largeTitle)
.bold()
Text("Duration: 30 minutes")
ForEach(dates, id: .self) { date in
Button {
} label: {
Text(date!.timeFromDate())
}
}
}
}
.navigationTitle("Saturday")
.navigationBarTitleDisplayMode(.inline)
.onAppear {
dates = timesWith30MinuteIncrements(for: date)
}
}
func timesWith30MinuteIncrements(for date: Date) -> [Date] {
let calendar = Calendar.current
let startDate = calendar.date(bySettingHour: 8, minute: 0, second: 0, of: date)!
let endDate = calendar.date(bySettingHour: 18, minute: 0, second: 0, of: date)!
var times: [Date] = []
var currentDate = startDate
while currentDate <= endDate {
times.append(currentDate)
guard let newDate = calendar.date(byAdding: .minute, value: 15, to: currentDate) else { break }
currentDate = newDate
}
return times
}
}
#Preview {
NavigationStack {
DayView(date: Date())
}
}
2
Answers
The problem you are seeing is not because of the
@State var date
, which isn’t optional, but due to the date input into the ForEach closure, i.e.This is an element of your
dates
array. Several elements of that array are created with theCalendar.current.date(byAdding: value:)
method that returns an optional date, and so the array has signaturevar dates: [Optional<Date>]
. Which is why the compiler is saying the date needs to be unwrapped prior to use.if you option click on
.date(byAdding:
you’ll see it returns an optional DateDate?
.(option clicking
dates
will also show it’s optional and you’d get an error if you told swift it should be non optionaldates: [Date]
)This means the type of your dates array is actually an array of optional dates
[Date?]
So your
date
inForEach(dates, id: .self) { date in
is in fact optionalFew ways to handle it depending on what your intent is,
You could wrap your button (or just the label) in an
if let date {
You could compact map your default dates array with
.compactMap { $0 }
Or you could do logic somewhere else and pass in an array of non-optional as an initial value when using DayView(dates = someNonOptionalDates).
I think the compiler was confused by something when telling you force unwrap was unavailable. Likely related to the
timeFromDate
method you have. (it’s not included in the code sample so I had to removed it to test). I instead usedText(date!, format: .dateTime)
which worked fine.Completed side note: Obviously no idea what your method did but could be worth looking at
Text
with format likeif let date { Text(date, format: .dateTime.hour().minute()) }
if you’re just looking to show a time, it will handle a lot of complexity and localisation for you!edit –
Another way since you already guard against optional date in your other date logic is to move that into an extension.
eg. roughly:
Which could make your onAppear and State roughly
with the side benefit that your state is no longer an array of optionals it’s just
[Date]