Skip to content

Commit 2f1621f

Browse files
committed
166
1 parent 9d205ac commit 2f1621f

16 files changed

Lines changed: 1569 additions & 0 deletions

File tree

0166-navigation-pt7/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## [Point-Free](https://www.pointfree.co)
2+
3+
> #### This directory contains code from Point-Free Episode: [SwiftUI Navigation: Links, Part 2](https://www.pointfree.co/episodes/ep166-swiftui-navigation-links-part-2)
4+
>
5+
> Let’s explore “tag” and “selection”-based navigation links in SwiftUI. What are they for and how do they compare with the link and link helpers we’ve used so far? We will then take a step back to compare links with all of the other forms of navigation out there and propose a “Grand Unified Theory of Navigation.”

0166-navigation-pt7/SwiftUINavigation/SwiftUINavigation.xcodeproj/project.pbxproj

Lines changed: 535 additions & 0 deletions
Large diffs are not rendered by default.

0166-navigation-pt7/SwiftUINavigation/SwiftUINavigation.xcodeproj/project.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"colors" : [
3+
{
4+
"idiom" : "universal"
5+
}
6+
],
7+
"info" : {
8+
"author" : "xcode",
9+
"version" : 1
10+
}
11+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "iphone",
5+
"scale" : "2x",
6+
"size" : "20x20"
7+
},
8+
{
9+
"idiom" : "iphone",
10+
"scale" : "3x",
11+
"size" : "20x20"
12+
},
13+
{
14+
"idiom" : "iphone",
15+
"scale" : "2x",
16+
"size" : "29x29"
17+
},
18+
{
19+
"idiom" : "iphone",
20+
"scale" : "3x",
21+
"size" : "29x29"
22+
},
23+
{
24+
"idiom" : "iphone",
25+
"scale" : "2x",
26+
"size" : "40x40"
27+
},
28+
{
29+
"idiom" : "iphone",
30+
"scale" : "3x",
31+
"size" : "40x40"
32+
},
33+
{
34+
"idiom" : "iphone",
35+
"scale" : "2x",
36+
"size" : "60x60"
37+
},
38+
{
39+
"idiom" : "iphone",
40+
"scale" : "3x",
41+
"size" : "60x60"
42+
},
43+
{
44+
"idiom" : "ipad",
45+
"scale" : "1x",
46+
"size" : "20x20"
47+
},
48+
{
49+
"idiom" : "ipad",
50+
"scale" : "2x",
51+
"size" : "20x20"
52+
},
53+
{
54+
"idiom" : "ipad",
55+
"scale" : "1x",
56+
"size" : "29x29"
57+
},
58+
{
59+
"idiom" : "ipad",
60+
"scale" : "2x",
61+
"size" : "29x29"
62+
},
63+
{
64+
"idiom" : "ipad",
65+
"scale" : "1x",
66+
"size" : "40x40"
67+
},
68+
{
69+
"idiom" : "ipad",
70+
"scale" : "2x",
71+
"size" : "40x40"
72+
},
73+
{
74+
"idiom" : "ipad",
75+
"scale" : "1x",
76+
"size" : "76x76"
77+
},
78+
{
79+
"idiom" : "ipad",
80+
"scale" : "2x",
81+
"size" : "76x76"
82+
},
83+
{
84+
"idiom" : "ipad",
85+
"scale" : "2x",
86+
"size" : "83.5x83.5"
87+
},
88+
{
89+
"idiom" : "ios-marketing",
90+
"scale" : "1x",
91+
"size" : "1024x1024"
92+
}
93+
],
94+
"info" : {
95+
"author" : "xcode",
96+
"version" : 1
97+
}
98+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import SwiftUI
2+
3+
enum Tab {
4+
case one, inventory, three
5+
}
6+
7+
class AppViewModel: ObservableObject {
8+
@Published var inventoryViewModel: InventoryViewModel
9+
@Published var selectedTab: Tab
10+
11+
init(
12+
inventoryViewModel: InventoryViewModel = .init(),
13+
selectedTab: Tab = .one
14+
) {
15+
self.inventoryViewModel = inventoryViewModel
16+
self.selectedTab = selectedTab
17+
}
18+
}
19+
20+
struct ContentView: View {
21+
@ObservedObject var viewModel: AppViewModel
22+
23+
var body: some View {
24+
TabView(selection: self.$viewModel.selectedTab) {
25+
Button("Go to 2nd tab") {
26+
self.viewModel.selectedTab = .inventory
27+
}
28+
.tabItem { Text("One") }
29+
.tag(Tab.one)
30+
31+
NavigationView {
32+
InventoryView(viewModel: self.viewModel.inventoryViewModel)
33+
}
34+
.tabItem { Text("Inventory") }
35+
.tag(Tab.inventory)
36+
37+
Text("Three")
38+
.tabItem { Text("Three") }
39+
.tag(Tab.three)
40+
}
41+
}
42+
}
43+
44+
struct ContentView_Previews: PreviewProvider {
45+
static var previews: some View {
46+
ContentView(viewModel: .init(selectedTab: .inventory))
47+
}
48+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import CasePaths
2+
import IdentifiedCollections
3+
import SwiftUI
4+
5+
struct Item: Equatable, Identifiable {
6+
let id = UUID()
7+
var name: String
8+
var color: Color?
9+
var status: Status
10+
11+
enum Status: Equatable {
12+
case inStock(quantity: Int)
13+
case outOfStock(isOnBackOrder: Bool)
14+
15+
var isInStock: Bool {
16+
guard case .inStock = self else { return false }
17+
return true
18+
}
19+
}
20+
21+
struct Color: Equatable, Hashable {
22+
var name: String
23+
var red: CGFloat = 0
24+
var green: CGFloat = 0
25+
var blue: CGFloat = 0
26+
27+
static var defaults: [Self] = [
28+
.red,
29+
.green,
30+
.blue,
31+
.black,
32+
.yellow,
33+
.white,
34+
]
35+
36+
static let red = Self(name: "Red", red: 1)
37+
static let green = Self(name: "Green", green: 1)
38+
static let blue = Self(name: "Blue", blue: 1)
39+
static let black = Self(name: "Black")
40+
static let yellow = Self(name: "Yellow", red: 1, green: 1)
41+
static let white = Self(name: "White", red: 1, green: 1, blue: 1)
42+
43+
var swiftUIColor: SwiftUI.Color {
44+
.init(red: self.red, green: self.green, blue: self.blue)
45+
}
46+
}
47+
}
48+
49+
class InventoryViewModel: ObservableObject {
50+
@Published var inventory: IdentifiedArrayOf<ItemRowViewModel>
51+
@Published var route: Route?
52+
53+
enum Route: Equatable {
54+
case add(Item)
55+
case row(id: ItemRowViewModel.ID, route: ItemRowViewModel.Route)
56+
}
57+
58+
init(
59+
inventory: IdentifiedArrayOf<ItemRowViewModel> = [],
60+
route: Route? = nil
61+
) {
62+
self.inventory = []
63+
self.route = route
64+
65+
for itemRowViewModel in inventory {
66+
self.bind(itemRowViewModel: itemRowViewModel)
67+
}
68+
}
69+
70+
private func bind(itemRowViewModel: ItemRowViewModel) {
71+
itemRowViewModel.onDelete = { [weak self, item = itemRowViewModel.item] in
72+
withAnimation {
73+
self?.delete(item: item)
74+
}
75+
}
76+
itemRowViewModel.onDuplicate = { [weak self] item in
77+
withAnimation {
78+
self?.add(item: item)
79+
}
80+
}
81+
itemRowViewModel.$route
82+
.map { [id = itemRowViewModel.id] route in
83+
route.map { Route.row(id: id, route: $0) }
84+
}
85+
.removeDuplicates()
86+
.dropFirst()
87+
.assign(to: &self.$route)
88+
self.$route
89+
.map { [id = itemRowViewModel.id] route in
90+
guard
91+
case let .row(id: routeRowId, route: route) = route,
92+
routeRowId == id
93+
else { return nil }
94+
return route
95+
}
96+
.removeDuplicates()
97+
.assign(to: &itemRowViewModel.$route)
98+
self.inventory.append(itemRowViewModel)
99+
}
100+
101+
func delete(item: Item) {
102+
withAnimation {
103+
_ = self.inventory.remove(id: item.id)
104+
}
105+
}
106+
107+
func add(item: Item) {
108+
withAnimation {
109+
self.bind(itemRowViewModel: .init(item: item))
110+
self.route = nil
111+
}
112+
}
113+
114+
func addButtonTapped() {
115+
self.route = .add(.init(name: "", color: nil, status: .inStock(quantity: 1)))
116+
117+
Task { @MainActor in
118+
try await Task.sleep(nanoseconds: 500 * NSEC_PER_MSEC)
119+
try (/Route.add).modify(&self.route) {
120+
$0.name = "Bluetooth Keyboard"
121+
}
122+
}
123+
}
124+
125+
func cancelButtonTapped() {
126+
self.route = nil
127+
}
128+
}
129+
130+
struct InventoryView: View {
131+
@ObservedObject var viewModel: InventoryViewModel
132+
133+
var body: some View {
134+
List {
135+
ForEach(
136+
self.viewModel.inventory,
137+
content: ItemRowView.init(viewModel:)
138+
)
139+
}
140+
.toolbar {
141+
ToolbarItem(placement: .primaryAction) {
142+
Button("Add") { self.viewModel.addButtonTapped() }
143+
}
144+
}
145+
.navigationTitle("Inventory")
146+
.sheet(unwrap: self.$viewModel.route.case(/InventoryViewModel.Route.add)) { $itemToAdd in
147+
NavigationView {
148+
ItemView(item: $itemToAdd)
149+
.navigationTitle("Add")
150+
.toolbar {
151+
ToolbarItem(placement: .cancellationAction) {
152+
Button("Cancel") { self.viewModel.cancelButtonTapped() }
153+
}
154+
ToolbarItem(placement: .primaryAction) {
155+
Button("Save") { self.viewModel.add(item: itemToAdd) }
156+
}
157+
}
158+
}
159+
}
160+
}
161+
}
162+
163+
struct TestView: View {
164+
@State var collection = [1, 2, 3]
165+
166+
var body: some View {
167+
ForEach(self.$collection, id: \.self) { $element in
168+
169+
}
170+
}
171+
}
172+
173+
struct InventoryView_Previews: PreviewProvider {
174+
static var previews: some View {
175+
let keyboard = Item(name: "Keyboard", color: .blue, status: .inStock(quantity: 100))
176+
177+
NavigationView {
178+
InventoryView(
179+
viewModel: .init(
180+
inventory: [
181+
.init(item: keyboard),
182+
.init(item: Item(name: "Charger", color: .yellow, status: .inStock(quantity: 20))),
183+
.init(item: Item(name: "Phone", color: .green, status: .outOfStock(isOnBackOrder: true))),
184+
.init(item: Item(name: "Headphones", color: .green, status: .outOfStock(isOnBackOrder: false))),
185+
],
186+
route: nil
187+
)
188+
)
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)