Complete SwiftUI beginner here. I started to play with SwiftUI using Nick Sarno aka "Swiftful Thinking" project (https://github.com/SwiftfulThinking/SwiftfUI-Map-App-MVVM). This project is a Map app with NVMM structure. It consists of database of locations and shows them on user-friendly interface consisting of Map, locations list and detail view overlay. I really like the interface and smooth feeling of this app.
There is viewModel used as @EnvironmentObject in all child views (LocationsViewModel.swift). Inside this model the database consisting of array of structs (stored as static let in LocationsDataService.swift file) is initialized and loaded into variable called locations (line 35 of LocationsViewModel.swift).
init() {
let locations = LocationsDataService.locations
self.locations = locations
self.mapLocation = locations.first!
self.updateMapRegion(location: locations.first!)
}
I am trying to update the database with additional locations on the runtime, but I can't figure out how to reload views to show new data on the list of locations without app restart. From my complete beginner point of view LocationsViewModel is set as ObservableObject, so any change to it should force views observing it to redraw, but it is not occurring. I even tried to "re-init" the view model to once again load database into let, but no luck..
Can you advice and point me into right direction how to force app to refresh itself when Locations database is changed (either new locations are added or some are removed)?
Edit - More details:
I am updating LocationsDataService.locations from local JSON file which I create/update upon receiving it from my Apple Watch. File is stored in AppSupportDirectory. If my JSON file is not empty and is correctly written, then its contents are decoded and loaded into LocationsViewModel().locations using below function:
class DataManager: ObservableObject{
func LoadFromJson() {
do{
let fileUrl = pathForAppSupportDirectoryAsUrl()?.appendingPathComponent("pinData.JSON")
let fileData = try Data(contentsOf: (fileUrl)!)
if !fileData.isEmpty {
let arrayOfPins = try JSONDecoder().decode([Location].self, from: fileData)
LocationsViewModel().locations = arrayOfPins
}
}catch{
print(error)
}
}
func pathForAppSupportDirectoryAsUrl() -> URL?{
var appSupportDirectory: URL?
do{
appSupportDirectory = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
}catch{
print("problem!")
}
return appSupportDirectory
}
}
LoadFromJson() is called after the file is successfully encoded and saved inside app support directory. I know that writing to local file and then decoding it works, as my locations dataset is correctly updated if I kill the app and start it again.
Thank you so much
Karol
I recommend separating LocationsViewModel
from DataManager
and just have DataManager
return the decoded Location
array:
final class DataManager {
func loadLocationPinsFromJson() throws -> [Location] {
let fileUrl = pathForAppSupportDirectoryAsUrl()?.appendingPathComponent("pinData.JSON")
let fileData = try Data(contentsOf: (fileUrl)!)
return try JSONDecoder().decode([Location].self, from: fileData)
}
private func pathForAppSupportDirectoryAsUrl() -> URL? {
try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
}
}
In LocationsViewModel
you could initialize an instance of DataManager
or pass in a shared instance and use it to load the Location
array when LocationsListView
appears:
final class LocationsViewModel: ObservableObject {
@Published var locations = [Location]()
private let dataManager = DataManager() // initialize here or pass in shared instance
func loadLocations() {
do {
locations = try dataManager.loadLocationPinsFromJson()
} catch {
// handle loading error...
}
}
}
Here LocationsListView
initializes LocationsViewModel
as a StateObject
and calls viewModel.loadLocations
in the onAppear
modifier:
struct LocationsListView: View {
@StateObject private var viewModel = LocationsViewModel()
var body: some View {
Text("Locations List Here (viewModel.locations)...")
.onAppear(perform: viewModel.loadLocations)
}
}