Skip to content

Commit a21cfe9

Browse files
committed
Updated to the Swift 5.2 branch of SwiftDux.
1 parent afa5ce4 commit a21cfe9

18 files changed

+103
-123
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# SwiftDux Todo List App
22

3-
A simple Todo app written for SwiftUI using the [SwiftDux](https://github.com/StevenLambion/SwiftDux) library. There's partial iPad support, but SwiftUI's split view and navigation functionality is not fully complete. It requires iOS 13.1+ to work properly.
3+
A simple Todo app written for SwiftUI using the [SwiftDux](https://github.com/StevenLambion/SwiftDux) library. There's partial iPad support, but SwiftUI's split view and navigation functionality is not fully complete. It's using the Swift 5.2 branch of SwiftDux, so it requires Xcode 11.4 or higher.
44

55
<img src="./screenshots/todoLists-iPad-screenshot.png" width="100%"/>
66

77
# Project Patterns
88

99
## Redux / Elm Architecture using Ducks
10-
SwiftDux helps build applications using an architecture popularized by elm and redux. There's a "Ducks" directory containing the application state and logic. "Ducks" is a common pattern to organize the application state into feature modules. Each feature has its own directory containing its state, actions, and reducer. One module (such as App), may require other modules. This builds the structure of the application's state.
10+
SwiftDux helps build applications using an architecture popularized by elm and redux. There's a "Ducks" directory containing the application state and logic. "Ducks" is a common pattern to organize the application state into feature modules. Each feature has its own directory containing its state, actions, and reducer. A module also defines a protocol suffixed with "Root" to provide the shape it requires in the application state.
1111

1212
## Container vs View
1313
This application uses a popular pattern from the React community know as the container ("smart") vs presentation ("dumb") components. The presentation component is stateless, and represents some kind of UI element. The container, however, is stateful. It tracks the application's state and connects functionality.
@@ -23,7 +23,7 @@ Each view and container has a preview to demonstrate how it works with SwiftDux.
2323

2424
- Multi-window support with UIScene.
2525
- Split view navigation button to expand / collapse the master view.
26-
- This is currently missing. It was also missing in Apple's SwiftUI Essentials video.
26+
- This is currently missing.
2727
- Remove arrows from master view on iPad.
2828
- This is an implementation detail of NavigationLink.
2929
- Autofocus text fields when adding a new item.

Todo Lists.xcodeproj/project.pbxproj

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
/* Begin PBXBuildFile section */
1010
A9307ECA23D91EF0005F5C96 /* SwiftDux in Frameworks */ = {isa = PBXBuildFile; productRef = A9307EC923D91EF0005F5C96 /* SwiftDux */; };
11+
A96EA6AC23F44F7C0024DBAA /* TodoListsRoot.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96EA6AB23F44F7C0024DBAA /* TodoListsRoot.swift */; };
12+
A96EA6AE23F44FB50024DBAA /* TodosRoot.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96EA6AD23F44FB50024DBAA /* TodosRoot.swift */; };
13+
A96EA6B023F452250024DBAA /* configureStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96EA6AF23F452250024DBAA /* configureStore.swift */; };
1114
A976F54F23D3D88E00004B33 /* TodosAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A976F54E23D3D88E00004B33 /* TodosAction.swift */; };
12-
A991C7C523D4E63D00AD7AA3 /* AppAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A991C7C423D4E63D00AD7AA3 /* AppAction.swift */; };
1315
A991C7DF23D6519F00AD7AA3 /* TodoListNameContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A991C7DE23D6519F00AD7AA3 /* TodoListNameContainer.swift */; };
1416
A991C7E123D6568400AD7AA3 /* NewTodoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A991C7E023D6568400AD7AA3 /* NewTodoContainer.swift */; };
1517
AA2F1A81231782D3003B088A /* CheckedToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2F1A80231782D3003B088A /* CheckedToggleStyle.swift */; };
@@ -32,7 +34,6 @@
3234
AA9DA96922AD8E4C00617E46 /* TodoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9DA96822AD8E4C00617E46 /* TodoContainer.swift */; };
3335
AA9DA96D22AD8FC600617E46 /* TodoListBrowserContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9DA96C22AD8FC600617E46 /* TodoListBrowserContainer.swift */; };
3436
AA9DA96F22AD8FED00617E46 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9DA96E22AD8FED00617E46 /* AppState.swift */; };
35-
AA9DA97122AD90B100617E46 /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9DA97022AD90B100617E46 /* AppReducer.swift */; };
3637
AABF238C22D2B45800F332DC /* TodoListsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABF238B22D2B45800F332DC /* TodoListsAction.swift */; };
3738
AAEBBAD122AF159B0014D88C /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = AAEBBAD022AF159B0014D88C /* README.md */; };
3839
/* End PBXBuildFile section */
@@ -52,8 +53,10 @@
5253
/* Begin PBXFileReference section */
5354
A904010A23D8BA63004D65F1 /* SwiftDux.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftDux.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5455
A904010C23D8BA63004D65F1 /* SwiftDuxExtras.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftDuxExtras.framework; sourceTree = BUILT_PRODUCTS_DIR; };
56+
A96EA6AB23F44F7C0024DBAA /* TodoListsRoot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListsRoot.swift; sourceTree = "<group>"; };
57+
A96EA6AD23F44FB50024DBAA /* TodosRoot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodosRoot.swift; sourceTree = "<group>"; };
58+
A96EA6AF23F452250024DBAA /* configureStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = configureStore.swift; sourceTree = "<group>"; };
5559
A976F54E23D3D88E00004B33 /* TodosAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodosAction.swift; sourceTree = "<group>"; };
56-
A991C7C423D4E63D00AD7AA3 /* AppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAction.swift; sourceTree = "<group>"; };
5760
A991C7DE23D6519F00AD7AA3 /* TodoListNameContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodoListNameContainer.swift; sourceTree = "<group>"; };
5861
A991C7E023D6568400AD7AA3 /* NewTodoContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewTodoContainer.swift; sourceTree = "<group>"; };
5962
AA2F1A80231782D3003B088A /* CheckedToggleStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckedToggleStyle.swift; sourceTree = "<group>"; };
@@ -78,7 +81,6 @@
7881
AA9DA96822AD8E4C00617E46 /* TodoContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodoContainer.swift; sourceTree = "<group>"; };
7982
AA9DA96C22AD8FC600617E46 /* TodoListBrowserContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodoListBrowserContainer.swift; sourceTree = "<group>"; };
8083
AA9DA96E22AD8FED00617E46 /* AppState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
81-
AA9DA97022AD90B100617E46 /* AppReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppReducer.swift; sourceTree = "<group>"; };
8284
AABF238B22D2B45800F332DC /* TodoListsAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListsAction.swift; sourceTree = "<group>"; };
8385
AAEBBAD022AF159B0014D88C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
8486
/* End PBXFileReference section */
@@ -95,9 +97,18 @@
9597
/* End PBXFrameworksBuildPhase section */
9698

