Nice article! I have to quibble a bit, however, with the concept demonstrated in PostListViewModel.
As a general rule one shouldn’t be triggering any type of API fetch or other behavioral side effect from a view model’s init method.
Why? Because it can suffer from premature activation.
I wrote about this in some length in SwiftUI and How NOT to Initialize Bindable Objects. (The title of which should indicate just how long this issue has been hidden in SwiftUI.)
Observe the following code where we implement a very simple “menu” view that launches your view.
struct MainMenuView : View {
var body: some View {
NavigationView {
NavigationLink(destination: PostListView()) {
Text("View Posts")
}
}
}
}
Straightforward, yes? The user clicks on a View Posts link to view the posts.
The problem, however, is that as soon as MainMenuView is displayed the NavigationLink is displayed and in order for NavigationLink to be generated its parameters must be passed to it. Which in turn means that PostListView() must be instantiated… along with its variables.
So the dependent variable PostListViewModel will also be created at that point in time and it will be initialized… which means your API request will be triggered from the wrong page.
Now, that could be what was intended… but in most cases it probably isn’t.
The solution is simple. Just change your init function into a load function.
load() {
self.webservice.fetchPosts { posts in
self.posts = posts.map(PostViewModel.init)
}
}
And call it when PostListView actually appears…
var body: some View {
List(postListVM.posts, id: \.id) { post in
...
}
.onAppear {
self.postListVM.load()
}
}
Now the current list of posts will be loaded if and when the user actually visits that page.
Once more, the approach shown will in fact work as presented. But there’s a bug hiding in there, just waiting to snare some unwary developer who just wanted to move PostListView to a different location in the app.
Again, enjoyed that article!