-
-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathExampleTimeTravel.swift
144 lines (124 loc) · 3.48 KB
/
ExampleTimeTravel.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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import Atoms
import SwiftUI
struct InputState: Equatable {
var text: String = ""
var latestInput: Int?
mutating func input(number: Int) {
text += String(number)
latestInput = number
}
mutating func clear() {
text = ""
latestInput = nil
}
}
struct InputStateAtom: StateAtom, Hashable {
func defaultValue(context: Context) -> InputState {
InputState()
}
}
struct NumberInputScreen: View {
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
@WatchState(InputStateAtom())
var inputState
var body: some View {
VStack {
TextField("Tap numbers", text: .constant(inputState.text))
.disabled(true)
.textFieldStyle(.roundedBorder)
.padding()
ForEach(matrix, id: \.first) { row in
HStack {
ForEach(row, id: \.self) { number in
Button {
inputState.input(number: number)
} label: {
Text("\(number)")
.font(.largeTitle)
.frame(width: 80, height: 80)
.background(Color(inputState.latestInput == number ? .systemOrange : .secondarySystemBackground))
.clipShape(Circle())
}
}
}
}
Button("Clear") {
inputState.clear()
}
.buttonStyle(.borderedProminent)
Spacer()
}
.frame(maxHeight: .infinity)
.padding()
.navigationTitle("Time Travel")
}
}
struct TimeTravelDebug<Content: View>: View {
@ViewBuilder
let content: () -> Content
@State
var snapshots = [Snapshot]()
@State
var position = 0
@ViewContext
var context
var body: some View {
AtomScope {
ZStack(alignment: .bottom) {
content()
slider
}
.padding()
}
.scopedObserve { snapshot in
Task {
snapshots = Array(snapshots.prefix(position + 1))
snapshots.append(snapshot)
position = snapshots.endIndex - 1
}
}
}
var slider: some View {
VStack(alignment: .leading) {
Text("History (\(position + 1) / \(snapshots.count))")
Slider(
value: Binding(
get: { Double(position) },
set: { value in
Task { @MainActor in
position = Int(value)
context.restore(snapshots[position])
}
}
),
in: 0...Double(max(0, snapshots.endIndex - 1))
)
}
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(8)
.shadow(radius: 5)
}
}
// swift-format-ignore: AllPublicDeclarationsHaveDocumentation
public struct ExampleTimeTravel: View {
public init() {}
public var body: some View {
TimeTravelDebug {
NumberInputScreen()
}
}
}
struct TimeTravelScreen_Preview: PreviewProvider {
static var previews: some View {
AtomRoot {
TimeTravelDebug {
NumberInputScreen()
}
}
}
}