Skip to content

Commit a3aa5d4

Browse files
authored
Case key paths (pointfreeco#132)
* wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * cleanup * wip * wip * wip * wip * wip * Don't bother running windows tests
1 parent 5eb5899 commit a3aa5d4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2533
-2814
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@ concurrency:
1515

1616
jobs:
1717
library:
18-
runs-on: macos-12
18+
runs-on: macos-13
1919
strategy:
2020
matrix:
21-
xcode: ['13.4.1', '14.1']
21+
xcode:
22+
- '15.0'
23+
- '14.3.1'
2224

2325
steps:
24-
- uses: actions/checkout@v3
26+
- uses: actions/checkout@v4
2527
- name: Select Xcode ${{ matrix.xcode }}
2628
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
29+
- name: Skip macro validation
30+
run: defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES
2731
- name: Run tests
2832
run: make test
2933

@@ -38,15 +42,8 @@ jobs:
3842
steps:
3943
- uses: compnerd/gha-setup-swift@main
4044
with:
41-
branch: swift-5.8.1-release
42-
tag: 5.8.1-RELEASE
43-
- uses: actions/checkout@v3
45+
branch: swift-5.9.1-release
46+
tag: 5.9.1-RELEASE
47+
- uses: actions/checkout@v4
4448
- name: Build
4549
run: swift build -c ${{ matrix.config }}
46-
- name: Run tests (debug only)
47-
# There is an issue that exists in the 5.8.1 toolchain
48-
# which fails on release configuration testing, but
49-
# this issue is fixed 5.9 so we can remove the if once
50-
# that is generally available.
51-
if: ${{ matrix.config == 'debug' }}
52-
run: swift test

.github/workflows/format.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@ on:
88
jobs:
99
swift_format:
1010
name: swift-format
11-
runs-on: macOS-11
11+
runs-on: macOS-13
1212
steps:
13-
- uses: actions/checkout@v2
13+
- uses: actions/checkout@v4
1414
- name: Xcode Select
15-
run: sudo xcode-select -s /Applications/Xcode_13.0.app
16-
- name: Tap
17-
run: brew tap pointfreeco/formulae
15+
run: sudo xcode-select -s /Applications/Xcode_15.0.app
1816
- name: Install
19-
run: brew install Formulae/swift-format@5.5
17+
run: brew install swift-format
2018
- name: Format
2119
run: make format
2220
- uses: stefanzweifel/git-auto-commit-action@v4

.spi.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
version: 1
2+
builder:
3+
configs:
4+
- documentation_targets: [SwiftUINavigation, SwiftUINavigationCore]
5+
swift_version: 5.9

Examples/CaseStudies/08-Routing.swift

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ private let readMe = """
1111
in this library.
1212
"""
1313

14+
@CasePathable
1415
enum Destination {
1516
case alert(AlertState<AlertAction>)
1617
case confirmationDialog(ConfirmationDialogState<DialogAction>)
@@ -80,7 +81,7 @@ struct Routing: View {
8081
}
8182
}
8283
.navigationTitle("Routing")
83-
.alert(unwrapping: self.$destination, case: /Destination.alert) { action in
84+
.alert(self.$destination.alert) { action in
8485
switch action {
8586
case .randomize?:
8687
self.count = .random(in: 0...1_000)
@@ -90,10 +91,7 @@ struct Routing: View {
9091
break
9192
}
9293
}
93-
.confirmationDialog(
94-
unwrapping: self.$destination,
95-
case: /Destination.confirmationDialog
96-
) { action in
94+
.confirmationDialog(self.$destination.confirmationDialog) { action in
9795
switch action {
9896
case .decrement?:
9997
self.count -= 1
@@ -103,13 +101,13 @@ struct Routing: View {
103101
break
104102
}
105103
}
106-
.navigationDestination(unwrapping: self.$destination, case: /Destination.link) { $count in
104+
.navigationDestination(unwrapping: self.$destination.link) { $count in
107105
Form {
108106
Stepper("Count: \(count)", value: $count)
109107
}
110108
.navigationTitle("Routing link")
111109
}
112-
.sheet(unwrapping: self.$destination, case: /Destination.sheet) { $count in
110+
.sheet(unwrapping: self.$destination.sheet) { $count in
113111
NavigationStack {
114112
Form {
115113
Stepper("Count: \(count)", value: $count)

Examples/CaseStudies/09-CustomComponents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ extension View {
106106

107107
fileprivate func bottomMenu<Enum, Case, Content>(
108108
unwrapping value: Binding<Enum?>,
109-
case casePath: CasePath<Enum, Case>,
109+
case casePath: AnyCasePath<Enum, Case>,
110110
@ViewBuilder content: @escaping (Binding<Case>) -> Content
111111
) -> some View
112112
where Content: View {

Examples/CaseStudies/11-IfLet.swift

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import SwiftUI
22
import SwiftUINavigation
33

44
private let readMe = """
5-
This demonstrates to use the IfLet view to unwrap a binding of an optional into a binding of \
6-
an honest value.
5+
This demonstrates how to unwrap a binding of an optional into a binding of an honest value.
76
87
Tap the "Edit" button to put the form into edit mode. Then you can make changes to the message \
98
and either commit the changes by tapping "Save", or discard the changes by tapping "Discard".
@@ -18,25 +17,29 @@ struct IfLetCaseStudy: View {
1817
Section {
1918
Text(readMe)
2019
}
21-
IfLet(self.$editableString) { $string in
22-
TextField("Edit string", text: $string)
23-
HStack {
24-
Button("Discard") {
25-
self.editableString = nil
26-
}
27-
Button("Save") {
28-
self.string = string
29-
self.editableString = nil
20+
Binding(unwrapping: self.$editableString).map { $string in
21+
VStack {
22+
TextField("Edit string", text: $string)
23+
HStack {
24+
Button("Discard") {
25+
self.editableString = nil
26+
}
27+
Spacer()
28+
Button("Save") {
29+
self.string = string
30+
self.editableString = nil
31+
}
3032
}
3133
}
32-
} else: {
34+
}
35+
if self.editableString == nil {
3336
Text("\(self.string)")
3437
Button("Edit") {
3538
self.editableString = self.string
3639
}
3740
}
38-
.buttonStyle(.borderless)
3941
}
42+
.buttonStyle(.borderless)
4043
}
4144
}
4245

Examples/CaseStudies/12-IfCaseLet.swift

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import SwiftUI
33
import SwiftUINavigation
44

55
private let readMe = """
6-
This demonstrates to use the IfCaseLet view to destructure a binding of an enum into a binding \
7-
of one of its cases.
6+
This demonstrates how to destructure a binding of an enum into a binding of one of its cases.
87
98
Tap the "Edit" button to put the form into edit mode. Then you can make changes to the message \
109
and either commit the changes by tapping "Save", or discard the changes by tapping "Discard".
@@ -14,6 +13,7 @@ struct IfCaseLetCaseStudy: View {
1413
@State var string: String = "Hello"
1514
@State var editableString: EditableString = .inactive
1615

16+
@CasePathable
1717
enum EditableString {
1818
case active(String)
1919
case inactive
@@ -24,25 +24,29 @@ struct IfCaseLetCaseStudy: View {
2424
Section {
2525
Text(readMe)
2626
}
27-
IfCaseLet(self.$editableString, pattern: /EditableString.active) { $string in
28-
TextField("Edit string", text: $string)
29-
HStack {
30-
Button("Discard") {
31-
self.editableString = .inactive
32-
}
33-
Button("Save") {
34-
self.string = string
35-
self.editableString = .inactive
27+
self.$editableString.active.map { $string in
28+
VStack {
29+
TextField("Edit string", text: $string)
30+
HStack {
31+
Button("Discard", role: .cancel) {
32+
self.editableString = .inactive
33+
}
34+
Spacer()
35+
Button("Save") {
36+
self.string = string
37+
self.editableString = .inactive
38+
}
3639
}
3740
}
38-
} else: {
41+
}
42+
if !self.editableString.is(\.active) {
3943
Text("\(self.string)")
4044
Button("Edit") {
4145
self.editableString = .active(self.string)
4246
}
4347
}
44-
.buttonStyle(.borderless)
4548
}
49+
.buttonStyle(.borderless)
4650
}
4751
}
4852

Examples/CaseStudies/RootView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ struct RootView: View {
5454
NavigationLink("Synchronized bindings") {
5555
SynchronizedBindings()
5656
}
57-
NavigationLink("IfLet view") {
57+
NavigationLink("Optional bindings") {
5858
IfLetCaseStudy()
5959
}
60-
NavigationLink("IfCaseLet view") {
60+
NavigationLink("Enum bindings") {
6161
IfCaseLetCaseStudy()
6262
}
6363
} header: {

Examples/Inventory/Inventory.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class InventoryModel {
99
}
1010
var destination: Destination?
1111

12+
@CasePathable
1213
enum Destination: Equatable {
1314
case add(Item)
1415
case edit(Item)
@@ -84,10 +85,7 @@ struct InventoryView: View {
8485
}
8586
}
8687
.navigationTitle("Inventory")
87-
.navigationDestination(
88-
unwrapping: self.$model.destination,
89-
case: /InventoryModel.Destination.edit
90-
) { $item in
88+
.navigationDestination(unwrapping: self.$model.destination.edit) { $item in
9189
ItemView(item: $item)
9290
.navigationBarTitle("Edit")
9391
.navigationBarBackButtonHidden(true)
@@ -104,10 +102,7 @@ struct InventoryView: View {
104102
}
105103
}
106104
}
107-
.sheet(
108-
unwrapping: self.$model.destination,
109-
case: /InventoryModel.Destination.add
110-
) { $itemToAdd in
105+
.sheet(unwrapping: self.$model.destination.add) { $itemToAdd in
111106
NavigationStack {
112107
ItemView(item: $itemToAdd)
113108
.navigationTitle("Add")

Examples/Inventory/Item.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,10 @@ struct Item: Equatable, Identifiable {
77
var name: String
88
var status: Status
99

10+
@CasePathable
1011
enum Status: Equatable {
1112
case inStock(quantity: Int)
1213
case outOfStock(isOnBackOrder: Bool)
13-
14-
var isInStock: Bool {
15-
guard case .inStock = self else { return false }
16-
return true
17-
}
1814
}
1915

2016
struct Color: Equatable, Hashable {
@@ -62,26 +58,32 @@ struct ItemView: View {
6258
}
6359
}
6460

65-
Switch(self.$item.status) {
66-
CaseLet(/Item.Status.inStock) { $quantity in
67-
Section(header: Text("In stock")) {
61+
switch self.item.status {
62+
case .inStock:
63+
self.$item.status.inStock.map { $quantity in
64+
Section {
6865
Stepper("Quantity: \(quantity)", value: $quantity)
6966
Button("Mark as sold out") {
7067
withAnimation {
7168
self.item.status = .outOfStock(isOnBackOrder: false)
7269
}
7370
}
71+
} header: {
72+
Text("In stock")
7473
}
7574
.transition(.opacity)
7675
}
77-
CaseLet(/Item.Status.outOfStock) { $isOnBackOrder in
78-
Section(header: Text("Out of stock")) {
76+
case .outOfStock:
77+
self.$item.status.outOfStock.map { $isOnBackOrder in
78+
Section {
7979
Toggle("Is on back order?", isOn: $isOnBackOrder)
8080
Button("Is back in stock!") {
8181
withAnimation {
8282
self.item.status = .inStock(quantity: 1)
8383
}
8484
}
85+
} header: {
86+
Text("Out of stock")
8587
}
8688
.transition(.opacity)
8789
}

Examples/Inventory/ItemRow.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class ItemRowModel: Identifiable {
77
var item: Item
88
var destination: Destination?
99

10+
@CasePathable
1011
enum Destination: Equatable {
1112
case alert(AlertState<AlertAction>)
1213
case duplicate(Item)
@@ -113,17 +114,11 @@ struct ItemRowView: View {
113114
.padding(.leading)
114115
}
115116
.buttonStyle(.plain)
116-
.foregroundColor(self.model.item.status.isInStock ? nil : Color.gray)
117-
.alert(
118-
unwrapping: self.$model.destination,
119-
case: /ItemRowModel.Destination.alert
120-
) {
117+
.foregroundColor(self.model.item.status.is(\.inStock) ? nil : Color.gray)
118+
.alert(self.$model.destination.alert) {
121119
self.model.alertButtonTapped($0)
122120
}
123-
.popover(
124-
unwrapping: self.$model.destination,
125-
case: /ItemRowModel.Destination.duplicate
126-
) { $item in
121+
.popover(unwrapping: self.$model.destination.duplicate) { $item in
127122
NavigationStack {
128123
ItemView(item: $item)
129124
.navigationBarTitle("Duplicate")

Package.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"repositoryURL": "https://github.com/pointfreeco/swift-case-paths",
77
"state": {
88
"branch": null,
9-
"revision": "5da6989aae464f324eef5c5b52bdb7974725ab81",
10-
"version": "1.0.0"
9+
"revision": "40773cbaf8d71ed5357f297b1ba4073f5b24faaa",
10+
"version": "1.1.0"
1111
}
1212
},
1313
{

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ let package = Package(
2222
],
2323
dependencies: [
2424
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
25-
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.0.0"),
25+
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.1.0"),
2626
.package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.0.0"),
2727
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"),
2828
],

0 commit comments

Comments
 (0)