SwiftUI View doesn't reload immediately after pulling new data from URL

Viewed 416

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 Answers

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.

Related