A simple view to demonstrate how to use the ViewStateWrapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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