Home > Enterprise >  Issue with SwiftUI Segmented Picker
Issue with SwiftUI Segmented Picker

Time:09-26

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()
    }
}

CodePudding user response:

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.

CodePudding user response:

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>
  • Related