Infinite loop onAppear in SwiftUI, but init() gives me another error

231 Views Asked by At

I want to call "getTestCounts" before displaying "Text(testCounts.counts[index].number)".

However, if I use onAppear, I end up with an infinite loop.

I think that since we are creating unique IDs with UUID and spinning them around in ForEach, we end up with an infinite loop.

Infinite loop when using onAppear in SwiftUI I tried to solve this problem using init() with reference to

"Cannot use instance member 'calendar' within property initializer; property initializers run before 'self' is available" The following error occurs.

How can I solve this problem? Thanks for suggestions.

Here is the UI I want to create I want to display the API response value under the date.

Here is the source code where the infinite loop occurs.

struct CalendarView: View {
    @Binding var year: String
    @Binding var month: String
    @Binding var day: String
    @EnvironmentObject var testCounts: TestCounts
    var body: some View {
        let calendar = Calendar(identifier: .gregorian)
        let selectedDate = calendar.date(from: DateComponents(year: Int(year), month: Int(month), day: Int(day)))
        let calendarDates = generateDates(selectedDate!)

        LazyVGrid(columns: Array(repeating: GridItem(.fixed(60.0), spacing: 0), count: 7), spacing: 0) {
            ForEach(calendarDates) { date in
                Button(action: {}, label: {
                    VStack(spacing: 0) {
                        if let date = date.date, let day = Calendar.current.day(for: date) {
                            Text("\(day)").fontWeight(.semibold).frame(width: 45, alignment: .leading).foregroundColor(Color("dayTextBrown"))
                            ForEach(0 ..< testCounts.counts.count, id: \.self) { index in
                                if testCounts.counts[index].date == DateTime.dateToStr(date) {
                                    Text(testCounts.counts[index].number)
                                    Text(testCounts.counts[index].hcount)
                                }
                            }
                        } else {
                            Text("").frame(width: 45, alignment: .leading)
                        }
                   }.frame(width: 60, height: 60).onAppear { 
                       getTestCounts(date.date ?? Date(), "all")
                   }
               }).background(.white).border(Color("drabBrown"), width: 1)
            }
        }
    }

    func getTestCounts(_ date: Date, _ timeType: String) {
        let since = Calendar.current.startOfMonth(for: date)
        let stringSince = DateTime.dateToStr(since!)

        let until = Calendar.current.endOfMonth(for: date)
        let stringUntil = DateTime.dateToStr(until!)

        TestCountsApi(LoginInformation.shared.token, shopId: LoginInformation.shared.defaultShopId, since: stringSince, until: stringUntil, timeType: timeType).request { json, error, result in
            switch result {
            case .success, .successWithMessage:
                TestCounts.shared.setTestCounts(json!)
            case .apiError:
                errorMessage = json!.message!
            case .communicationError:
                errorMessage = error!.localizedDescription
            case .otherError:
                errorMessage = "otherError"
            }
        }
    }
}


struct CalendarDates: Identifiable {
    var id = UUID()
    var date: Date?
}

func generateDates(_ date: Date) -> [CalendarDates] {
    var days = [CalendarDates]()

    let startOfMonth = Calendar.current.startOfMonth(for: date)
    let daysInMonth = Calendar.current.daysInMonth(for: date)
    guard let daysInMonth = daysInMonth, let startOfMonth = startOfMonth else {
        return []
    }

    for day in 0 ..< daysInMonth {
        days.append(CalendarDates(date: Calendar.current.date(byAdding: .day, value: day, to: startOfMonth)))
        }
    }

    guard let firstDay = days.first, let lastDay = days.last,
          let firstDate = firstDay.date, let lastDate = lastDay.date,
          let firstDateWeekday = Calendar.current.weekday(for: firstDate),
          let lastDateWeekday = Calendar.current.weekday(for: lastDate)
    else { return [] }

    let firstWeekEmptyDays = firstDateWeekday - 1
    let lastWeekEmptyDays = 7 - lastDateWeekday

    for _ in 0 ..< firstWeekEmptyDays {
        days.insert(CalendarDates(date: nil), at: 0)
    }

    for _ in 0 ..< lastWeekEmptyDays {
        days.append(CalendarDates(date: nil))
    }
    return days
}

class TestCounts: ObservableObject {
    struct TestCount {
        var date: String
        var number: Int
        var hcount: Int
    }

    static let shared = TestCounts()
    @Published var counts: [TestCount] = []

    func setTestCounts(_ json: TestCountsJson) {
        counts = []

        if let countsJsons = json.counts {
            for countJson in countsJsons {
                counts.append(TestCount(
                    date: countJson.date ?? "",
                    number: countJson.number ?? 0,
                    hcount: countJson.hcount ?? 0
                ))
            }
        }
    }
}

Here is the source code that tries to use init().

struct CalendarView: View {
    @Binding var year: String
    @Binding var month: String
    @Binding var day: String
    @EnvironmentObject var testCounts: TestCounts

    let calendar = Calendar(identifier: .gregorian)
    let selectedDate = calendar.date(from: DateComponents(year: Int(year), month: Int(month), day: Int(day)))
    let calendarDates = generateDates(selectedDate!)

    var body: some View {
        LazyVGrid(columns: Array(repeating: GridItem(.fixed(60.0), spacing: 0), count: 7), spacing: 0) {
            ForEach(calendarDates) { date in
                Button(action: {}, label: {
                    VStack(spacing: 0) {
                        if let date = date.date, let day = Calendar.current.day(for: date) {
                            Text("\(day)").fontWeight(.semibold).frame(width: 45, alignment: .leading).foregroundColor(Color("dayTextBrown"))
                            ForEach(0 ..< testCounts.counts.count, id: \.self) { index in
                                if testCounts.counts[index].date == DateTime.dateToStr(date) {
                                    Text(testCounts.counts[index].number)
                                    Text(testCounts.counts[index].hcount)
                                }
                            }
                        } else {
                            Text("").frame(width: 45, alignment: .leading)
                        }
                    }.frame(width: 60, height: 60)
                }).background(.white).border(Color("drabBrown"), width: 1)
            }
        }
    }
}

class TestViewModel {
    var date: Date
    var timeType: String
    var errorMessage = ""

    init (date: Date, timeType: String) {
        self.date = date
        self.timeType = timeType
       getTestCounts(date, timeType)
    }
    func getTestCounts(_ date: Date, _ timeType: String) {
        let since = Calendar.current.startOfMonth(for: date)
        let stringSince = DateTime.dateToStr(since!)

        let until = Calendar.current.endOfMonth(for: date)
        let stringUntil = DateTime.dateToStr(until!)

        TestCountsApi(LoginInformation.shared.token, shopId: LoginInformation.shared.defaultShopId, since: stringSince, until: stringUntil, timeType: timeType).request { json, error, result in
            switch result {
            case .success, .successWithMessage:
                print("success")
                TestCounts.shared.setTestCounts(json!)
            case .apiError:
                self.errorMessage = json!.message!
                print("errorMessage")
            case .communicationError:
                self.errorMessage = error!.localizedDescription
                print("errorMessage")
            case .otherError:
                self.errorMessage = "otherError"
                print("errorMessage")
            }
        }
    }
}


0

There are 0 best solutions below