-
Notifications
You must be signed in to change notification settings - Fork 1
/
ViewStateModifier.swift
101 lines (96 loc) · 4.37 KB
/
ViewStateModifier.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//
// ViewStateModifier.swift
// ViewStateController
//
// Created by Manu on 22/02/2023.
//
import Foundation
import SwiftUI
public extension View {
/// Adds a view state modifier that can display different views depending on the state of a `ViewStateController`.
/// - Parameters:
/// - controller: The `ViewStateController` that controls the state of the view.
/// - indicatorView: The view to show when the view is loading.
/// - initialLoadingType: The type of loading indicator to show when the view is initially loading.
/// - loadedView: The view to show when the view is not loading and has valid information.
/// - loadingAfterInfoType: The type of loading indicator to show when the view is loading after it has already
/// displayed valid information.
/// - errorView: The view to show when the view has an error.
/// - loadingAfterErrorType: The type of loading indicator to show when the view is loading after it has displayed
/// an error.
func withViewStateModifier<Info, IndicatorView: View, LoadedView: View>(
controller: ViewStateController<Info>,
indicatorView: IndicatorView = ProgressView(),
initialLoadingType: LoadingModifierType = .material(),
loadedView: @escaping (Info) -> LoadedView,
loadingAfterInfoType: LoadingModifierType = .horizontal(),
errorView: @escaping (Error) -> ErrorView,
loadingAfterErrorType: LoadingModifierType = .overCurrentContent(alignment: .trailing)
) -> some View {
modifier(
ViewStateModifier<Info, IndicatorView, LoadedView>(
controller: controller,
initialLoadingModifier: .init(
type: initialLoadingType,
indicatorView: indicatorView
),
loadedView: loadedView,
loadingAfterInfoModifier: .init(
type: loadingAfterInfoType,
indicatorView: indicatorView
),
errorView: errorView,
loadingAfterErrorModifier: .init(
type: loadingAfterErrorType,
indicatorView: indicatorView
)
)
)
}
}
struct ViewStateModifier<Info, IndicatorView: View, LoadedView: View>: ViewModifier {
private var controller: ViewStateController<Info>
private var initialLoadingModifier: LoadingViewModifier<IndicatorView>
private var loadedView: (Info) -> LoadedView
private var loadingAfterInfoModifier: LoadingViewModifier<IndicatorView>
private var errorView: (Error) -> ErrorView
private var loadingAfterErrorModifier: LoadingViewModifier<IndicatorView>
init(
controller: ViewStateController<Info>,
initialLoadingModifier: LoadingViewModifier<IndicatorView>,
loadedView: @escaping (Info) -> LoadedView,
loadingAfterInfoModifier: LoadingViewModifier<IndicatorView>,
errorView: @escaping (Error) -> ErrorView,
loadingAfterErrorModifier: LoadingViewModifier<IndicatorView>
) {
self.controller = controller
self.initialLoadingModifier = initialLoadingModifier
self.loadedView = loadedView
self.loadingAfterInfoModifier = loadingAfterInfoModifier
self.errorView = errorView
self.loadingAfterErrorModifier = loadingAfterErrorModifier
}
func body(content: Content) -> some View {
if controller.isInitialLoading {
// Initial loading modifier displayed on the initial loading state.
content.modifier(initialLoadingModifier)
} else if let info = controller.latestValidInfo {
// If we have valid info loaded we display it:
loadedView(info)
.if(controller.isLoading) { view in
// If we are on a subsequent loading, we add the modifier.
view.modifier(loadingAfterInfoModifier)
}
} else if let error = controller.latestValidError {
// If we have a value error we display it:
errorView(error)
.if(controller.isLoading) { view in
// If we are on a subsequent loading, we add the modifier.
view.modifier(loadingAfterErrorModifier)
}
} else {
// Otherwise, we display the initial content.
content
}
}
}