TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: How to assign an AppStorage value to another value?

Forums > SwiftUI

Hello,

I have 2 views. A Main view and a Settings view. On the Settings view, I can select working hours with a picker. The value will be saved in an AppStorage value after I click the save button. That works. Now, when I call the settings view again, I want that the saved value for work hours will be preselected. At the moment there is just the default value selected. I've tried to assign it like this:

savedvalue = timevalue

But I get the error: Cannot use instance member within property initializer.

Here is my full code of both views:

import SwiftUI

struct ContentView: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    @AppStorage("ValueKey") var timevalue: Int = 0

    @State var hours: String = ""

    var body: some View {

        //Navigation View
        NavigationView{
            VStack{
                Text(String(timevalue))
                NavigationLink(destination: SettingsView()) {
                    Label("Settings", systemImage: "gearshape").foregroundColor(.gray).font(.subheadline).opacity(100)

                }
            }

        }

    }
}

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

struct SettingsView: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    @AppStorage("ValueKey") var timevalue: Int = 0
    @State var savedvalue: Int = 0
    @State var withpause: Bool = true

    savedvalue = timevalue

    var body: some View {
        //Setting VStack
        VStack{
            NavigationView {
                //Create a form
                Form {
                    Section(header: Text("Nuvora Time Settings")) {
                        //Time Picker
                        Picker("Working hours", selection: $savedvalue) {
                            Text("35").tag(0)
                            Text("38.5").tag(1)
                            Text("40").tag(2)
                        }
                        //Toggle Switch
                        Toggle(isOn: $withpause) {
                            Text("Include pause")
                        }

                    }
                }
                .navigationBarTitle("Settings")

            }

            //Save Button
            NavigationLink(destination: ContentView()){
                Button(action:{ self.presentationMode.wrappedValue.dismiss()

                    timevalue = savedvalue
                })
                {
                    Text("Save").foregroundColor(.gray).font(.title3).padding(70)
                }
            }       .frame(width: 300, height: 50.0)
        }
    }

    struct SettingsView_Previews: PreviewProvider {
        static var previews: some View {
            SettingsView()
        }
    }
}

I really appreciate any help how to fix that.

Thanks in advance Kay

2      

Get rid of this:

    savedvalue = timevalue

You can't have bare statements like that at the root level of your struct.

Instead, you can do this:

Picker("Working hours", selection: $savedvalue) {
//...blah blah blah
}
.onAppear {
    savedvalue = timevalue
}

3      

Take a breath and think about your problem. To me, it seems you are struggling with two concepts. Take a big problem and break it down into smaller, solvable problems. There's another issue with naming, but that's minor.

First, let's look at passing data from one view to another view. Because views are structs, you know you get a COPY of values that you pass in. But you want two views to SHARE the same value. This is solved using bindings.

@twostraws has some great video on bindings. Please watch them and take notes. Then watch them again while reviewing your notes. In your case, you have one view where you want to store the index to an array of time values. You are offering the user a choice between 35, 38.5, and 40. However you are storing the index to this choice, 0, 1, or 2. Recommend you change your variable to timeIndex (camelCase!).

So focus first on this feature.

A binding allows you to pass a value from one view to a second view. You can change the value in the second view and it is automatically updated in the first view.

The second feature is a requirement to save this index using AppStorage. You are using the AppStorage property wrapper. Nice! Review this article for more info on AppStorage. In short, whenever you change the value, SwiftUI automatically writes that value to UserDefaults. Luxury!

So the cool part is, if you define the timeIndex using the AppStorage property wrapper, then pass that to the second view as a binding, then whenever you change the value (either in view 1, or in view 2) the timeIndex is automatically saved to UserDefaults.

Sweet!

struct ContentView: View {
    // whenever you change timeIndex, it is automatically stored!
    @AppStorage("ValueKey") var timeIndex = 0

    var body: some View {
        NavigationView{
            VStack{
                Text("Time index: \(timeIndex)").padding()
                // Pass timeIndex as a binding to the SettingsView
                NavigationLink(destination: SettingsView( timeIndex: $timeIndex)) {
                    Label("Settings", systemImage: "gearshape").foregroundColor(.gray).font(.subheadline).opacity(100)
                }
            }
        }
    }
}

struct SettingsView: View {
    // Bind this to the variable in the parent view.
    @Binding var timeIndex: Int   // populated by parent view
    @State private var withpause = true  // Swift KNOWS this is a Bool

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Nuvora Time Settings")) {
                    // when you pick a new index,
                    // the binding is sent to the parent
                    // which automatically stores it.
                    Picker("Working hours", selection: $timeIndex) {
                        Text("35").tag(0)
                        Text("38.5").tag(1)
                        Text("40").tag(2)
                    }
                    Toggle(isOn: $withpause) {
                        Text("Include pause")
                    }
                }
            }
            .navigationBarTitle("Settings")
        }
    }
}

3      

Hi @kaypohl

Are you changing the @AppStorage when you change the picker? If so the Picker requires <Binding<>> for it so you can use it instead of savedvalue and it will show the stored value when you go back to it.

struct ContentView: View {
    @AppStorage("ValueKey") var timeValue: Int = 0

    var body: some View {
        // other code
        Picker("Working hours", selection: $timeValue) {
            Text("35").tag(0)
            Text("38.5").tag(1)
            Text("40").tag(2)
        }
        // other code
    }
}

3      

Thanks everyone for your answers.

@roosterboy I get the point of using .onAppear for the picker. But it don't let me select a new value while the picker is shown.

@NigelGee It would work, but I want to have the save button to change the AppStorage value.

@Obelix I changed my code accordingly. And it works, but the same as the solution from NigelGee. Due to the binding, it changes immediately. I would like to change the value only after clicking the save button. Do you have a solution for that?

2      

@roosterboy I get the point of using .onAppear for the picker. But it don't let me select a new value while the picker is shown.

Okay, yeah, that's because the Picker is in a Form and so the onAppear fires every time you pick a value.

Honestly, if you aren't going to make use of the AppStorage binding I would just do this:

struct SettingsView: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    @State var savedvalue: Int
    @State var withpause: Bool = true

    //get the value directly from UserDefaults
    init() {
        let time = UserDefaults.standard.integer(forKey: "ValueKey")
        _savedvalue = State(wrappedValue: time)
    }

    var body: some View {
        //Setting VStack
        VStack{
            //Create a form
            Form {
                Section(header: Text("Nuvora Time Settings")) {
                    //Time Picker
                    Picker("Working hours", selection: $savedvalue) {
                        Text("35").tag(0)
                        Text("38.5").tag(1)
                        Text("40").tag(2)
                    }
                    //Toggle Switch
                    Toggle(isOn: $withpause) {
                        Text("Include pause")
                    }

                }
            }
            .navigationBarTitle("Settings")

            //Save Button
            Button {
                //use UserDefaults to directly set the value when the user Saves
                UserDefaults.standard.set(savedvalue, forKey: "ValueKey")
                self.presentationMode.wrappedValue.dismiss()
            } label: {
                Text("Save").foregroundColor(.gray).font(.title3).padding(70)
            }
            .frame(width: 300, height: 50.0)
        }
    }
}

2      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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.