Created
January 8, 2023 14:03
-
-
Save mdb1/e3a65b84ccc3340b27b524fd05b6eced to your computer and use it in GitHub Desktop.
A reusable way to handle the state of the views in SwiftUI
This file contains hidden or 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 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