SwiftUI Basics: List Fetching

Mat Schmid
Jun 12 · 4 min read

Disclaimer: This was written within the first week of SwiftUI being released. Some of this content might be outdated at the time of reading.

📦 Source code for this project is linked at the bottom of the article.


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:

  • Using BindableObject + URLSession to request and decode JSON payloads into Identifiable models.
  • Gracefully handling loading and and error states using a generic LoadableState enum.
  • Conditionally displaying View elements based on state.
  • Fetching and displaying images from a URL.
  • Adding UIActivityIndicator as an unofficial View element.

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.

Models

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 failure response.

This state represents three cases:

  • .loading: Awaiting a network response.
  • .fetched(.failure(FetchError)): The network response contained an error, where FetchError represents a description of the error.
  • .fetched(.success(T)): The network response returned successfully with a payload, where T represent 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

when different 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.

With this 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.

Fetching data

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 ProductListView.

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 Data.

Using this data, we can construct LoadableImageView — a stateful image View. A spinner will be show in this view while awaiting image data.

A spinner is used as the placeholder while an image loads

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 View:



Thanks for reading! Feel free to follow me on Twitter and Github for more like this 👨🏻‍💻

Mat Schmid

Written by

iOS Developer @shopify, Prev Comp Sci @ Carleton University

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade