Introduction

In modern iOS apps, it’s common to have screens that load content from a server, which can take some time. When users open a screen that requires data loading, it’s essential to provide feedback and communicate the loading process to them. While displaying a default loading spinner is a straightforward approach, there’s an alternative method called Skeletons that is widely used in mobile and web applications. In this blog post, we’ll explore how to use placeholders in SwiftUI to indicate loading states and enhance the user experience.

SwiftUI’s redacted modifier

Before SwiftUI and iOS 14 developers had to implement placeholders themselves or rely on third-party libraries. However, since iOS 14, SwiftUI provides a system API that makes it easier to create placeholders. By applying the .redacted(reason: .placeholder) modifier to a SwiftUI View, we can instantly turn it into a skeleton.

Text("Hello world!").redacted(reason: .placeholder)



Implementing Placeholders in SwiftUI

To illustrate the usage of placeholders, let’s consider an example of an app that displays a list of cities fetched from a server.

CityView

The CityView consists of a title, subtitle, and image.



By adding the .redacted(reason: .placeholder) modifier to this view, we can visualize both the skeleton state and the actual content using SwiftUI’s Preview feature.



CityListView

The CityListView displays multiple cities as a list.



By applying the .redacted(reason: .placeholder) modifier to the view, it cascades down to the individual CityViews, creating a skeleton effect.



Design Considerations

When deciding whether to show the actual view or its skeleton, one approach is to make the view’s model optional. If the model is nil, the skeleton will be displayed; otherwise, the actual view will be shown. The placeholder model represents data that looks good as a skeleton.

.redacted(reason: model == nil ? .placeholder : [])



CityListLoadingView

Alternatively, for more flexibility, we can create a separate view specifically for the loading state. In this case, we can use a CityListLoadingView that holds CityListView with a placeholder model and applies the .redacted(reason: .placeholder) modifier to it.



Managing States

Suppose we have a CitiesScreen that maintains a CitiesScreen State, which has two cases: .loading and .loaded.

Depending on the current state, we can use either the CityListView or the CityListLoadingView. By separating the states, we ensure a clear distinction between the .loading and .loaded states. Additionally, it’s possible to include an .error case and implement a dedicated CityListErrorView for loading error.



Conclusion

Utilizing placeholders in SwiftUI allows us to effectively communicate the loading process to users, resulting in a better app experience. By leveraging the .redacted(reason: .placeholder) modifier, we can enhance the visual feedback during data loading. Feel free to download the example project and explore how placeholders can be implemented in your own app.