Philip
Philip

Reputation: 23

SwiftUI not updating state variable in ForEach loop function call

I cannot seem to find a solution to this after much searching. I am trying to update my state variable d in the function call addDay. This function is called in the ForEach loop. The problem is, d is updated once to a value of 2.0, then it stays at 2.0 forever. I'm not sure what is wrong, any insight?

let weekdays: [String] = ["Mon", "Tues", "Wed", "Thurs", "Fri"]
    @State var startDate: Date = Date().next(.monday, direction: .backward)
    @State var d:Double  = 1.0
    
    /**
     - parameter startDate: the beginning date that will be modified
     - Returns: A new Text that has a date attached
     */
    func addDay(startDate: Date) -> Text {
        self.startDate = startDate.addingTimeInterval(86400 * self.d)
        self.d += 1.0
        return Text("\(startDate, formatter: Self.weekFormat)")
    }
    
    static let weekFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMMM dd"
        return formatter
    }()
    
    var body: some View {
        ScrollView(.horizontal) {
            HStack{
                ForEach(weekdays, id: \.self) { weekday in
                    VStack{
                        RoundedRectangle(cornerRadius: 9, style: .continuous)
                            .stroke(Color.blue)
                            .frame(width: 68.5, height: 68.5)
                        
                        self.addDay(startDate: self.startDate)
                        Text(String(self.d))
                        Text(weekday)
                        
                        
                    }
                    
                }
            }
        }
        
    }
}

Upvotes: 2

Views: 2454

Answers (2)

Asperi
Asperi

Reputation: 258413

I'm not sure what you try to achieve, but here is a demo of approach of how to use temporary variables during view generation.

Tested with Xcode 12 / iOS 14 (original logic preserved)

demo

struct ContentView: View {
    let weekdays: [String] = ["Mon", "Tues", "Wed", "Thurs", "Fri"]
    @State var startDate: Date = Date()//.next(.monday, direction: .backward)

    /**
     - parameter startDate: the beginning date that will be modified
     - Returns: A new Text that has a date attached
     */
    func addDay(startDate: inout Date, d: inout Double) -> Text {
        startDate = startDate.addingTimeInterval(86400 * d)
        d += 1.0
        return Text("\(startDate, formatter: Self.weekFormat)")
    }

    static let weekFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMMM dd"
        return formatter
    }()

    var body: some View {
        ScrollView(.horizontal) {
            genererateContent()
        }
    }

    private func genererateContent() -> some View {
        var d: Double = 1.0
        var startDate = self.startDate.addingTimeInterval(86400 * d)

        return HStack{
            ForEach(weekdays, id: \.self) { weekday in
                VStack{
                    RoundedRectangle(cornerRadius: 9, style: .continuous)
                        .stroke(Color.blue)
                        .frame(width: 68.5, height: 68.5)

                    self.addDay(startDate: &startDate, d: &d)
                    Text(String(d))
                    Text(weekday)
                }
            }
        }
    }
}

Upvotes: 0

Samuel-IH
Samuel-IH

Reputation: 813

It appears that you might be modifying the view state during update...

This is a fancy term for a possible never ending loop:

  1. SwiftUI evaluates the view, which causes addDay to be called.
  2. addDay causes startDate or d to change, both of which are @State variables.
  3. SwiftUI sees that the view state to change, so it re-evaluates the view.
  4. ( back to top :P )

Your case is a bit different however, because SwiftUI appears to be actually stopping the state change, which is probably good in your case because if it wasn't stopping the state change, your CPU usage would spike up to 100 percent.


But... how do I get around this?

For your particular situation I suggest handling it like so:

func addDay(_ index: Double) -> Text {
    let someDate = startDate.addingTimeInterval(86400 * index)
    return Text("\(someDate, formatter: Self.weekFormat)")
}

...

ForEach(0..<weekdays.count) { weekday in
    VStack{
        RoundedRectangle(cornerRadius: 9, style: .continuous)
            .stroke(Color.blue)
            .frame(width: 68.5, height: 68.5)
        
        self.addDay(Double(weekday))
        Text(String(Double(weekday)))
        Text(self.weekdays[weekday])
        
        
    }
}

This once again boils down to the way SwiftUI works, you can think of SwiftUI as a "function" of its states. In my example you can pass in any weekday, with it's position in the array, and you'll be able to generate a UI.


-Side note-

I did not know that you could access the static version of self by using Self (capital S). How convenient Swift is!

Upvotes: 1

Related Questions