9799
/* Begin PBXGroup section */
100+
A96EA6B123F453580024DBAA /* App */ = {
101+
isa = PBXGroup;
102+
children = (
103+
AA9DA96E22AD8FED00617E46 /* AppState.swift */,
104+
);
105+
path = App;
106+
sourceTree = "<group>";
107+
};
98108
A976F54723D3D65E00004B33 /* TodoLists */ = {
99109
isa = PBXGroup;
100110
children = (
111+
A96EA6AB23F44F7C0024DBAA /* TodoListsRoot.swift */,
101112
AA9DA96222AD8B5900617E46 /* TodoList.swift */,
102113
AABF238B22D2B45800F332DC /* TodoListsAction.swift */,
103114
AA427B0822C3D097005A4368 /* TodoListsReducer.swift */,
@@ -113,19 +124,10 @@
113124
path = Styles;
114125
sourceTree = "<group>";
115126
};
116-
AA427B0022C31B28005A4368 /* App */ = {
117-
isa = PBXGroup;
118-
children = (
119-
AA9DA96E22AD8FED00617E46 /* AppState.swift */,
120-
AA9DA97022AD90B100617E46 /* AppReducer.swift */,
121-
A991C7C423D4E63D00AD7AA3 /* AppAction.swift */,
122-
);
123-
path = App;
124-
sourceTree = "<group>";
125-
};
126127
AA427B0122C31B35005A4368 /* Todos */ = {
127128
isa = PBXGroup;
128129
children = (
130+
A96EA6AD23F44FB50024DBAA /* TodosRoot.swift */,
129131
AA9DA96422AD8BA100617E46 /* Todo.swift */,
130132
AA427AC922C02E75005A4368 /* TodosReducer.swift */,
131133
A976F54E23D3D88E00004B33 /* TodosAction.swift */,
@@ -200,9 +202,10 @@
200202
AA9DA96022AD8B2A00617E46 /* Ducks */ = {
201203
isa = PBXGroup;
202204
children = (
203-
AA427B0022C31B28005A4368 /* App */,
205+
A96EA6B123F453580024DBAA /* App */,
204206
A976F54723D3D65E00004B33 /* TodoLists */,
205207
AA427B0122C31B35005A4368 /* Todos */,
208+
A96EA6AF23F452250024DBAA /* configureStore.swift */,
206209
);
207210
path = Ducks;
208211
sourceTree = "<group>";
@@ -311,13 +314,14 @@
311314
AA9DA96922AD8E4C00617E46 /* TodoContainer.swift in Sources */,
312315
AA2F1A81231782D3003B088A /* CheckedToggleStyle.swift in Sources */,
313316
A991C7E123D6568400AD7AA3 /* NewTodoContainer.swift in Sources */,
314-
A991C7C523D4E63D00AD7AA3 /* AppAction.swift in Sources */,
315317
AA9DA94722AD8A2100617E46 /* SceneDelegate.swift in Sources */,
316318
AA94FB9822F4D869008D0272 /* TodoListNameField.swift in Sources */,
319+
A96EA6B023F452250024DBAA /* configureStore.swift in Sources */,
320+
A96EA6AE23F44FB50024DBAA /* TodosRoot.swift in Sources */,
317321
AA94FB9522F4D7F8008D0272 /* TodoRow.swift in Sources */,
318322
AA9DA96F22AD8FED00617E46 /* AppState.swift in Sources */,
319323
AA427ACA22C02E75005A4368 /* TodosReducer.swift in Sources */,
320-
AA9DA97122AD90B100617E46 /* AppReducer.swift in Sources */,
324+
A96EA6AC23F44F7C0024DBAA /* TodoListsRoot.swift in Sources */,
321325
AA94FB9C22F4D969008D0272 /* AddButton.swift in Sources */,
322326
AA9DA94922AD8A2100617E46 /* TodoListContainer.swift in Sources */,
323327
AA9DA96322AD8B5900617E46 /* TodoList.swift in Sources */,
@@ -521,7 +525,7 @@
521525
isa = XCRemoteSwiftPackageReference;
522526
repositoryURL = "https://github.com/StevenLambion/SwiftDux.git";
523527
requirement = {
524-
branch = master;
528+
branch = "swift-5.2";
525529
kind = branch;
526530
};
527531
};

Todo/AppDelegate.swift

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,7 @@ import SwiftDuxExtras
1313
@UIApplicationMain
1414
class AppDelegate: UIResponder, UIApplicationDelegate {
1515

16-
var store = Store(
17-
state: AppState(),
18-
reducer: AppReducer(),
19-
middleware: [
20-
// PrintActionMiddleware(),
21-
PersistStateMiddleware(JSONStatePersistor<AppState>()) { state in
22-
state.schemaVersion == AppState.currentSchemaVersion
23-
}
24-
]
25-
)
16+
var store = configureStore()
2617

2718
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
2819
// Override point for customization after application launch.

Todo/Containers/AppContainer.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ struct AppContainer : View {
1515
#if DEBUG
1616
public enum AppContainer_Previews: PreviewProvider {
1717
static var store: Store<AppState> {
18-
Store(
19-
state: AppState(),
20-
reducer: AppReducer()
21-
)
18+
configureStore()
2219
}
2320

2421
public static var previews: some View {

Todo/Containers/NewTodoContainer.swift

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ struct NewTodoContainer : ConnectableView {
77

88
@MappedDispatch() private var dispatch
99

10-
func map(state: AppState, binder: StateBinder) -> Binding<String>? {
10+
struct Props: Equatable {
11+
@ActionBinding var newTodoText: String
12+
}
13+
14+
func map(state: AppState, binder: ActionBinder) -> Props? {
1115
guard let todoList = state.todoLists[id] else { return nil }
12-
return binder.bind(todoList.newTodoText) {
13-
TodoListsAction.setNewTodoText(id: todoList.id, text: $0)
14-
}
16+
return Props(
17+
newTodoText: binder.bind(todoList.newTodoText) {
18+
TodoListsAction.setNewTodoText(id: todoList.id, text: $0)
19+
}
20+
)
1521
}
1622

1723
func body(props: Props) -> some View {
18-
NewTodoRow(text: props) {
24+
NewTodoRow(text: props.$newTodoText) {
1925
self.dispatch(TodoListsAction.addTodo(id: self.id, text: $0))
2026
}.padding()
2127
}
@@ -25,10 +31,7 @@ struct NewTodoContainer : ConnectableView {
2531
#if DEBUG
2632
public enum NewTodoContainer_Previews: PreviewProvider {
2733
static var store: Store<AppState> {
28-
Store(
29-
state: AppState(),
30-
reducer: AppReducer()
31-
)
34+
configureStore()
3235
}
3336

3437
public static var previews: some View {

Todo/Containers/TodoContainer.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ struct TodoContainer : ConnectableView {
77
var todoId: String
88

99
struct Props: Equatable {
10-
@Binding var text: String
11-
@Binding var completed: Bool
10+
@ActionBinding var text: String
11+
@ActionBinding var completed: Bool
1212
}
1313

14-
func map(state: AppState, binder: StateBinder) -> Props? {
14+
func map(state: AppState, binder: ActionBinder) -> Props? {
1515
guard let todo = state.todos[todoId] else { return nil }
1616
return Props(
1717
text: binder.bind(todo.text) { TodosAction.setText(id: todo.id, text: $0) },
@@ -34,10 +34,7 @@ struct TodoContainer : ConnectableView {
3434
#if DEBUG
3535
public enum TodoListDetailsRowContainer_Previews: PreviewProvider {
3636
static var store: Store<AppState> {
37-
Store(
38-
state: AppState(),
39-
reducer: AppReducer()
40-
)
37+
configureStore()
4138
}
4239

4340
public static var previews: some View {

Todo/Containers/TodoListBrowserContainer.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,20 @@ struct TodoListBrowserContainer : ConnectableView {
6262
}
6363

6464
func selectTodoList(id: String) {
65-
dispatch(AppAction.selectTodoList(id: id))
65+
dispatch(TodoListsAction.selectTodoList(id: id))
6666
}
6767

6868
func selectDefaultTodoList(props: Props) {
6969
guard props.selectedTodoListId == nil && sizeClass == .regular else { return }
70-
dispatch(AppAction.selectTodoList(id: props.todoLists.first?.id))
70+
dispatch(TodoListsAction.selectTodoList(id: props.todoLists.first?.id))
7171
}
7272

7373
}
7474

7575
#if DEBUG
7676
public enum TodoListBrowserContainer_Previews: PreviewProvider {
7777
static var store: Store<AppState> {
78-
Store(
79-
state: AppState(),
80-
reducer: AppReducer()
81-
)
78+
configureStore()
8279
}
8380

8481
public static var previews: some View {

Todo/Containers/TodoListContainer.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,15 @@ struct TodoListContainer : ConnectableView {
4545

4646
func deselectTodoList() {
4747
if self.sizeClass == .compact {
48-
self.dispatch(AppAction.selectTodoList(id: nil))
48+
self.dispatch(TodoListsAction.selectTodoList(id: nil))
4949
}
5050
}
5151
}
5252

5353
#if DEBUG
5454
public enum TodoListContainer_Previews: PreviewProvider {
5555
static var store: Store<AppState> {
56-
Store(
57-
state: AppState(),
58-
reducer: AppReducer()
59-
)
56+
configureStore()
6057
}
6158

6259
public static var previews: some View {

Todo/Containers/TodoListNameContainer.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,22 @@ import SwiftDux
55
struct TodoListNameContainer : ConnectableView {
66
var id: String
77

8-
func map(state: AppState, binder: StateBinder) -> Binding<String>? {
8+
func map(state: AppState, binder: ActionBinder) -> ActionBinding<String>? {
99
guard let todoList = state.todoLists[id] else { return nil }
1010
return binder.bind(todoList.name) {
1111
TodoListsAction.setName(id: todoList.id, name: $0)
1212
}
1313
}
1414

15-
func body(props: Binding<String>) -> some View {
16-
TodoListNameField(name: props)
15+
func body(props: ActionBinding<String>) -> some View {
16+
TodoListNameField(name: props.projectedValue)
1717
}
1818
}
1919

2020
#if DEBUG
2121
public enum TodoListNameContainer_Previews: PreviewProvider {
2222
static var store: Store<AppState> {
23-
Store(
24-
state: AppState(),
25-
reducer: AppReducer()
26-
)
23+
configureStore()
2724
}
2825

2926
public static var previews: some View {

Todo/Ducks/App/AppAction.swift

Lines changed: 0 additions & 6 deletions
This file was deleted.

Todo/Ducks/App/AppReducer.swift

Lines changed: 0 additions & 25 deletions
This file was deleted.

Todo/Ducks/App/AppState.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ fileprivate let defaultTodos = [
2929
return todos
3030
}) { (_, new) in new }
3131

32-
struct AppState : StateType {
32+
struct AppState: StateType, TodoListsRoot {
3333
static let currentSchemaVersion = 2
3434

3535
var schemaVersion: Int = currentSchemaVersion

0 commit comments

Comments
 (0)