Skip to content

Instantly share code, notes, and snippets.

@mdb1
Created January 8, 2023 14:03
Show Gist options
  • Select an option

  • Save mdb1/e3a65b84ccc3340b27b524fd05b6eced to your computer and use it in GitHub Desktop.

Select an option

Save mdb1/e3a65b84ccc3340b27b524fd05b6eced to your computer and use it in GitHub Desktop.
A reusable way to handle the state of the views in SwiftUI
import Foundation
/// Enum that represents the state of a view that loads information from the backend.
enum ViewState<Info: Any> {
/// Represents non state. Useful when used in combination with other ViewState.
case initial
/// Represents the loading state.
case loading
/// Represents an error.
case error(_: Error)
/// Represents that the information has been loaded correctly.
case loaded(_ info: Info)
/// Returns true if state is loading.
var isLoading: Bool {
guard case .loading = self else { return false }
return true
}
/// Returns info associated to loaded state.
var info: Info? {
guard case let .loaded(info) = self else { return nil }
return info
}
/// Returns info associated to error state.
var error: Error? {
guard case let .error(error) = self else { return nil }
return error
}
}
/// ViewState Wrapper that allows to handle different states based on the loading counter and the previous info loaded.
struct ViewStateWrapper<Info: Any> {
/// State reference.
/// When new state is loading, it increases loadingCounter.
/// When new state is loaded, it sets lastInfoLoaded using the info associated.
/// When an error happens, resets both loadingCounter and lastInfoLoaded.
/// Otherwise, it resets the last info loaded.
var state: ViewState<Info> = .initial {
willSet {
if newValue.isLoading {
loadingCounter += 1
} else if let info = newValue.info {
lastInfoLoaded = info
} else if newValue.error != nil {
loadingCounter = 0
lastInfoLoaded = nil
} else {
lastInfoLoaded = nil
}
}
}
/// Initializes a ViewStateWrapper based on the current state
init(state: ViewState<Info> = .initial) {
self.state = state
}
/// Number of times loading state was set
var loadingCounter: Int = 0
/// Info associated to the last time loaded state was set
var lastInfoLoaded: Info?
/// Returns true only if loading state was set once
var isInitialLoading: Bool {
loadingCounter == 1
}
/// Use this property when you need to make changes to specific parts of your view.
/// Example: When you want to display a ProgressView while deleting an item from a list.
var modifyingIds: [String]?
}
extension ViewState: Equatable {
public static func == (lhs: ViewState<Info>, rhs: ViewState<Info>) -> Bool {
switch (lhs, rhs) {
case (.initial, .initial):
return true
case (.loading, .loading):
return true
case (.loaded, .loaded):
return true
case (.error, .error):
return true
default:
return false
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment