3

I am relatively new to SwiftUI and I'm trying to work on my first app. I am trying to use a segmented picker to give the user an option of changing between a DayView and a Week View. In each on of those Views, there would be specific user data that whould be shown as a graph. The issue I am having is loading the data. I posted the code below, but from what I can see, the issue comes down to the following:

When the view loads in, it starts with loading the dayView, since the selectedTimeInterval = 0. Which is fine, but then when the users presses on the "Week" in the segmented Picker, the data does not display. This due to the rest of the View loading prior to the .onChange() function from the segmented picker running. Since the .onChange is what puts the call into the viewModel to load the new data, there is no data. You can see this in the print statements if you run the code below.

I would have thought that the view load order would have been

load segmented picker run the .onChange if the value changed load the rest of the view but the order actual is

load segmented picker, load the rest of the view (graph loads with no data here!!!!!) run the .onChange if the value has changed. I am pretty lost so any help would be great! Thank you so much!

import SwiftUI 
import OrderedCollections

class ViewModel: ObservableObject{ 
    @Published var testDictionary: OrderedDictionary<String, Int> = ["":0]

    public func daySelected() {
        testDictionary = ["Day View Data": 100]
    }

    public func weekSelected() {
        testDictionary = ["Week View Data": 200]
    }
}


struct ContentView: View {
    @State private var selectedTimeInterval = 0
    @StateObject private var vm = ViewModel()
    
    var body: some View {
        VStack {
            Picker("Selected Date", selection: $selectedTimeInterval) {
                Text("Day").tag(0)
                Text("Week").tag(1)
            }
            .pickerStyle(SegmentedPickerStyle())
            .onChange(of: selectedTimeInterval) { _ in
                let _ = print("In on change")
                //Logic to handle different presses of the selector
                switch selectedTimeInterval {
                case 0:
                    vm.daySelected()
                case 1:
                    vm.weekSelected()
                default:
                    print("Unknown Selected Case")
                }
            }
            switch selectedTimeInterval {
            case 0:
                let _ = print("In view change")
                Day_View()
            case 1:
                let _ = print("In view change")
                Week_View(inputDictionary: vm.testDictionary)
            default:
                Text("Whoops")
            }
        }
    }
}

struct Day_View: View {
    
    var body: some View {
        Text("Day View!")
    }
}

struct Week_View: View {
    @State private var inputDictionary: OrderedDictionary<String,Int>
    
    init(inputDictionary: OrderedDictionary<String,Int>) {
        self.inputDictionary = inputDictionary
    }
    
    var body: some View {
        
        let keys = Array(inputDictionary.keys)
        let values = Array(inputDictionary.values)
        VStack {
            Text(keys[0])
            Text(String(values[0]))
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
2
  • Remove @State property wrapper from inputDictionary property in Week_View and you are good to go because self.inputDictionary = inputDictionary is not correct way to initialize @State property in init Commented Sep 24, 2022 at 18:13
  • If your concern is to update the week or day views after the model changes its variable you must use a binding in week view and not a local state var. or you can define your vm as an environment object and use it in your week view. Commented Sep 24, 2022 at 18:19

2 Answers 2

1

In your WeekView, change

@State private var inputDictionary: OrderedDictionary<String,Int>

to

private let inputDictionary: OrderedDictionary<String,Int>

@State is for the local state of the view. The idea is that you are initing it with initial state and from then on the view itself will change it and cause re-renders. When the WeekView is re-rendered, SwiftUI is ignoring the parameter you pass into the init and copying it from the previous WeekView to maintain state.

But, you want to keep passing in the dictionary from ContentView and cause re-renders from the parent view.

Sign up to request clarification or add additional context in comments.

Comments

0

The main issue is that the initialization of the @State property wrapper is wrong.

You must use this syntax

@State private var inputDictionary: OrderedDictionary<String,Int>

init(inputDictionary: OrderedDictionary<String,Int>) {
    _inputDictionary = State(wrappedValue:  inputDictionary)
}

Or – if inputDictionary is not going to be modified – just declare it as (non-private) constant and remove the init method

let inputDictionary: OrderedDictionary<String,Int>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.