Home > Mobile >  SwiftUI - Update body only once when @EnviromentObject and @State changes
SwiftUI - Update body only once when @EnviromentObject and @State changes

Time:07-01

I have created this example project replicating my app architecture, in which a random number is generated in a Manager class and then displayed in the SecondaryView. Inside this view, calculations need to be made based on this randomNumber and displayed as well (randomNumberTimesTen). This example is of course non-sensical and could be fixed by changing the architecture. The important part is the relation between @State and @EnvironmentObject and they need to stay where they are.

How can I make sure that the body of SecondaryView is only updated once after the randomNumberTimesTen has been calculated and not twice? At the moment it happens right away after the randomNumber was generated and this causes issues in my app as I need the value of randomNumberTimesTen before the body can be drawn correctly.

I think the problematic part might be the .onChange(of: manager.randomNumber) as this is probably only executed when the body is updated. Is there a way to move this somewhere else? The multiplyByTen() needs to stay in the SecondaryView though.

Example code:

struct ContentView: View {
    @StateObject var manager = Manager()
    var body: some View {
        VStack {
            SecondaryView()
                .environmentObject(manager)
            Button("Random Number") {
                manager.generateRandomNumber()
            }
        }
    }
}

struct SecondaryView: View {
    @State var randomNumberTimesTen: Int = 0
    @EnvironmentObject var manager: Manager

    var body: some View {

        VStack {
            Text("Random Number: \(manager.randomNumber)")
            Text("Random Number x10: \(randomNumberTimesTen)")
        }
        .onChange(of: manager.randomNumber) { _ in
            multiplyByTen()
        }
    }

    func multiplyByTen() {
        randomNumberTimesTen = manager.randomNumber * 10
    }
}


public class Manager: ObservableObject {
    @Published public var randomNumber: Int = 0

    public func generateRandomNumber() {
        self.randomNumber = Int.random(in: 0..<10)
    }
}

CodePudding user response:

You have two "source of truth" changed, so two updates. So make one - manager.randomNumber as it is really so, and everything else should be just based on it.

This exact case can be fixed by making randomNumberTimesTen calculable instead of state, like

struct SecondaryView: View {

    @EnvironmentObject var manager: Manager

    private var randomNumberTimesTen: Int {
        self.manager.randomNumber * 10
    }

    var body: some View {

        VStack {
            Text("Random Number: \(manager.randomNumber)")
            Text("Random Number x10: \(randomNumberTimesTen)")
        }
    }
}
  • Related