With SwiftUI being just released, everyone has been really excited and focused on (re)building their nice UI using it. However, not many have dug deep into building and handling the lifecycle of “real world” app — which primarily consists of fetching data and displaying it in a list.
This article will dive into the basics of maintaining state throughout the “real world” app lifecycle. This will include:
URLSessionto request and decode JSON payloads into
- Gracefully handling
errorstates using a generic
- Conditionally displaying
Viewelements based on state.
- Fetching and displaying images from a
UIActivityIndicatoras an unofficial
The demo app
For the purposes of this article, I will be building and app which catalogues a store’s products. The JSON API used is in the following format:
The UI of the app itself will be simple — displaying the image, title, and body of each product in a row.
With the API laid out above, we can represent our data using the following models:
It’s important to note that any model which contains UI data must conform to
Identifiable. These will also need to conform to
Decodable as they will be constructed using
JSONDecoder when parsing the network responses — hence the property names and types matching those in the API.
Stateful list rendering
The basis of our stateful list rendering will revolve around this generic
LoadableState<T> enum. The
fetched case has an associated value of a
Result type— this will contain either a
success or a
This state represents three cases:
.loading: Awaiting a network response.
.fetched(.failure(FetchError)): The network response contained an error, where
FetchErrorrepresents a description of the error.
.fetched(.success(T)): The network response returned successfully with a payload, where
Trepresent the model type.
Usage in a View Builder
From this state, we can display the relevant content to the user. Ideally, we would
switch over this enum in the
body of our
View and return different
View elements accordingly — but, at least in the current SwiftUI beta, this is not possible. Xcode will show you compile errors such as,
Closure containing control flow statement cannot be used with function builder ‘ViewBuilder’
when conditionally displaying view elements, or,
Function declares an opaque return type, but the return statements in its body do not have matching underlying types
View types are returned conditionally.
To get around this issue, SwiftUI provides a wrapper on any
View object to be used such that there is only one underlying type being returned — appropriately named
AnyView wrapper, our
body can be kept slim and the logic can be extracted into this
stateContent computed property. While loading, we’ll show a spinner in the view (👀 have a look at the bottom of this article for implementation details). If the response contains an error, we’ll show the error description as plain
Text, otherwise we’ll show the list of products. We can wrap this all in a
NavigationView named “Products” in the
body for a finished look.
SwiftUI introduces many new tools — one of those,
BindableObject, serves data between a model and a view. In this
BindableObject class, we will perform a
URLSession network request and parse the results into a
LoadableState. If no errors occur,
JSONDecoder is used to parse the data into its respective models. The state, initialized as
.loading, will use
PassThroughSubject (available through
BindableObject) to send an update to its listeners whenever it is changed.
Views become listeners for these updates using the
@ObjectBinding property delegate, as seen above in the
Rendering images from a URL
As the JSON API used returns image URLs, we’ll need to convert those to
UIImages. A similar approach used for product fetching can be used here, however, rather than sending updates in the form of a state, we will send
Using this data, we can construct
LoadableImageView — a stateful image
View. A spinner will be show in this view while awaiting image data.
That’s all! With this, we’ve built a stateful list that fetches data and images from an external source. 🎉
Bonus: ActivityIndicator View
SwiftUI comes with a lot of out-of-the-box
View elements, but have yet to port a select few. Luckily, the
UIViewRepresentable protocol enables us to build out the missing ones. Use this snippet to show a spinner on any