Skip to content

Instantly share code, notes, and snippets.

@ikesyo
Created September 3, 2023 16:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ikesyo/b86158d882e9a3ef29fa247b29209369 to your computer and use it in GitHub Desktop.
Save ikesyo/b86158d882e9a3ef29fa247b29209369 to your computer and use it in GitHub Desktop.
ScreenState.swift
public enum ScreenState<Value, Error: Swift.Error> {
case loading
case failed(Error)
case empty
case loaded(Value)
public var isLoading: Bool {
if case .loading = self { return true }
return false
}
public var isEmpty: Bool {
if case .empty = self { return true }
return false
}
public var value: Value? {
if case .loaded(let value) = self { return value }
return nil
}
public var error: Error? {
if case .failed(let error) = self { return error }
return nil
}
/// 自身が `.loaded` であれば状態を変更しない。そうでなければ状態を `.loading` に変更する。
public mutating func setLoadingIfNeeded() {
switch self {
case .failed, .empty:
self = .loading
case .loading, .loaded:
break
}
}
/// 自身が `.loaded` 以外の状態であれば状態を `.failed` に変更し、trueを返す。
/// そうでなければ状態を変更せず、falseを返す。
public mutating func handleError(_ error: Error) -> Bool {
switch self {
case .loaded:
return false
case .loading, .failed, .empty:
self = .failed(error)
return true
}
}
}
public struct ScreenStateProjector<Value, Failure: Error, Loading: View, Empty: View, Loaded: View, Failed: View>: View {
public let state: ScreenState<Value, Failure>
private let refreshAction: (() -> Void)?
private var loading: () -> Loading
private var empty: ((() -> Void)?) -> Empty
private var loaded: (Value) -> Loaded
private var failed: (Failure, (() -> Void)?) -> Failed
public init(
_ state: ScreenState<Value, Failure>,
refreshAction: (() -> Void)?,
@ViewBuilder onLoading: @escaping (() -> Loading) = {
LoadingView(isLoading: true)
},
@ViewBuilder onFailed: @escaping (Failure, (() -> Void)?) -> Failed,
@ViewBuilder onEmpty: @escaping ((() -> Void)?) -> Empty = {
EmptyStateView(action: $0)
},
@ViewBuilder onLoaded: @escaping (Value) -> Loaded
) {
self.state = state
self.refreshAction = refreshAction
self.loading = onLoading
self.empty = onEmpty
self.loaded = onLoaded
self.failed = onFailed
}
public init(
_ state: ScreenState<Value, Failure>,
refreshAction: (() -> Void)?,
@ViewBuilder onLoading: @escaping (() -> Loading) = {
LoadingView(isLoading: true)
},
@ViewBuilder onEmpty: @escaping ((() -> Void)?) -> Empty = {
EmptyStateView(action: $0)
},
@ViewBuilder onLoaded: @escaping (Value) -> Loaded
) where Failed == ErrorMessageView<Failure> {
self.state = state
self.refreshAction = refreshAction
self.loading = onLoading
self.empty = onEmpty
self.loaded = onLoaded
self.failed = {
ErrorMessageView($0, action: $1)
}
}
public var body: some View {
switch state {
case .loading:
loading()
case .failed(let error):
failed(error, refreshAction)
case .empty:
empty(refreshAction)
case .loaded(let value):
loaded(value)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment