Wrong selection with LazyHGrid SwiftUI

250 Views Asked by At

Hi all I have a problem with selecting cells in a LazyHGrid using SwiftUI

In the TimePickerGridView.swift I create a horizontal list with times that the user can select. To manage cell selection I use @State private var selectedItem: Int = 0

Everything seems to work but I have some problems when I save the user selection to create a date which contains the selected times

When the user selects a cell, it immediately updates a date by setting the hours and minutes.

The date being modified is @Binding var date: Date, this date refers to a RiservationViewModel.swift which is located in the RiservationView structure

struct ReservationsView: View {
   
    @StateObject var viewModels = ReservationViewModel()
    
    var body: some View {
        VStack {
            TimePickerGridView(date: $viewModels.selectedDate)
        }
    }
}

Now the problem is that when the user selects the time and creates the date the LazyHGrid continuously loses the selection and has to select the same cell more than once to get the correct selection again ...

At this point the variable date is observed because it can change thanks to another view that contains a calendar where the user selects the day.

How can I solve this problem? where is my error?


private extension Date {
    var hour: Int { Calendar.current.component(.hour, from: self) }
    var minute: Int { Calendar.current.component(.minute, from: self) }

    var nextReservationTime: TimePicker {
        let nextHalfHour = self.minute < 30 ? self.hour : (self.hour + 1) % 24
        let nextHalfMinute = self.minute < 30 ? 30 : 0
        return TimePicker(hour: nextHalfHour, minute: nextHalfMinute)
    }
}

struct TimePickerGridView: View {
    @Binding var date: Date
    @State private var selectedItem: Int = 0
    @State private var showNoticeView: Bool = false
   
    private var items: [TimePicker] = TimePicker.items
    
    private func setTimeForDate(_ time: (Int, Int)) {
        guard let newDate = Calendar.current.date(bySettingHour: time.0, minute: time.1, second: 0, of: date) else { return }
        date = newDate
    }
    
    private var startIndex: Int {
        // se trova un orario tra quelli della lista seleziona l'index appropriato
        // altrimenti seleziona sempre l'index 0
        items.firstIndex(of: date.nextReservationTime) ?? 0
    }
    
    init(date: Binding<Date>) {
        // Date = ReservationViewModel.date
        self._date = date
    }
    
    var body: some View {
        VStack(spacing: 20) {
            HStack {
                Spacer()
                TitleView("orario")
                VStack {
                    Divider()
                        .frame(width: screen.width * 0.2)
                }
                Button(action: {}) {
                    Text("RESET")
                        .font(.subheadline.weight(.semibold))
                        .foregroundColor(date.isToday() ? .gray : .bronze)
                        .padding(.vertical, 5)
                        .padding(.horizontal)
                        .background(.thinMaterial)
                        .clipShape(Capsule())
                }
                Spacer()
            }
            ZStack {
                ScrollView(.horizontal, showsIndicators: false) {
                    ScrollViewReader { reader in
                        LazyHGrid(rows: Array(repeating: GridItem(.fixed(60), spacing: 0), count: 2), spacing: 0) {
                            ForEach(items.indices) { item in
                                TimePickerGridCellView(date: $date, selectedItem: $selectedItem, picker: items[item], selection: selectedItem == items.indices[item])
                                    
                                    .frame(width: (UIScreen.main.bounds.width - horizontalDefaultPadding * 2) / 4)
                                    
                                    .onTapGesture {
                                        setTimeForDate((items[item].hour, items[item].minute))
                                        selectedItem = item
                                    }
                                
                                    .onChange(of: date) { _ in
                                        selectedItem = startIndex
                                    }
                                
                                    .onAppear(perform: {
                                        selectedItem = startIndex
                                    })
                            }
                        }
                        .background(Divider())
                    }
                }
                .frame(height: showNoticeView ? 70 : 130)
            }
        }
    }
}
0

There are 0 best solutions below