skip to Main Content

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


  1. 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.

    ForEach(dates, id: .self) { date in  //<<< this one
                        Button {
                            
                        } label: {
                            Text(date!.timeFromDate())
                        }
                    }
    

    This is an element of your dates array. Several elements of that array are created with the Calendar.current.date(byAdding: value:) method that returns an optional date, and so the array has signature var dates: [Optional<Date>]. Which is why the compiler is saying the date needs to be unwrapped prior to use.

    Login or Signup to reply.
  2. if you option click on .date(byAdding: you’ll see it returns an optional Date Date?.
    (option clicking dates will also show it’s optional and you’d get an error if you told swift it should be non optional dates: [Date])

    This means the type of your dates array is actually an array of optional dates [Date?]

    So your date in ForEach(dates, id: .self) { date in is in fact optional

    Few 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 used Text(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 like if 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:

    extension Date {
        func thirtyMinuteIncrements(startHour: Int = 8, endHour: Int = 18) -> [Date] {
            ...
            let startDate = calendar.date(bySettingHour: startHour, minute: 0, second: 0, of: self)!
            ...
        }
    }
    

    Which could make your onAppear and State roughly

    @State var dates = Date.now.thirtyMinuteIncrements()
    
    .onAppear { dates = date.thirtyMinuteIncrements() }
    

    with the side benefit that your state is no longer an array of optionals it’s just [Date]

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search