Search code examples
swiftswiftuiswift-playground

How to update variable proper in different struct Swift


In the code below, the variable "yourVariable" is defined in a struct. Then I use a slider to change the "yourVariable" but when I try and use the variable in the SecondView, it uses the original variable and not the updated one. I have been told I need to update the property, should I do this? If yes can someone please explain why.

import SwiftUI
import PlaygroundSupport

struct MyVariables {
    static var yourVariable = 500000.0
}



struct SecondView : View {
    @Environment(\.presentationMode) var presentationMode
    var string = MyVariables.yourVariable
    var body: some View {
        VStack {Button("\(string)") {
            self.presentationMode.wrappedValue.dismiss()
            } 
        }
    }
}


struct ContentView : View {
    @State private var showingSheet = false

    @State public var step = 0
    @State public var text = ""
    @State public var slide = 20.0
    @State public var slidetwo = MyVariables.yourVariable + 60000
    var r0 : Double {
        rvalues[(wearingMaskToggle ? 1:0) + (washingHandsToggle ? 2:0) + (quarantineToggle ? 8:0) + (coverMouthToggle ? 4:0)]
    }
    @State private var rvalues = [6.5, 6.1, 5.3, 4.8, 4.2, 3.5, 3.1, 2.9, 1.75, 1.5, 1.2, 1.0, 0.92, 0.82, 0.75, 0.7]

    var body: some View {
        ZStack {
            Color.gray
                .edgesIgnoringSafeArea(.all)
                VStack {
                    HStack {
                        Button(action: {
                            if self.slidetwo > 10000{
                                self.slidetwo -= 1000 //When rand = 10050, it still works
                            }
                        }, label: {
                            Image(systemName: "minus")
                        })
                        Slider(value: $slidetwo, in: 10000...1000000, step: 1000)
                            .padding(20)
                            .accentColor(Color.green)
                        Button(action: {
                            if self.slidetwo < 1000000{
                                self.slidetwo += 1000
                            }
                        }, label: {
                            Image(systemName: "plus")
                        })
                    }.foregroundColor(Color.green) .padding(.horizontal, 100)
                }
                Text("Initial Population: \(Int(slidetwo))")

                Button("Show Sheet") {
                    self.showingSheet.toggle()
                }
                .sheet(isPresented: $showingSheet) {
                    SecondView()
                }
            }
        }
}
PlaygroundPage.current.setLiveView(ContentView())

Solution

  • First off, the slider is updating the variable slidetwo witch has an initial value of 560000.

    When you open the second view you are getting the value of the struct into the variable string witch will result in having always the value that's defined by yourVariable, so moving the slider does not affect at all the second view.

    In order to properly do this you could use a Binding in case that you want the variable to be changed inside the second view and reflecting those changes in the ContentView or a simple un-initialized @State var

    Going with the second option it would result in replacing, in the SecondView var string = MyVariables.yourVariable with @State var string: String

    Then in the ContentView inside the sheet you call SecondView(string: String(slidetwo)) instead of SecondView()

    I couldn't figure out why would you need a struct in here. All it does is hold a default value and nothing more, and you don't update the variable anywhere. You can't update it directly because it can't be binded.

    Update 1:

    Based on your comment, what I understand is that you want multiple variables to the SecondView, if that's the case you are better off using and ObservableObject in conjunction of EnvironmentObject

    And it's rather easy to do. First create a class that will hold all your variables. This class inherits from the ObservableObject

    class MyVariables: ObservableObject {
    
        @Published var sliderTwo: Double = 0.0
        @Published var yourVariable = 500000.0 // <- I don't know the purpose of this
    
        init() {
            sliderTwo = yourVariable + 60000   // <- This is the original value, in the original code
        }
    }
    

    Now on the first view you want to initialize the class and use it where you need it. At this point the slide two will not update the @State variable, but the variable in the model, and we will need to pass the ObservedObject to the second view like so.

    struct ContentView: View {
        @ObservedObject var model: MyVariables = MyVariables()
        //@State public var slidetwo = MyVariables.yourVariable + 60000 <- no longer needed
        // ... change every other reference of slidertwo to model.slidertwo
        // except the following one witch instead of $slidertwo it will be $model.slidertwo
        Slider(value: $model.slidertwo, in: 10000...1000000, step: 1000)
        // ...
        .sheet(isPresented: $showingSheet) {
            SecondView().environmentObject(self.model) // send the model as an environment object
        }
    }
    

    Now we are sending the model with the data updated on the first view, to the second view, and any changes will be reflected in both views, just one last thing, capture this EnvironmentObject on the second view

    struct SecondView: View {
        // var string = MyVariables.yourVariable <- no longer needed
        @EnvironmentObject var model: MyVariables
        // ....
    }
    

    In this case the model.yourVariable still doesn't change because we are updating the model.slidertwo variable. Changes made to the model will be reflected in both views.

    From the experience that I have this is the correct way to do things in SwiftUI