Sometimes we need to invoke an async function for fetching data before presenting a SwiftUI view. Therefore, a common flow is showing a spinner while the data is being fetched and then showing the main view. Moreover, if an error occurs, we show a failure view with a retry button. Let’s dive in how to build such view in a generic way.
As said before, our container view, let’s call it ContentPrepareView (similar naming to Apple’s ContentUnavailableView), has three distinct states: loading, failure, and success (named as “content” in the enum).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We’ll go for a fully generic implementation where each of the view state corresponds to a view builder. This gives as flexibility if in some places we want to use custom loading views or different failure view. But on the other hand, most of the time we just want to use a common loading and failure views, that is why we set default values for loading and failure view builders (see below). In addition to view builders, we need an async throwing task closure which handles the data fetching/preparation. If we put it all together, then the ContentPrepareView becomes this:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Since loading, failure and success views can be any kind of views, then our view needs to be a generic view. The body of the view has a switch-case for creating a view for the current view state. One thing to note here is that the onLoad view modifier is a custom one, and the idea is that it makes sure that the content preparation work only runs once per view life-time (onAppear() or task() can run multiple times). The reasoning is that we want to have an experience where we show the loading spinner only when the view is presented the first time, not when it appears again. The loadTask function is async and has responsibility of running the passed in async task closure and updating the current view state.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
In this example we used a custom FailureView and it is a small view wrapping Apple’s ContentUnavailableView. It sets a label, description and handles the creation of the retry button.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here is an example how to use the final ContentPrepareView. For demo purposes, it fails the first load and allows succeeding the second.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters