Modern app development with SwiftUI and Combine eliminates a lot of boilerplate code. Tools like Playgrounds extend this further to allow quick prototyping. One common issue is to load arbitrary content (JSON or binary) from network and display it in a SwiftUI view. The other day I was looking for a simple yet elegant way to use in quick prototypes. I came up with a reusable view that can load arbitrary content. After a couple of iterations I discovered some interesting tricks to share.
Content Loading stages: initial, in progress, success, and failure
Content loading is at least three stage process:
SwiftUI encourages creating small, reusable views, and use composition to create the complete picture. Each stage of the content loading process will require a view. The container view will compose the result.
The content loading process can be represented using an enum with associated values. Swift provides Result
type, that could be used to represent completed process. However, I find it more convenient to represent the result as separate success
and failure
cases. This is due to added support of switch
statement in function builders (Xcode 12).
It is easy to see what we are aiming for: the container view can switch over the loading state to provide a corresponding view.
enum RemoteContentLoadingState<Value> {
case initial
case inProgress
case success(_ value: Value)
case failure(_ error: Error)
}
You probably know by now that a view in SwiftUI can not load content by itself. This is because SwiftUI views are value types. Content loading requires back and forth communication possible only with reference types.
The way to provide remote content to a SwiftUI view is by using ObservedObject
property wrapper and ObservableObject
protocol.
ObservableObject
protocol synthesizes a publisher that emits before the object has changed.
Typically, you would create a class that confirms to ObservableObject
and reference it using ObservedObject
property wrapper from your view.
Because we are building a reusable view it makes sense to inject ObservableObject
. To start, declare RemoteContent
protocol.
protocol RemoteContent : ObservableObject {
associatedtype Value
var loadingState: RemoteContentLoadingState<Value> { get }
func load()
func cancel()
}
#ios #apple #xcode #swiftui #swift