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 using prepend to always emit the loading state first.
  • If we get an Output from the publisher, we can use map and inspect the collection to see if it is empty and transform the Output 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.

iOS Architect @ Raya

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store