In this demo, you’ll move some data-handling code from a view into a view model class.
Open TheMet app in the Starter folder.
Open ContentView
and refresh the preview.
This app lets the user search The Metropolitan Museum of Art, New York for objects matching a query term. The app starts with “rhino” as the query term. Try searching for another term:
Some objects are in the public domain, so the app can download and display an image.
The detail view has a link to the object’s web page. This works in a simulator but not in the preview.
Other objects aren’t in the public domain, so the app loads the web page.
Network requests are handled by TheMetService
.
This service has two methods: getObjectIDs from queryTerm
and getObject from objectID
.
If the user doesn’t enter a query term, the app searches for “the”.
The app downloads, at most, 20 objectIDs to avoid hammering the server.
ContentView maintains the array of objects as a State variable.
And has a method to call the network service methods. This data handling shouldn’t be in the view; it belongs in a ViewModel.
Create a new Swift file named TheMetStore and create a group named ViewModel to contain it.
In this new file, create an Observable
class:
@Observable
class TheMetStore {
}
For now, you don’t need to worry about what @Observable
means, you’ll learn all about it in the next lesson.
Now, move the first three properties from ContentView
to TheMetStore
. Remove @State
and don’t initialize maxIndex
:
var objects: [Object] = []
private let service = TheMetService()
let maxIndex: Int
Add an init method:
init(_ maxIndex: Int = 20) {
self.maxIndex = maxIndex
}
TheMetStore
can be instantiated without a maxIndex
parameter. The default value is 20.
Next, move fetchObjects
from ContentView
to TheMetStore
:
And that’s all you need for your view model.
Now, go back to ContentView
, to use this. Add a State
variable:
@State var store = TheMetStore()
Then, wherever Xcode complains about objects
not in scope, insert store.
:
List(store.objects, id: \.objectID) { object in
And also for fetchObjects
:
fetchObjectsTask = Task {
do {
store.objects = []
try await store.fetchObjects(for: query)
} catch {}
}
And right down at the bottom:
.overlay {
if store.objects.isEmpty { ProgressView() }
}
}
.task {
do {
try await store.fetchObjects(for: query)
} catch {}
}
That’s it! Refresh the preview and check it all works.