4

I'm learning Swift and SwiftUI and trying to understand how MVVM, data flow, and network requests work. I created a simple app that gets data in JSON from the URL and shows covid information for the selected country.

I have settings where I can change the country and hide covid data with toggles. I want to instantly reload all data from the URL in the MainView.swift if country changed in the Settings.swift

When I change the country in the settings > close settings > view data remains the same, it only updates if I force close the app then reopen.

Here is a link to a video with issue

Model.swift

import SwiftUI

struct Covid19Data: Codable {
    let country:                String
    let cases:                  Int
    let todayCases:             Int
    let deaths:                 Int
    let todayDeaths:            Int
    let recovered:              Int
    let active:                 Int
    let critical:               Int
    let casesPerOneMillion:     Int
    let deathsPerOneMillion:    Int
    let totalTests:             Int
    let testsPerOneMillion:     Int
}

let countrySelection = ["Ukraine", "Canada", "Germany"]

ViewModel.swift

import SwiftUI

class CovidFetcher: ObservableObject {
    
    @Published var jsonFetch: Covid19Data?
    @ObservedObject var userSettings = UserSettings()
    
    
    init(){
        load()
    }
    
    func load() {
        let url = URL(string: "https://coronavirus-19-api.herokuapp.com/countries/\(countrySelection[userSettings.selectedCountryIndex])")
        
        URLSession.shared.dataTask(with: url!) { (data, response, error) in
            if let d = data {
                let webData = try? JSONDecoder().decode(Covid19Data.self, from: d)
                DispatchQueue.main.async {
                    self.jsonFetch = webData
                }
            }
        }.resume()
    }
}

class UserSettings: ObservableObject {
    
    @Published var selectedCountryIndex: Int {
        didSet {
            UserDefaults.standard.set(selectedCountryIndex, forKey: "selectedCountryIndex")
        }
    }
    
    init() {
        self.selectedCountryIndex = UserDefaults.standard.object(forKey: "selectedCountryIndex") as? Int ?? 0       
    }
}

MainView.swift

import SwiftUI

struct MainView: View {
    
    @ObservedObject var fetcher = CovidFetcher()
    @ObservedObject var userSettings = UserSettings()

    @State var showOrderSheet = false
    
    var body: some View {
        VStack {
            VStack {
                Button(action: {
                    showOrderSheet.toggle()
                }) {
                    Image(systemName: "gear")
                        .font(.system(size: 20))
                }
                .fullScreenCover(isPresented: $showOrderSheet) {
                    Settings(fetcher: self.fetcher, userSettings: self.userSettings)
                }
            }.offset(x: -155, y: 5)
            VStack {
                Text(countrySelection[userSettings.selectedCountryIndex])
                    .bold()
                    .frame(width: 150, height: 170, alignment: .center)
            }
            VStack {
                List {
                    Group {
                        if userSettings.casesToggle {
                            HStack {
                                Text("Cases:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.cases ?? 0)")
                            }
                        }
                        if userSettings.casesTodayToggle {
                            HStack {
                                Text("Cases Today:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.todayCases ?? 0)")
                            }
                        }
                        if userSettings.deathsToggle {
                            HStack {
                                Text("Deaths:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.deaths ?? 0)")
                            }
                        }
                        if userSettings.deathsTodayToggle {
                            HStack {
                                Text("Deaths Today:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.todayDeaths ?? 0)")
                            }
                        }
                        if userSettings.recoveredToggle {
                            HStack {
                                Text("Recovered:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.recovered ?? 0)")
                            }
                        }
                        if userSettings.activeToggle {
                            HStack {
                                Text("Active:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.active ?? 0)")
                            }
                        }
                        if userSettings.criticalToggle {
                            HStack {
                                Text("Critical:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.critical ?? 0)")
                            }
                        }
                    }
                    Group {
                        if userSettings.casesPerOneMillionToggle {
                            HStack {
                                Text("Cases Per One Million:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.casesPerOneMillion ?? 0)")
                            }
                        }
                        if userSettings.deathsPerOneMillionToggle {
                            HStack {
                                Text("Deaths Per One Million:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.deathsPerOneMillion ?? 0)")
                            }
                        }
                        if userSettings.totalTestsToggle {
                            HStack {
                                Text("Total Tests:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.totalTests ?? 0)")
                            }
                        }
                        if userSettings.testsPerOneMillionToggle {
                            HStack {
                                Text("Tests Per One Million:")
                                Spacer()
                                Text("\(self.fetcher.jsonFetch?.testsPerOneMillion ?? 0)")
                            }
                        }
                    }
                }
                .listStyle(InsetGroupedListStyle())
            }
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarHidden(true)
    }
}

Settings.swift

import SwiftUI

struct Settings: View {
    
    @ObservedObject var fetcher = CovidFetcher()
    @ObservedObject var userSettings = UserSettings()
    
    @Environment (\.presentationMode) var presentationMode
        
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Change country")) {
                    Picker(selection: $userSettings.selectedCountryIndex, label: Text("Select Country")) {
                        ForEach(0 ..< countrySelection.count, id: \.self) { countryInx in
                            Text(countrySelection[countryInx])
                        }
                    }
                }
                Section(header: Text("Select Data")) {
                    VStack {
                        List {
                            Group {
                                Toggle("Cases",                      isOn: $userSettings.casesToggle)
                                Toggle("Cases Today",                isOn: $userSettings.casesTodayToggle)
                                Toggle("Deaths",                     isOn: $userSettings.deathsToggle)
                                Toggle("Deaths Today",               isOn: $userSettings.deathsTodayToggle)
                                Toggle("Recovered",                  isOn: $userSettings.recoveredToggle)
                                Toggle("Active",                     isOn: $userSettings.activeToggle)
                                Toggle("Critical",                   isOn: $userSettings.criticalToggle)
                            }
                            Group {
                                Toggle("Cases Per One Million",      isOn: $userSettings.casesPerOneMillionToggle)
                                Toggle("Deaths Per One Million",     isOn: $userSettings.deathsPerOneMillionToggle)
                                Toggle("Total Tests",                isOn: $userSettings.totalTestsToggle)
                                Toggle("Tests Per One Million",      isOn: $userSettings.testsPerOneMillionToggle)
                            }
                        }
                    }
                }
                Button(action: {
                    userSettings.hasOnBoarded = false
                }) {
                    Text("Reset Onboarding ")
                }
            }
            .listStyle(InsetGroupedListStyle())
            .navigationBarTitle("Settings", displayMode: .inline)
            .navigationBarItems(trailing: Button("Done") {
                self.fetcher.load()
                presentationMode.wrappedValue.dismiss()
            })
        }
    }
}

URL with JSON data - "https://coronavirus-19-api.herokuapp.com/countries/"

I've tried to call fetcher.load() in the settings when pressing the "Done" button but it doesn't reload the view, also googled a lot but cannot find an answer.

1 Answer 1

2

The issue is that toggling $userSettings.selectedCountryIndex updates the userSettings instance belonging to Settings, but that's a different instance than fetcher.userSettings.

Therefore, when fetcher.load() is called on tapping Done, fetcher.userSettings.selectedCountryIndex is still the old value.

EDIT: sorry, I forgot to list a solution. You could just make userSettings within CovidFetcher computed (since CovidFetcher never mutates it anyways). That way the UserDefaults value will always be read as the current value.

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

1 Comment

I got it to work by moving @Published var selectedCountryIndex: Int part from UserSettings to CovidFetcher, but I don't understand how can I make userSettings within CovidFetchercomputed

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.