UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Modifying state during view update, this will cause undefined behavior. Need help

Forums > SwiftUI

Hi there. Today I try to write a program to calculate everyday in week by swiftUI.But I got two error like my pic. Here is my code.Can anyone help? thx a lot

//
//  ContentView.swift
//  Calculator day in week
//
//  Created by chen on 2022/1/22.
//2022.1.3 Monday
// monday tuesday wednesday thursday friday saturday sunday
//
import SwiftUI

struct ContentView: View {

    @State private var choosedYear = 1
    @State private var choosedMonth = 1
    @State private var choosedDay = 1
    //@FocusState private var isOnFocus:Bool

    let weeks = ["Monday" ,"Tuesday" ,"Wednesday", "Thursday" ,"Friday","Saturday" ,"Sunday"]
    // 1.1.1 is Monday
    let months = ["January","February","March","April","May","June","July","August","September","October","November","December"]
    //@State private var calculatorWeek:String = ""

    var month:[Int] {
        var month:[Int] = []
        for i in 1...12{
            month.append(i)
        }
        return month
    }
    var day:[Int]{
        var day:[Int] = []
        for i in 1...31{
            day.append(i)
        }
        return day
    }
    func calculatorWeek() -> String{
        var i = 0
        var dayChange:Int{
            if choosedMonth < 3{
                choosedMonth += 12
                choosedYear -= 1
            }
            let dayChange = (choosedYear * 365) + (choosedYear / 4) + (choosedYear / 400) - (choosedYear / 100) + ((153 * choosedMonth - 457) / 5) + choosedDay - 306
            return dayChange
        }
        i = dayChange % 7
        return weeks[i]

    }

    var body: some View {
        NavigationView{
            Form{
                Section{
                    TextField("input years",value: $choosedYear,format:.number)
                        .keyboardType(.decimalPad)
                    Picker("choose month",selection: $choosedMonth){
                        ForEach(month,id:\.self){
                            Text("\($0)")
                        }
                    }//.pickerStyle(.segmented)
                    Picker("choose day",selection: $choosedDay){
                        ForEach(day,id:\.self){
                            Text("\($0)")
                        }
                    }//.pickerStyle(.segmented)
                }header: {
                    Text("Please input years here")
                }

                Section{
                    Text("\(months[choosedMonth - 1]) \(choosedDay),\(choosedYear) is \(calculatorWeek())")
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

2      

The problem is that in your function calculatorWeek you change @State variables where the change itself triggers another view update which could lead to an unexpected behaviour.

What you could do is to pass them as parameters into the function and don't do changes on the variables itself but on the copies.

3      

The reason is that when you select a month/year you are trying to change it when displayed eg select choosedMonth then try to change the display to something else. However in calculatorWeek method you returning a String so make a copy then calculate on that like below.

func calculatorWeek() -> String {
    var i = 0
    var newMonth = choosedMonth
    var newYear = choosedYear

    var dayChange:Int{
        if choosedMonth < 3 {
            newMonth += 12
            newYear -= 1
        }

        let dayChange = (newYear * 365) + (newYear / 4) + (newYear / 400) - (newYear / 100) + ((153 * newMonth - 457) / 5) + choosedDay - 306

        return dayChange
    }

    i = dayChange % 7

    return weeks[i]
}

Just a couple other pointers to make an array of numbers you can do.

let monthIndex = Array(1...12)

but you use this in the Picker so you change you picker to:

Picker("Choose month",selection: $choosedMonth){
    ForEach(1...12, id:\.self) { // add the range here
        Text("\($0)")
    }
}

So a bit of refactoring end with

struct ContentView: View {
    @State private var chosenYear = 1
    @State private var chosenMonth = 1
    @State private var chosenDay = 1

    let weeks = ["Monday" ,"Tuesday" ,"Wednesday", "Thursday" ,"Friday","Saturday" ,"Sunday"]
    // 1.1.1 is Monday
    let months = ["January","February","March","April","May","June","July","August","September","October","November","December"]

    var body: some View {
        NavigationView{
            Form{
                Section("Please input years here") {
                    TextField("input years",value: $chosenYear, format:.number)
                        .keyboardType(.decimalPad)

                    Picker("Choose month",selection: $chosenMonth){
                        ForEach(1...12, id:\.self){
                            Text("\($0)")
                        }
                    }

                    Picker("Choose day",selection: $chosenDay){
                        ForEach(1...31, id:\.self){
                            Text("\($0)")
                        }
                    }
                }

                Section{
                    Text("\(months[chosenMonth - 1]) \(chosenDay), \(chosenYear) is \(calculatorWeek())")
                }
            }
        }
    }

    func calculatorWeek() -> String {
        var i = 0
        var newMonth = chosenMonth
        var newYear = chosenYear

        var dayChange: Int{
            if chosenMonth < 3 {
                newMonth += 12
                newYear -= 1
            }

            let dayChange = (newYear * 365) + (newYear / 4) + (newYear / 400) - (newYear / 100) + ((153 * newMonth - 457) / 5) + chosenDay - 306

            return dayChange
        }

        i = dayChange % 7

        return weeks[i]
    }
}

**PS not sure if you logic is correct as 1.1.1 is coming out as Tuesday!

You might be better to use dates formatters etc as dates are very complicated!

3      

Definitely you should look into using Calendar and Date methods to do your calculations. Much easier and accurate (1/1/2022 gives me Sunday with your code, which is incorrect) and they will handle all of the edge cases for you.

All of the manual calculations you are doing in your calculatorWeek function can be replaced with this*:

var weekday: String {
    let cal = Calendar.current
    if let chosenDate = cal.date(from: DateComponents(year: chosenYear,
                                                      month: chosenMonth,
                                                      day: chosenDay)) {
        return chosenDate.formatted(.dateTime.weekday(.wide))
        //i.e., Sunday, Monday, Tuesday, etc
    } else {
        return "Could not calculate weekday" //or whatever you want
    }
}

* For iOS 15. For earlier versions, you would need to use a DateFormatter object. It would still be better than the manual stuff you are currently doing, though.

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.