Skip to content

Instantly share code, notes, and snippets.

@mdb1
Created January 8, 2023 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mdb1/dd71a798e7a9a5e91c4fc9885f990d93 to your computer and use it in GitHub Desktop.
Save mdb1/dd71a798e7a9a5e91c4fc9885f990d93 to your computer and use it in GitHub Desktop.
A simple view to demonstrate how to use the ViewStateWrapper
import SwiftUI
/// In this example:
/// * The view model holds and publishes the view state wrapper value.
/// * The view reacts to changes by holding an @StateObject reference to the view model.
/// * The initial loading is an empty screen with a ProgressView in the middle.
/// * Once we have some data, the subsequent loadings will not override the loaded data,
/// and instead, will just display a ProgressView in the toolbar.
struct ViewStateExampleView: View {
@StateObject private var viewModel: ViewModel = .init()
var body: some View {
NavigationView {
Group {
switch viewModel.stateWrapper.state {
case .initial:
loadUsersButton
case .loading:
if let users = viewModel.stateWrapper.lastInfoLoaded {
loadedBody(users)
} else {
ProgressView()
}
case let .loaded(users):
loadedBody(users)
case .error:
errorBody
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("View State Example")
.toolbar {
toolbarBody
}
}
}
private var loadUsersButton: some View {
Button("Load users") {
viewModel.load()
}
}
private var errorBody: some View {
VStack {
Text("Error")
loadUsersButton
}
}
private func loadedBody(_ users: [User]) -> some View {
VStack {
ForEach(users) { user in
Text(user.name)
}
loadUsersButton
}
}
@ViewBuilder
private var toolbarBody: some View {
// Display a ProgressView in the toolbar, only if it's not the initial loading.
if viewModel.stateWrapper.state == .loading && !viewModel.stateWrapper.isInitialLoading {
ProgressView()
}
}
}
extension ViewStateExampleView {
final class ViewModel: ObservableObject {
@Published var stateWrapper = ViewStateWrapper<[User]>()
func load() {
if Bool.random() {
loadUsers()
} else {
loadUsersAndGetError()
}
}
private func loadUsers() {
withAnimation {
stateWrapper.state = .loading
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
withAnimation {
self?.stateWrapper.state = .loaded(
[.init(name: "Manu"), .init(name: "Muralla")]
)
}
}
}
}
private func loadUsersAndGetError() {
withAnimation {
stateWrapper.state = .loading
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
withAnimation {
self?.stateWrapper.state = .error(SomeError())
}
}
}
}
}
}
struct User: Codable, Identifiable {
let name: String
var id: String { name }
}
struct SomeError: Error {}
struct ViewStateExample_Previews: PreviewProvider {
static var previews: some View {
ViewStateExampleView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment