0

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)
            }
        }
    }
}
kAiN
  • 2,559
  • 1
  • 26
  • 54
  • **"This question currently includes multiple questions in one. It should focus on one problem only."** I don't understand why we vote for the closure of this question ... My question does not contain more questions but only one ... Because my lazyHGrid loses the selection when the user creates a new date by setting the selected times in the lazyHGrid. .. it's just one question ... **I am using an English translator ... sorry if my english is not good** – kAiN Feb 10 '22 at 18:22
  • Are you trying to select multiple cells at once? – Yrb Feb 10 '22 at 18:33
  • @Yrb No no ... I need the user to select only one cell at a time ... When the user uses the calendar (not in this view) the date changes and if the date is today I want selectedItem to be equal to startIndex, on the other hand, if the date is not today, the selectedItem variable is equal to zero. But unfortunately when the date changes and the user tries a new selection the lazyGrid loses the selection showing wrong selections – kAiN Feb 10 '22 at 18:38
  • This needs a [Minimal, Reproducible Example (MRE)](https://stackoverflow.com/help/minimal-reproducible-example). You need to remove all unnecessary code, and add in necessary code like `TimePicker`. – Yrb Feb 10 '22 at 18:51
  • yes exactly. My first guess is: in `.onTapGesture` you call `setTimeForDate` which changes `date` – which triggers the `.onChange` and `selectedItem` is set back. – comment out the `.onChange` and see what happens. – ChrisR Feb 10 '22 at 18:55
  • @ChrisR If I comment `.onChange` everything works fine .. **this is the problem ..** I need ` .onChange ` because I need to observe the date ... If the date is not today the time selection must be ** index 0** as soon as the date changes (and I manage to get this) but when the user selects another time again (with index different from 0) the selection is wrong .. this is my problem Thanks for understanding my problem chris – kAiN Feb 10 '22 at 19:04
  • can you differentiate day and time? – as I understand they are selected in different views – and combine them later? So your `TimePickerGridView` can safely pick a time, and only reset when the day changes (from another view). – ChrisR Feb 10 '22 at 19:13
  • @ChrisR I could add `@Published var hour` `@Published var minute` to the template and change these two values from the `TimePickerView` and create a date through `calendar.current.component` for display in the summary view ... Is this what you recommend me to do? is it a good way? is it a good way? – kAiN Feb 10 '22 at 19:18
  • It sounds good, by that you can still check for day changes. And it should fix your original question. More can only be said with a MRE, as noted above. – ChrisR Feb 10 '22 at 19:23
  • @ChrisR Yes, in this way from the view that contains the calendar I can observe the `date` variable from the `reservationViewModel` model and the `hours` and `minutes` variables of the `reservationViewModel` model from the view that contains the pickerTime. From the `SummaryView` where I display the date and time data I recreate a date .. I just hope that it is not a wrong way to manage these things – kAiN Feb 10 '22 at 19:38
  • @ChrisR Seems to work perfectly ... thanks for your hint input chris .. thanks for wasting a few minutes with me – kAiN Feb 10 '22 at 20:04

0 Answers0