Xcode показывает переменную как необязательную, если это не так

У меня проблема с Xcode: переменная отображается как необязательная, хотя это не так.
Сначала я получаю ошибку в Xcode:

Значение необязательного типа «Необязательно» должно быть развернуто, чтобы ссылаться на элемент «timeFromDate» обернутого базового типа «Дата».

Поэтому я добавляю оператор !, а затем Xcode выдает следующую ошибку:

Невозможно принудительно развернуть значение необязательного типа «Дата».


Вот мой код:

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
    }
}

Проблемная строка — Text(date.timeFromDate()).

Есть идеи?

Обновлено: я перестроил все приложение с нуля, скопировав и вставив. Я сократил код, чтобы изолировать проблему. В той же строке Text(date.timeFromDate()) дата заполняется как необязательная, и код строится таким образом.

Почему моя переменная необязательна?
Вот урезанный код:

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())
    }
}

Вот моя функция timeFromDate:

func timeFromDate() -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "hh:mm a"
        return formatter.string(from: self)
    }

80
2

Ответы:

Проблема, которую вы видите, связана не с @State var date, который не является обязательным, а с вводом даты в замыкание ForEach, т.е.

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

Это элемент вашего массива dates. Несколько элементов этого массива создаются с помощью метода Calendar.current.date(byAdding: value:), который возвращает необязательную дату, поэтому массив имеет подпись var dates: [Optional<Date>]. Вот почему компилятор говорит, что дату необходимо развернуть перед использованием.


Решено

если вы выберете вариант, нажмите .date(byAdding:, вы увидите, что он возвращает необязательную дату Date?. (нажатие на опцию dates также покажет, что она не является обязательной, и вы получите сообщение об ошибке, если скажете Swift, что она не должна быть необязательной dates: [Date])

Это означает, что тип вашего массива дат на самом деле является массивом необязательных дат [Date?]

Так что ваш date в ForEach(dates, id: \.self) { date in на самом деле необязателен.

Несколько способов справиться с этим в зависимости от ваших намерений,

Вы можете обернуть кнопку (или просто метку) if let date {

Вы можете сжать массив дат по умолчанию с помощью .compactMap { $0 }

Или вы можете выполнить логику где-то еще и передать массив необязательных значений в качестве начального значения при использовании DayView(dates = someNonOptionalDates).

Я думаю, что компилятор был чем-то сбит с толку, сообщив вам, что принудительное развертывание недоступно. Вероятно, это связано с вашим методом timeFromDate. (он не включен в пример кода, поэтому мне пришлось удалить его для тестирования). Вместо этого я использовал Text(date!, format: .dateTime), и это сработало нормально.

Завершенное примечание: очевидно, понятия не имею, что делает ваш метод, но, возможно, стоит посмотреть Text в таком формате, как if let date { Text(date, format: .dateTime.hour().minute()) }, если вы просто хотите показать время, он справится с большой сложностью и локализацией для вас!

редактировать -

Другой способ, поскольку вы уже защищаете от необязательной даты в другой логике даты, — это переместить ее в расширение. например. грубо:

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

Что может примерно сделать ваши onAppear и State

@State var dates = Date.now.thirtyMinuteIncrements()

.onAppear { dates = date.thirtyMinuteIncrements() }

с дополнительным преимуществом, что ваше состояние больше не является набором дополнительных опций, это просто [Date]