Generic Shimmer Loading Skeletons in SwiftUI
Apps often require separate loading, loaded, empty and error views. With SwiftUI we can solve this problem generically and give any view the ability to represent these states.
The loading state
Presenting a shimmering skeleton of the expected data during loading has become an increasing popular design (see the Facebook, Yelp and Meetup apps for example). Facebook provides a popular pod FBShimmering to to help you achieve this effect in UIKit, although it requires that you provide loading images for all of the possible placeholders.
The redacted modifier
With iOS 14 and the redacted
modifier SwiftUI allows you to generate loading placeholders with a single line of code. Since these placeholders are dynamically generated, they scale to any size and device orientation, allowing for seamless placeholder generation across MacOS, iPadOS, and iOS, which is a huge time savings over having to write code to dynamically generate images or making static images for each possible resolution every time your design changes.
For example adding the modifier .redacted(reason: .placeholder)
to the HStack
in the code below transforms each cell in the list into a placeholder.
Creating a shimmer ViewModifier
The redacted
views don’t shimmer, but SwiftUI makes it easy to write our own shimmer ViewModifier
.
The modifier simply creates a back and whiteLinearGradient
with black on the left and right and white in the middle that is vertically centered offscreen to the right. Over the course of 2 seconds it moves until it is vertically centered offscreen to the left. By using the .screen
blendmode we can add the luminosity of the gradient to the underlying view, which gives us the shimmering effect.
We can now apply the shimmer
modifier to our redacted view and instantly we have a loading state with just two lines of code!
However, with SwiftUI and Combine we can do even better than this and add generic loading, error and empty views for any list view.
Modeling loading states
A core tenant of SwiftUI is composition, the idea that we can build up our views from generic reusable components. A single app may make dozens of network requests, each of which requires a loading view. Furthermore each network request could fail, requiring an error view and its possible that a request could return an empty collection, where good design generally dictates that you show an empty state view instead of a blank screen. In Swift, heterogenous types are best represented as an enum
and we can create an enum
called CollectionLoadingState
to represent the various possibilities.
Combine allows us to represent a network request as a Publisher
. We can extend publisher to lift any Publisher
who’s Output
is a Collection
into a Publisher<CollectionLoadingState<Output, Error>, Never>
generically.
This consists of handling each of the possible states:
- We always begin in the
.loading
state, which we can insure by usingprepend
to always emit the loading state first. - If we get an
Output
from the publisher, we can usemap
and inspect the collection to see if it is empty and transform theOutput
into either.empty
or.loaded
accordingly. - Finally if we receive an error, we can use
.catch
to replace the failing publisher with one that always emits the.error
state instead. By materializing the error we insure that the publisher never fails, since failure is now always captured by our.error
state instead.
Generic View Composition
Now that we know how to get a CollectionLoadingState
, we can make a SwiftUI.View
that can generically display a CollectionLoadingState
. All that our view needs to do is switch over the state and supply the view and animations for each possible state:
Conclusion
I’ve made the shimmer
modifier and the CollectionLoadingState
into its own swift package that you can import into your project. The sample project includes an example app that uses the code in this article. You can easily extend this idea to generically model other states (single values, search, submit buttons, paginated collections, etc.)
Generic compositional design allows us to identify a common problem, and extract out its common logic, while leaving behind generic parameters that can be used to customize particular parts of our solution. Its a powerful pattern that lets us reuse code, increase maintainability, consolidate side effects, and build great infrastructure while still providing the flexibility for customization.