diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/UserInterfaceState.xcuserstate
new file mode 100644
index 0000000..5563385
Binary files /dev/null and b/.swiftpm/xcode/package.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ComposableArchitecture.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ComposableArchitecture.xcscheme
new file mode 100644
index 0000000..c802e05
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/ComposableArchitecture.xcscheme
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/reactiveswift-composable-architecture-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/reactiveswift-composable-architecture-Package.xcscheme
new file mode 100644
index 0000000..6187657
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/reactiveswift-composable-architecture-Package.xcscheme
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-composable-architecture-benchmark.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-composable-architecture-benchmark.xcscheme
new file mode 100644
index 0000000..1e2feec
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/swift-composable-architecture-benchmark.xcscheme
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 0000000..8fff740
--- /dev/null
+++ b/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,87 @@
+
+
+
+
+ SchemeUserState
+
+ ComposableArchitecture.xcscheme_^#shared#^_
+
+ orderHint
+ 4
+
+ CustomDump (Playground) 1.xcscheme
+
+ isShown
+
+ orderHint
+ 18
+
+ CustomDump (Playground) 2.xcscheme
+
+ isShown
+
+ orderHint
+ 19
+
+ CustomDump (Playground).xcscheme
+
+ isShown
+
+ orderHint
+ 17
+
+ ReactiveSwift (Playground) 1.xcscheme
+
+ isShown
+
+ orderHint
+ 24
+
+ ReactiveSwift (Playground) 2.xcscheme
+
+ isShown
+
+ orderHint
+ 25
+
+ ReactiveSwift (Playground).xcscheme
+
+ isShown
+
+ orderHint
+ 23
+
+ ReactiveSwift-UIExamples (Playground) 1.xcscheme
+
+ isShown
+
+ orderHint
+ 21
+
+ ReactiveSwift-UIExamples (Playground) 2.xcscheme
+
+ isShown
+
+ orderHint
+ 22
+
+ ReactiveSwift-UIExamples (Playground).xcscheme
+
+ isShown
+
+ orderHint
+ 20
+
+ reactiveswift-composable-architecture-Package.xcscheme_^#shared#^_
+
+ orderHint
+ 9
+
+ swift-composable-architecture-benchmark.xcscheme_^#shared#^_
+
+ orderHint
+ 13
+
+
+
+
diff --git a/ComposableArchitecture.xcworkspace/contents.xcworkspacedata b/ComposableArchitecture.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1eef849
--- /dev/null
+++ b/ComposableArchitecture.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/ComposableArchitecture.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ComposableArchitecture.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/ComposableArchitecture.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000..cf43ed3
--- /dev/null
+++ b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,124 @@
+{
+ "object": {
+ "pins": [
+ {
+ "package": "Alamofire",
+ "repositoryURL": "https://github.com/Alamofire/Alamofire.git",
+ "state": {
+ "branch": null,
+ "revision": "d120af1e8638c7da36c8481fd61a66c0c08dc4fc",
+ "version": "5.4.4"
+ }
+ },
+ {
+ "package": "AnyRequest",
+ "repositoryURL": "https://github.com/FullStack-Swift/AnyRequest",
+ "state": {
+ "branch": "main",
+ "revision": "42a2e613f0d3f3e7390d9817262c28533804e97d",
+ "version": null
+ }
+ },
+ {
+ "package": "combine-schedulers",
+ "repositoryURL": "https://github.com/pointfreeco/combine-schedulers",
+ "state": {
+ "branch": null,
+ "revision": "4cf088c29a20f52be0f2ca54992b492c54e0076b",
+ "version": "0.5.3"
+ }
+ },
+ {
+ "package": "Json",
+ "repositoryURL": "https://github.com/FullStack-Swift/Json",
+ "state": {
+ "branch": "main",
+ "revision": "1b6b4508691d0341fc3a5ddd7a75845734bdf41d",
+ "version": null
+ }
+ },
+ {
+ "package": "ReactiveCocoa",
+ "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveCocoa.git",
+ "state": {
+ "branch": null,
+ "revision": "579364393ad587352bcce133b7b3f73392cb585b",
+ "version": "11.2.2"
+ }
+ },
+ {
+ "package": "swift-argument-parser",
+ "repositoryURL": "https://github.com/apple/swift-argument-parser",
+ "state": {
+ "branch": null,
+ "revision": "6b2aa2748a7881eebb9f84fb10c01293e15b52ca",
+ "version": "0.5.0"
+ }
+ },
+ {
+ "package": "Benchmark",
+ "repositoryURL": "https://github.com/google/swift-benchmark",
+ "state": {
+ "branch": null,
+ "revision": "a0564bf88df5f94eec81348a2f089494c6b28d80",
+ "version": "0.1.1"
+ }
+ },
+ {
+ "package": "swift-case-paths",
+ "repositoryURL": "https://github.com/pointfreeco/swift-case-paths",
+ "state": {
+ "branch": null,
+ "revision": "d226d167bd4a68b51e352af5655c92bce8ee0463",
+ "version": "0.7.0"
+ }
+ },
+ {
+ "package": "swift-collections",
+ "repositoryURL": "https://github.com/apple/swift-collections",
+ "state": {
+ "branch": null,
+ "revision": "2d33a0ea89c961dcb2b3da2157963d9c0370347e",
+ "version": "1.0.1"
+ }
+ },
+ {
+ "package": "swift-custom-dump",
+ "repositoryURL": "https://github.com/pointfreeco/swift-custom-dump",
+ "state": {
+ "branch": null,
+ "revision": "c2dd2c64b753dda592f5619303e02f741cd3e862",
+ "version": "0.2.0"
+ }
+ },
+ {
+ "package": "swift-identified-collections",
+ "repositoryURL": "https://github.com/pointfreeco/swift-identified-collections",
+ "state": {
+ "branch": null,
+ "revision": "c8e6a40209650ab619853cd4ce89a0aa51792754",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "package": "ConvertSwift",
+ "repositoryURL": "https://github.com/FullStack-Swift/SwiftConvert",
+ "state": {
+ "branch": "main",
+ "revision": "46c1624cff8a1df05edb503a5b9940bf27beabce",
+ "version": null
+ }
+ },
+ {
+ "package": "xctest-dynamic-overlay",
+ "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay",
+ "state": {
+ "branch": null,
+ "revision": "50a70a9d3583fe228ce672e8923010c8df2deddd",
+ "version": "0.2.1"
+ }
+ }
+ ]
+ },
+ "version": 1
+}
diff --git a/ComposableArchitecture.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/UserInterfaceState.xcuserstate b/ComposableArchitecture.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/UserInterfaceState.xcuserstate
new file mode 100644
index 0000000..793131d
Binary files /dev/null and b/ComposableArchitecture.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/ComposableArchitecture.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/ComposableArchitecture.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
new file mode 100644
index 0000000..9e6f25a
--- /dev/null
+++ b/ComposableArchitecture.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -0,0 +1,6 @@
+
+
+
diff --git a/ComposableArchitecture.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/xcschemes/xcschememanagement.plist b/ComposableArchitecture.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 0000000..a1c854e
--- /dev/null
+++ b/ComposableArchitecture.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,261 @@
+
+
+
+
+ SchemeUserState
+
+ CustomDump (Playground) 1.xcscheme
+
+ isShown
+
+ orderHint
+ 22
+
+ CustomDump (Playground) 2.xcscheme
+
+ isShown
+
+ orderHint
+ 23
+
+ CustomDump (Playground).xcscheme
+
+ isShown
+
+ orderHint
+ 21
+
+ ReactiveCocoa-iOS (Playground) 1.xcscheme
+
+ isShown
+
+ orderHint
+ 37
+
+ ReactiveCocoa-iOS (Playground) 2.xcscheme
+
+ isShown
+
+ orderHint
+ 38
+
+ ReactiveCocoa-iOS (Playground).xcscheme
+
+ isShown
+
+ orderHint
+ 36
+
+ ReactiveCocoa-macOS (Playground) 1.xcscheme
+
+ isShown
+
+ orderHint
+ 34
+
+ ReactiveCocoa-macOS (Playground) 2.xcscheme
+
+ isShown
+
+ orderHint
+ 35
+
+ ReactiveCocoa-macOS (Playground).xcscheme
+
+ isShown
+
+ orderHint
+ 33
+
+ ReactiveCocoa-tvOS (Playground) 1.xcscheme
+
+ isShown
+
+ orderHint
+ 31
+
+ ReactiveCocoa-tvOS (Playground) 2.xcscheme
+
+ isShown
+
+ orderHint
+ 32
+
+ ReactiveCocoa-tvOS (Playground).xcscheme
+
+ isShown
+
+ orderHint
+ 30
+
+ ReactiveSwift (Playground) 1.xcscheme
+
+ isShown
+
+ orderHint
+ 25
+
+ ReactiveSwift (Playground) 10.xcscheme
+
+ isShown
+
+ orderHint
+ 52
+
+ ReactiveSwift (Playground) 11.xcscheme
+
+ isShown
+
+ orderHint
+ 53
+
+ ReactiveSwift (Playground) 2.xcscheme
+
+ isShown
+
+ orderHint
+ 26
+
+ ReactiveSwift (Playground) 3.xcscheme
+
+ isShown
+
+ orderHint
+ 39
+
+ ReactiveSwift (Playground) 4.xcscheme
+
+ isShown
+
+ orderHint
+ 40
+
+ ReactiveSwift (Playground) 5.xcscheme
+
+ isShown
+
+ orderHint
+ 41
+
+ ReactiveSwift (Playground) 6.xcscheme
+
+ isShown
+
+ orderHint
+ 45
+
+ ReactiveSwift (Playground) 7.xcscheme
+
+ isShown
+
+ orderHint
+ 46
+
+ ReactiveSwift (Playground) 8.xcscheme
+
+ isShown
+
+ orderHint
+ 47
+
+ ReactiveSwift (Playground) 9.xcscheme
+
+ isShown
+
+ orderHint
+ 51
+
+ ReactiveSwift (Playground).xcscheme
+
+ isShown
+
+ orderHint
+ 24
+
+ ReactiveSwift-UIExamples (Playground) 1.xcscheme
+
+ isShown
+
+ orderHint
+ 28
+
+ ReactiveSwift-UIExamples (Playground) 10.xcscheme
+
+ isShown
+
+ orderHint
+ 55
+
+ ReactiveSwift-UIExamples (Playground) 11.xcscheme
+
+ isShown
+
+ orderHint
+ 56
+
+ ReactiveSwift-UIExamples (Playground) 2.xcscheme
+
+ isShown
+
+ orderHint
+ 29
+
+ ReactiveSwift-UIExamples (Playground) 3.xcscheme
+
+ isShown
+
+ orderHint
+ 42
+
+ ReactiveSwift-UIExamples (Playground) 4.xcscheme
+
+ isShown
+
+ orderHint
+ 43
+
+ ReactiveSwift-UIExamples (Playground) 5.xcscheme
+
+ isShown
+
+ orderHint
+ 44
+
+ ReactiveSwift-UIExamples (Playground) 6.xcscheme
+
+ isShown
+
+ orderHint
+ 48
+
+ ReactiveSwift-UIExamples (Playground) 7.xcscheme
+
+ isShown
+
+ orderHint
+ 49
+
+ ReactiveSwift-UIExamples (Playground) 8.xcscheme
+
+ isShown
+
+ orderHint
+ 50
+
+ ReactiveSwift-UIExamples (Playground) 9.xcscheme
+
+ isShown
+
+ orderHint
+ 54
+
+ ReactiveSwift-UIExamples (Playground).xcscheme
+
+ isShown
+
+ orderHint
+ 27
+
+
+
+
diff --git a/Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj b/Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..19e818e
--- /dev/null
+++ b/Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj
@@ -0,0 +1,772 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 55;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 87030D732710EF07009A5353 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 87030D722710EF07009A5353 /* ComposableArchitecture */; };
+ 87030D772710EFF5009A5353 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 87030D762710EFF5009A5353 /* ComposableArchitecture */; };
+ 873FE73427062BB80054CF07 /* RootAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE72F27062BB80054CF07 /* RootAction.swift */; };
+ 873FE73527062BB80054CF07 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE73027062BB80054CF07 /* RootViewController.swift */; };
+ 873FE73627062BB80054CF07 /* RootEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE73127062BB80054CF07 /* RootEnvironment.swift */; };
+ 873FE73727062BB80054CF07 /* RootReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE73227062BB80054CF07 /* RootReducer.swift */; };
+ 873FE73827062BB80054CF07 /* RootState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE73327062BB80054CF07 /* RootState.swift */; };
+ 873FE73B27062C7A0054CF07 /* ActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE73A27062C7A0054CF07 /* ActivityIndicatorViewController.swift */; };
+ 873FE73D27062C8B0054CF07 /* IfLetStoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE73C27062C8B0054CF07 /* IfLetStoreController.swift */; };
+ 873FE74327062D850054CF07 /* AuthAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE73E27062D850054CF07 /* AuthAction.swift */; };
+ 873FE74427062D850054CF07 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE73F27062D850054CF07 /* AuthViewController.swift */; };
+ 873FE74527062D850054CF07 /* AuthEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE74027062D850054CF07 /* AuthEnvironment.swift */; };
+ 873FE74627062D850054CF07 /* AuthReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE74127062D850054CF07 /* AuthReducer.swift */; };
+ 873FE74727062D850054CF07 /* AuthState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE74227062D850054CF07 /* AuthState.swift */; };
+ 873FE74E27062DF80054CF07 /* MainAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE74927062DF70054CF07 /* MainAction.swift */; };
+ 873FE74F27062DF80054CF07 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE74A27062DF70054CF07 /* MainViewController.swift */; };
+ 873FE75027062DF80054CF07 /* MainEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE74B27062DF70054CF07 /* MainEnvironment.swift */; };
+ 873FE75127062DF80054CF07 /* MainReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE74C27062DF70054CF07 /* MainReducer.swift */; };
+ 873FE75227062DF80054CF07 /* MainState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE74D27062DF70054CF07 /* MainState.swift */; };
+ 873FE759270632E00054CF07 /* CountersTableAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE754270632E00054CF07 /* CountersTableAction.swift */; };
+ 873FE75A270632E00054CF07 /* CountersTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE755270632E00054CF07 /* CountersTableViewController.swift */; };
+ 873FE75B270632E00054CF07 /* CountersTableEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE756270632E00054CF07 /* CountersTableEnvironment.swift */; };
+ 873FE75C270632E00054CF07 /* CountersTableReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE757270632E00054CF07 /* CountersTableReducer.swift */; };
+ 873FE75D270632E00054CF07 /* CountersTableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE758270632E00054CF07 /* CountersTableState.swift */; };
+ 873FE765270633310054CF07 /* LazyNavigationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE760270633310054CF07 /* LazyNavigationAction.swift */; };
+ 873FE766270633310054CF07 /* LazyNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE761270633310054CF07 /* LazyNavigationViewController.swift */; };
+ 873FE767270633310054CF07 /* LazyNavigationEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE762270633310054CF07 /* LazyNavigationEnvironment.swift */; };
+ 873FE768270633310054CF07 /* LazyNavigationReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE763270633310054CF07 /* LazyNavigationReducer.swift */; };
+ 873FE769270633310054CF07 /* LazyNavigationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE764270633310054CF07 /* LazyNavigationState.swift */; };
+ 873FE76F2706334C0054CF07 /* EagerNavigationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE76A2706334C0054CF07 /* EagerNavigationAction.swift */; };
+ 873FE7702706334C0054CF07 /* EagerNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE76B2706334C0054CF07 /* EagerNavigationViewController.swift */; };
+ 873FE7712706334C0054CF07 /* EagerNavigationEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE76C2706334C0054CF07 /* EagerNavigationEnvironment.swift */; };
+ 873FE7722706334C0054CF07 /* EagerNavigationReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE76D2706334C0054CF07 /* EagerNavigationReducer.swift */; };
+ 873FE7732706334C0054CF07 /* EagerNavigationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873FE76E2706334C0054CF07 /* EagerNavigationState.swift */; };
+ 878AF92D2710F0D30066E71C /* ReactiveCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 878AF92C2710F0D30066E71C /* ReactiveCocoa */; };
+ 87BA5F3F270622DB00984D7B /* SwiftUICaseStudiesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BA5F3E270622DB00984D7B /* SwiftUICaseStudiesApp.swift */; };
+ 87BA5F41270622DB00984D7B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BA5F40270622DB00984D7B /* ContentView.swift */; };
+ 87BA5F43270622E500984D7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 87BA5F42270622E500984D7B /* Assets.xcassets */; };
+ 87BA5F46270622E500984D7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 87BA5F45270622E500984D7B /* Preview Assets.xcassets */; };
+ 87BA5F512706231100984D7B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BA5F502706231100984D7B /* AppDelegate.swift */; };
+ 87BA5F532706231100984D7B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BA5F522706231100984D7B /* SceneDelegate.swift */; };
+ 87BA5F582706231100984D7B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 87BA5F562706231100984D7B /* Main.storyboard */; };
+ 87BA5F5A2706231200984D7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 87BA5F592706231200984D7B /* Assets.xcassets */; };
+ 87BA5F5D2706231200984D7B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 87BA5F5B2706231200984D7B /* LaunchScreen.storyboard */; };
+ 87BA5F682706240E00984D7B /* CounterAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BA5F632706240E00984D7B /* CounterAction.swift */; };
+ 87BA5F692706240E00984D7B /* CounterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BA5F642706240E00984D7B /* CounterViewController.swift */; };
+ 87BA5F6A2706240E00984D7B /* CounterEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BA5F652706240E00984D7B /* CounterEnvironment.swift */; };
+ 87BA5F6B2706240E00984D7B /* CounterReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BA5F662706240E00984D7B /* CounterReducer.swift */; };
+ 87BA5F6C2706240E00984D7B /* CounterState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BA5F672706240E00984D7B /* CounterState.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 873FE72F27062BB80054CF07 /* RootAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootAction.swift; sourceTree = ""; };
+ 873FE73027062BB80054CF07 /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = ""; };
+ 873FE73127062BB80054CF07 /* RootEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootEnvironment.swift; sourceTree = ""; };
+ 873FE73227062BB80054CF07 /* RootReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootReducer.swift; sourceTree = ""; };
+ 873FE73327062BB80054CF07 /* RootState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootState.swift; sourceTree = ""; };
+ 873FE73A27062C7A0054CF07 /* ActivityIndicatorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorViewController.swift; sourceTree = ""; };
+ 873FE73C27062C8B0054CF07 /* IfLetStoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IfLetStoreController.swift; sourceTree = ""; };
+ 873FE73E27062D850054CF07 /* AuthAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthAction.swift; sourceTree = ""; };
+ 873FE73F27062D850054CF07 /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; };
+ 873FE74027062D850054CF07 /* AuthEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthEnvironment.swift; sourceTree = ""; };
+ 873FE74127062D850054CF07 /* AuthReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthReducer.swift; sourceTree = ""; };
+ 873FE74227062D850054CF07 /* AuthState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthState.swift; sourceTree = ""; };
+ 873FE74927062DF70054CF07 /* MainAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainAction.swift; sourceTree = ""; };
+ 873FE74A27062DF70054CF07 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; };
+ 873FE74B27062DF70054CF07 /* MainEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainEnvironment.swift; sourceTree = ""; };
+ 873FE74C27062DF70054CF07 /* MainReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainReducer.swift; sourceTree = ""; };
+ 873FE74D27062DF70054CF07 /* MainState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainState.swift; sourceTree = ""; };
+ 873FE754270632E00054CF07 /* CountersTableAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountersTableAction.swift; sourceTree = ""; };
+ 873FE755270632E00054CF07 /* CountersTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountersTableViewController.swift; sourceTree = ""; };
+ 873FE756270632E00054CF07 /* CountersTableEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountersTableEnvironment.swift; sourceTree = ""; };
+ 873FE757270632E00054CF07 /* CountersTableReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountersTableReducer.swift; sourceTree = ""; };
+ 873FE758270632E00054CF07 /* CountersTableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountersTableState.swift; sourceTree = ""; };
+ 873FE760270633310054CF07 /* LazyNavigationAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyNavigationAction.swift; sourceTree = ""; };
+ 873FE761270633310054CF07 /* LazyNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyNavigationViewController.swift; sourceTree = ""; };
+ 873FE762270633310054CF07 /* LazyNavigationEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyNavigationEnvironment.swift; sourceTree = ""; };
+ 873FE763270633310054CF07 /* LazyNavigationReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyNavigationReducer.swift; sourceTree = ""; };
+ 873FE764270633310054CF07 /* LazyNavigationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyNavigationState.swift; sourceTree = ""; };
+ 873FE76A2706334C0054CF07 /* EagerNavigationAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EagerNavigationAction.swift; sourceTree = ""; };
+ 873FE76B2706334C0054CF07 /* EagerNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EagerNavigationViewController.swift; sourceTree = ""; };
+ 873FE76C2706334C0054CF07 /* EagerNavigationEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EagerNavigationEnvironment.swift; sourceTree = ""; };
+ 873FE76D2706334C0054CF07 /* EagerNavigationReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EagerNavigationReducer.swift; sourceTree = ""; };
+ 873FE76E2706334C0054CF07 /* EagerNavigationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EagerNavigationState.swift; sourceTree = ""; };
+ 87BA5F3C270622DB00984D7B /* SwiftUICaseStudies.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUICaseStudies.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 87BA5F3E270622DB00984D7B /* SwiftUICaseStudiesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUICaseStudiesApp.swift; sourceTree = ""; };
+ 87BA5F40270622DB00984D7B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ 87BA5F42270622E500984D7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 87BA5F45270622E500984D7B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
+ 87BA5F4E2706231100984D7B /* UIKitCaseStudies.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIKitCaseStudies.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 87BA5F502706231100984D7B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 87BA5F522706231100984D7B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
+ 87BA5F572706231100984D7B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 87BA5F592706231200984D7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 87BA5F5C2706231200984D7B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 87BA5F5E2706231200984D7B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 87BA5F632706240E00984D7B /* CounterAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterAction.swift; sourceTree = ""; };
+ 87BA5F642706240E00984D7B /* CounterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterViewController.swift; sourceTree = ""; };
+ 87BA5F652706240E00984D7B /* CounterEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterEnvironment.swift; sourceTree = ""; };
+ 87BA5F662706240E00984D7B /* CounterReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterReducer.swift; sourceTree = ""; };
+ 87BA5F672706240E00984D7B /* CounterState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterState.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 87BA5F39270622DB00984D7B /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 87030D732710EF07009A5353 /* ComposableArchitecture in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 87BA5F4B2706231100984D7B /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 87030D772710EFF5009A5353 /* ComposableArchitecture in Frameworks */,
+ 878AF92D2710F0D30066E71C /* ReactiveCocoa in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 872458582706058700A88871 = {
+ isa = PBXGroup;
+ children = (
+ 87BA5F3D270622DB00984D7B /* SwiftUICaseStudies */,
+ 87BA5F4F2706231100984D7B /* UIKitCaseStudies */,
+ 872458662706058800A88871 /* Products */,
+ 87DAAA6027060D190048D12A /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 872458662706058800A88871 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 87BA5F3C270622DB00984D7B /* SwiftUICaseStudies.app */,
+ 87BA5F4E2706231100984D7B /* UIKitCaseStudies.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 873FE72D27062B9A0054CF07 /* RootScreen */ = {
+ isa = PBXGroup;
+ children = (
+ 873FE73027062BB80054CF07 /* RootViewController.swift */,
+ 873FE73327062BB80054CF07 /* RootState.swift */,
+ 873FE72F27062BB80054CF07 /* RootAction.swift */,
+ 873FE73127062BB80054CF07 /* RootEnvironment.swift */,
+ 873FE73227062BB80054CF07 /* RootReducer.swift */,
+ );
+ path = RootScreen;
+ sourceTree = "";
+ };
+ 873FE72E27062BA20054CF07 /* AuthScreen */ = {
+ isa = PBXGroup;
+ children = (
+ 873FE73F27062D850054CF07 /* AuthViewController.swift */,
+ 873FE74227062D850054CF07 /* AuthState.swift */,
+ 873FE73E27062D850054CF07 /* AuthAction.swift */,
+ 873FE74027062D850054CF07 /* AuthEnvironment.swift */,
+ 873FE74127062D850054CF07 /* AuthReducer.swift */,
+ );
+ path = AuthScreen;
+ sourceTree = "";
+ };
+ 873FE73927062C690054CF07 /* Internal */ = {
+ isa = PBXGroup;
+ children = (
+ 873FE73A27062C7A0054CF07 /* ActivityIndicatorViewController.swift */,
+ 873FE73C27062C8B0054CF07 /* IfLetStoreController.swift */,
+ );
+ path = Internal;
+ sourceTree = "";
+ };
+ 873FE74827062DE80054CF07 /* MainScreen */ = {
+ isa = PBXGroup;
+ children = (
+ 873FE74A27062DF70054CF07 /* MainViewController.swift */,
+ 873FE74D27062DF70054CF07 /* MainState.swift */,
+ 873FE74927062DF70054CF07 /* MainAction.swift */,
+ 873FE74B27062DF70054CF07 /* MainEnvironment.swift */,
+ 873FE74C27062DF70054CF07 /* MainReducer.swift */,
+ );
+ path = MainScreen;
+ sourceTree = "";
+ };
+ 873FE753270632BC0054CF07 /* CountersListScreen */ = {
+ isa = PBXGroup;
+ children = (
+ 873FE755270632E00054CF07 /* CountersTableViewController.swift */,
+ 873FE758270632E00054CF07 /* CountersTableState.swift */,
+ 873FE754270632E00054CF07 /* CountersTableAction.swift */,
+ 873FE756270632E00054CF07 /* CountersTableEnvironment.swift */,
+ 873FE757270632E00054CF07 /* CountersTableReducer.swift */,
+ );
+ path = CountersListScreen;
+ sourceTree = "";
+ };
+ 873FE75E270632ED0054CF07 /* EagerNavigation */ = {
+ isa = PBXGroup;
+ children = (
+ 873FE76B2706334C0054CF07 /* EagerNavigationViewController.swift */,
+ 873FE76E2706334C0054CF07 /* EagerNavigationState.swift */,
+ 873FE76A2706334C0054CF07 /* EagerNavigationAction.swift */,
+ 873FE76C2706334C0054CF07 /* EagerNavigationEnvironment.swift */,
+ 873FE76D2706334C0054CF07 /* EagerNavigationReducer.swift */,
+ );
+ path = EagerNavigation;
+ sourceTree = "";
+ };
+ 873FE75F270633070054CF07 /* LazyNavigation */ = {
+ isa = PBXGroup;
+ children = (
+ 873FE761270633310054CF07 /* LazyNavigationViewController.swift */,
+ 873FE764270633310054CF07 /* LazyNavigationState.swift */,
+ 873FE760270633310054CF07 /* LazyNavigationAction.swift */,
+ 873FE762270633310054CF07 /* LazyNavigationEnvironment.swift */,
+ 873FE763270633310054CF07 /* LazyNavigationReducer.swift */,
+ );
+ path = LazyNavigation;
+ sourceTree = "";
+ };
+ 87BA5F3D270622DB00984D7B /* SwiftUICaseStudies */ = {
+ isa = PBXGroup;
+ children = (
+ 87BA5F3E270622DB00984D7B /* SwiftUICaseStudiesApp.swift */,
+ 87BA5F40270622DB00984D7B /* ContentView.swift */,
+ 87BA5F42270622E500984D7B /* Assets.xcassets */,
+ 87BA5F44270622E500984D7B /* Preview Content */,
+ );
+ path = SwiftUICaseStudies;
+ sourceTree = "";
+ };
+ 87BA5F44270622E500984D7B /* Preview Content */ = {
+ isa = PBXGroup;
+ children = (
+ 87BA5F45270622E500984D7B /* Preview Assets.xcassets */,
+ );
+ path = "Preview Content";
+ sourceTree = "";
+ };
+ 87BA5F4F2706231100984D7B /* UIKitCaseStudies */ = {
+ isa = PBXGroup;
+ children = (
+ 873FE73927062C690054CF07 /* Internal */,
+ 873FE72D27062B9A0054CF07 /* RootScreen */,
+ 873FE74827062DE80054CF07 /* MainScreen */,
+ 873FE72E27062BA20054CF07 /* AuthScreen */,
+ 87BA5F62270623FC00984D7B /* CounterScreen */,
+ 873FE753270632BC0054CF07 /* CountersListScreen */,
+ 873FE75E270632ED0054CF07 /* EagerNavigation */,
+ 873FE75F270633070054CF07 /* LazyNavigation */,
+ 87BA5F502706231100984D7B /* AppDelegate.swift */,
+ 87BA5F522706231100984D7B /* SceneDelegate.swift */,
+ 87BA5F562706231100984D7B /* Main.storyboard */,
+ 87BA5F592706231200984D7B /* Assets.xcassets */,
+ 87BA5F5B2706231200984D7B /* LaunchScreen.storyboard */,
+ 87BA5F5E2706231200984D7B /* Info.plist */,
+ );
+ path = UIKitCaseStudies;
+ sourceTree = "";
+ };
+ 87BA5F62270623FC00984D7B /* CounterScreen */ = {
+ isa = PBXGroup;
+ children = (
+ 87BA5F642706240E00984D7B /* CounterViewController.swift */,
+ 87BA5F672706240E00984D7B /* CounterState.swift */,
+ 87BA5F632706240E00984D7B /* CounterAction.swift */,
+ 87BA5F652706240E00984D7B /* CounterEnvironment.swift */,
+ 87BA5F662706240E00984D7B /* CounterReducer.swift */,
+ );
+ path = CounterScreen;
+ sourceTree = "";
+ };
+ 87DAAA6027060D190048D12A /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 87BA5F3B270622DB00984D7B /* SwiftUICaseStudies */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 87BA5F49270622E500984D7B /* Build configuration list for PBXNativeTarget "SwiftUICaseStudies" */;
+ buildPhases = (
+ 87BA5F38270622DB00984D7B /* Sources */,
+ 87BA5F39270622DB00984D7B /* Frameworks */,
+ 87BA5F3A270622DB00984D7B /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = SwiftUICaseStudies;
+ packageProductDependencies = (
+ 87030D722710EF07009A5353 /* ComposableArchitecture */,
+ );
+ productName = SwiftUICaseStudies;
+ productReference = 87BA5F3C270622DB00984D7B /* SwiftUICaseStudies.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 87BA5F4D2706231100984D7B /* UIKitCaseStudies */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 87BA5F5F2706231200984D7B /* Build configuration list for PBXNativeTarget "UIKitCaseStudies" */;
+ buildPhases = (
+ 87BA5F4A2706231100984D7B /* Sources */,
+ 87BA5F4B2706231100984D7B /* Frameworks */,
+ 87BA5F4C2706231100984D7B /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = UIKitCaseStudies;
+ packageProductDependencies = (
+ 87030D762710EFF5009A5353 /* ComposableArchitecture */,
+ 878AF92C2710F0D30066E71C /* ReactiveCocoa */,
+ );
+ productName = UIKitCaseStudies;
+ productReference = 87BA5F4E2706231100984D7B /* UIKitCaseStudies.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 872458592706058700A88871 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1300;
+ LastUpgradeCheck = 1300;
+ TargetAttributes = {
+ 87BA5F3B270622DB00984D7B = {
+ CreatedOnToolsVersion = 13.0;
+ };
+ 87BA5F4D2706231100984D7B = {
+ CreatedOnToolsVersion = 13.0;
+ };
+ };
+ };
+ buildConfigurationList = 8724585C2706058700A88871 /* Build configuration list for PBXProject "CaseStudies" */;
+ compatibilityVersion = "Xcode 13.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 872458582706058700A88871;
+ packageReferences = (
+ 87030D6F2710EED7009A5353 /* XCRemoteSwiftPackageReference "ReactiveCocoa" */,
+ );
+ productRefGroup = 872458662706058800A88871 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 87BA5F3B270622DB00984D7B /* SwiftUICaseStudies */,
+ 87BA5F4D2706231100984D7B /* UIKitCaseStudies */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 87BA5F3A270622DB00984D7B /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 87BA5F46270622E500984D7B /* Preview Assets.xcassets in Resources */,
+ 87BA5F43270622E500984D7B /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 87BA5F4C2706231100984D7B /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 87BA5F5D2706231200984D7B /* LaunchScreen.storyboard in Resources */,
+ 87BA5F5A2706231200984D7B /* Assets.xcassets in Resources */,
+ 87BA5F582706231100984D7B /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 87BA5F38270622DB00984D7B /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 87BA5F41270622DB00984D7B /* ContentView.swift in Sources */,
+ 87BA5F3F270622DB00984D7B /* SwiftUICaseStudiesApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 87BA5F4A2706231100984D7B /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 873FE74F27062DF80054CF07 /* MainViewController.swift in Sources */,
+ 873FE765270633310054CF07 /* LazyNavigationAction.swift in Sources */,
+ 873FE759270632E00054CF07 /* CountersTableAction.swift in Sources */,
+ 87BA5F692706240E00984D7B /* CounterViewController.swift in Sources */,
+ 87BA5F682706240E00984D7B /* CounterAction.swift in Sources */,
+ 873FE7712706334C0054CF07 /* EagerNavigationEnvironment.swift in Sources */,
+ 873FE7702706334C0054CF07 /* EagerNavigationViewController.swift in Sources */,
+ 873FE74E27062DF80054CF07 /* MainAction.swift in Sources */,
+ 873FE73527062BB80054CF07 /* RootViewController.swift in Sources */,
+ 873FE769270633310054CF07 /* LazyNavigationState.swift in Sources */,
+ 873FE74727062D850054CF07 /* AuthState.swift in Sources */,
+ 873FE73627062BB80054CF07 /* RootEnvironment.swift in Sources */,
+ 873FE74427062D850054CF07 /* AuthViewController.swift in Sources */,
+ 873FE74327062D850054CF07 /* AuthAction.swift in Sources */,
+ 873FE75A270632E00054CF07 /* CountersTableViewController.swift in Sources */,
+ 873FE7732706334C0054CF07 /* EagerNavigationState.swift in Sources */,
+ 87BA5F6A2706240E00984D7B /* CounterEnvironment.swift in Sources */,
+ 873FE73B27062C7A0054CF07 /* ActivityIndicatorViewController.swift in Sources */,
+ 87BA5F512706231100984D7B /* AppDelegate.swift in Sources */,
+ 873FE766270633310054CF07 /* LazyNavigationViewController.swift in Sources */,
+ 873FE7722706334C0054CF07 /* EagerNavigationReducer.swift in Sources */,
+ 87BA5F6B2706240E00984D7B /* CounterReducer.swift in Sources */,
+ 873FE76F2706334C0054CF07 /* EagerNavigationAction.swift in Sources */,
+ 873FE73D27062C8B0054CF07 /* IfLetStoreController.swift in Sources */,
+ 873FE75127062DF80054CF07 /* MainReducer.swift in Sources */,
+ 873FE75027062DF80054CF07 /* MainEnvironment.swift in Sources */,
+ 873FE768270633310054CF07 /* LazyNavigationReducer.swift in Sources */,
+ 873FE75227062DF80054CF07 /* MainState.swift in Sources */,
+ 873FE74527062D850054CF07 /* AuthEnvironment.swift in Sources */,
+ 873FE75D270632E00054CF07 /* CountersTableState.swift in Sources */,
+ 873FE74627062D850054CF07 /* AuthReducer.swift in Sources */,
+ 873FE75B270632E00054CF07 /* CountersTableEnvironment.swift in Sources */,
+ 873FE75C270632E00054CF07 /* CountersTableReducer.swift in Sources */,
+ 873FE73827062BB80054CF07 /* RootState.swift in Sources */,
+ 873FE73427062BB80054CF07 /* RootAction.swift in Sources */,
+ 87BA5F6C2706240E00984D7B /* CounterState.swift in Sources */,
+ 87BA5F532706231100984D7B /* SceneDelegate.swift in Sources */,
+ 873FE73727062BB80054CF07 /* RootReducer.swift in Sources */,
+ 873FE767270633310054CF07 /* LazyNavigationEnvironment.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 87BA5F562706231100984D7B /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 87BA5F572706231100984D7B /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 87BA5F5B2706231200984D7B /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 87BA5F5C2706231200984D7B /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 872458742706058800A88871 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 872458752706058800A88871 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ };
+ name = Release;
+ };
+ 87BA5F47270622E500984D7B /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"SwiftUICaseStudies/Preview Content\"";
+ DEVELOPMENT_TEAM = 2H5PN3F9B6;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = mike.fullstackswift.SwiftUICaseStudies;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 87BA5F48270622E500984D7B /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"SwiftUICaseStudies/Preview Content\"";
+ DEVELOPMENT_TEAM = 2H5PN3F9B6;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = mike.fullstackswift.SwiftUICaseStudies;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 87BA5F602706231200984D7B /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 2H5PN3F9B6;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = UIKitCaseStudies/Info.plist;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
+ INFOPLIST_KEY_UIMainStoryboardFile = Main;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = mike.fullstackswift.UIKitCaseStudies;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 87BA5F612706231200984D7B /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 2H5PN3F9B6;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = UIKitCaseStudies/Info.plist;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
+ INFOPLIST_KEY_UIMainStoryboardFile = Main;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = mike.fullstackswift.UIKitCaseStudies;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 8724585C2706058700A88871 /* Build configuration list for PBXProject "CaseStudies" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 872458742706058800A88871 /* Debug */,
+ 872458752706058800A88871 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 87BA5F49270622E500984D7B /* Build configuration list for PBXNativeTarget "SwiftUICaseStudies" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 87BA5F47270622E500984D7B /* Debug */,
+ 87BA5F48270622E500984D7B /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 87BA5F5F2706231200984D7B /* Build configuration list for PBXNativeTarget "UIKitCaseStudies" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 87BA5F602706231200984D7B /* Debug */,
+ 87BA5F612706231200984D7B /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ 87030D6F2710EED7009A5353 /* XCRemoteSwiftPackageReference "ReactiveCocoa" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/ReactiveCocoa/ReactiveCocoa.git";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 11.2.2;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 87030D722710EF07009A5353 /* ComposableArchitecture */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = ComposableArchitecture;
+ };
+ 87030D762710EFF5009A5353 /* ComposableArchitecture */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = ComposableArchitecture;
+ };
+ 878AF92C2710F0D30066E71C /* ReactiveCocoa */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 87030D6F2710EED7009A5353 /* XCRemoteSwiftPackageReference "ReactiveCocoa" */;
+ productName = ReactiveCocoa;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 872458592706058700A88871 /* Project object */;
+}
diff --git a/Examples/CaseStudies/CaseStudies.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/CaseStudies/CaseStudies.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/Examples/CaseStudies/CaseStudies.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Examples/CaseStudies/CaseStudies.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/CaseStudies/CaseStudies.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/Examples/CaseStudies/CaseStudies.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Examples/CaseStudies/CaseStudies.xcodeproj/xcshareddata/xcschemes/CaseStudies(SwiftUI).xcscheme b/Examples/CaseStudies/CaseStudies.xcodeproj/xcshareddata/xcschemes/CaseStudies(SwiftUI).xcscheme
new file mode 100644
index 0000000..4159c51
--- /dev/null
+++ b/Examples/CaseStudies/CaseStudies.xcodeproj/xcshareddata/xcschemes/CaseStudies(SwiftUI).xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/CaseStudies/CaseStudies.xcodeproj/xcshareddata/xcschemes/CaseStudies(UIKit).xcscheme b/Examples/CaseStudies/CaseStudies.xcodeproj/xcshareddata/xcschemes/CaseStudies(UIKit).xcscheme
new file mode 100644
index 0000000..770d9fa
--- /dev/null
+++ b/Examples/CaseStudies/CaseStudies.xcodeproj/xcshareddata/xcschemes/CaseStudies(UIKit).xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AccentColor.colorset/Contents.json b/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AppIcon.appiconset/AppIcon.png b/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AppIcon.appiconset/AppIcon.png
new file mode 100644
index 0000000..186b90e
Binary files /dev/null and b/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AppIcon.appiconset/AppIcon.png differ
diff --git a/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..e4b96da
--- /dev/null
+++ b/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,99 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/Contents.json b/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Examples/CaseStudies/SwiftUICaseStudies/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/CaseStudies/SwiftUICaseStudies/ContentView.swift b/Examples/CaseStudies/SwiftUICaseStudies/ContentView.swift
new file mode 100644
index 0000000..35abb2c
--- /dev/null
+++ b/Examples/CaseStudies/SwiftUICaseStudies/ContentView.swift
@@ -0,0 +1,14 @@
+import SwiftUI
+
+struct ContentView: View {
+ var body: some View {
+ Text("Hello, world!")
+ .padding()
+ }
+}
+
+struct ContentView_Previews: PreviewProvider {
+ static var previews: some View {
+ ContentView()
+ }
+}
diff --git a/Examples/CaseStudies/SwiftUICaseStudies/Preview Content/Preview Assets.xcassets/Contents.json b/Examples/CaseStudies/SwiftUICaseStudies/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Examples/CaseStudies/SwiftUICaseStudies/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/CaseStudies/SwiftUICaseStudies/SwiftUICaseStudiesApp.swift b/Examples/CaseStudies/SwiftUICaseStudies/SwiftUICaseStudiesApp.swift
new file mode 100644
index 0000000..1cfeafc
--- /dev/null
+++ b/Examples/CaseStudies/SwiftUICaseStudies/SwiftUICaseStudiesApp.swift
@@ -0,0 +1,10 @@
+import SwiftUI
+
+@main
+struct SwiftUICaseStudiesApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/AppDelegate.swift b/Examples/CaseStudies/UIKitCaseStudies/AppDelegate.swift
new file mode 100644
index 0000000..26cbdef
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/AppDelegate.swift
@@ -0,0 +1,7 @@
+import UIKit
+
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+}
+
diff --git a/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/AccentColor.colorset/Contents.json b/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/AppIcon.appiconset/AppIcon.png b/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/AppIcon.appiconset/AppIcon.png
new file mode 100644
index 0000000..186b90e
Binary files /dev/null and b/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/AppIcon.appiconset/AppIcon.png differ
diff --git a/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..e4b96da
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,99 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/Contents.json b/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthAction.swift b/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthAction.swift
new file mode 100644
index 0000000..15ae7d4
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthAction.swift
@@ -0,0 +1,11 @@
+import ComposableArchitecture
+import Foundation
+
+enum AuthAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case login
+ case changeRootScreen(RootScreen)
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthEnvironment.swift b/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthEnvironment.swift
new file mode 100644
index 0000000..a96bc7e
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct AuthEnvironment {
+
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthReducer.swift b/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthReducer.swift
new file mode 100644
index 0000000..8106c4f
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthReducer.swift
@@ -0,0 +1,22 @@
+import ComposableArchitecture
+import Foundation
+
+let AuthReducer = Reducer.combine(
+
+ Reducer { state, action, environment in
+ switch action {
+ case .viewDidLoad:
+ break
+ case .viewWillAppear:
+ break
+ case .viewWillDisappear:
+ break
+ case .login:
+ return Effect(value: AuthAction.changeRootScreen(.main))
+ default:
+ break
+ }
+ return .none
+ }
+)
+.debug()
diff --git a/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthState.swift b/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthState.swift
new file mode 100644
index 0000000..0f0ea3d
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthState.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct AuthState: Equatable {
+
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthViewController.swift b/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthViewController.swift
new file mode 100644
index 0000000..d4a2a00
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/AuthScreen/AuthViewController.swift
@@ -0,0 +1,117 @@
+import ComposableArchitecture
+import SwiftUI
+import UIKit
+
+
+final class AuthViewController: UIViewController {
+
+ private var store: Store!
+
+ private var viewStore: ViewStore!
+
+ @IBOutlet weak private var btnLogin: UIButton!
+
+ init(store: Store? = nil) {
+ super.init(nibName: nil, bundle: nil)
+ setStore(store: store)
+ }
+
+ static func fromStoryboard(store: Store? = nil) -> AuthViewController {
+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
+ let vc = storyboard.instantiateViewController(withIdentifier: "AuthViewController") as! AuthViewController
+ vc.setStore(store: store)
+ return vc
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ private func setStore(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: AuthState(), reducer: AuthReducer, environment: AuthEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: AuthAction.init))
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ viewStore.send(.viewDidLoad)
+ viewStore.action <~ btnLogin.reactive.controlEvents(.touchUpInside).map { _ in ViewAction.clickBtnLogin }
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ viewStore.send(.viewWillAppear)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ viewStore.send(.viewWillDisappear)
+ }
+
+}
+
+struct AuthViewController_Previews: PreviewProvider {
+ static var previews: some View {
+ let vc = AuthViewController()
+ UIViewRepresented(makeUIView: { _ in vc.view })
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+
+ init(state: AuthState) {
+
+ }
+}
+
+fileprivate enum ViewAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case clickBtnLogin
+
+ init(action: AuthAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension AuthState {
+
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+
+ }
+ }
+}
+
+fileprivate extension AuthAction {
+
+ init(action: ViewAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ case .clickBtnLogin:
+ self = .login
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/Base.lproj/LaunchScreen.storyboard b/Examples/CaseStudies/UIKitCaseStudies/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/CaseStudies/UIKitCaseStudies/Base.lproj/Main.storyboard b/Examples/CaseStudies/UIKitCaseStudies/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..d267725
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/Base.lproj/Main.storyboard
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterAction.swift b/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterAction.swift
new file mode 100644
index 0000000..2c85ba0
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterAction.swift
@@ -0,0 +1,11 @@
+import ComposableArchitecture
+import Foundation
+
+enum CounterAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case decrementButtonTapped
+ case incrementButtonTapped
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterEnvironment.swift b/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterEnvironment.swift
new file mode 100644
index 0000000..e756835
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct CounterEnvironment {
+
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterReducer.swift b/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterReducer.swift
new file mode 100644
index 0000000..23841d5
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterReducer.swift
@@ -0,0 +1,24 @@
+import ComposableArchitecture
+import Foundation
+
+let CounterReducer = Reducer.combine(
+
+ Reducer { state, action, environment in
+ switch action {
+ case .viewDidLoad:
+ break
+ case .viewWillAppear:
+ break
+ case .viewWillDisappear:
+ break
+ case .decrementButtonTapped:
+ state.count -= 1
+ case .incrementButtonTapped:
+ state.count += 1
+ default:
+ break
+ }
+ return .none
+ }
+)
+ .debug()
diff --git a/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterState.swift b/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterState.swift
new file mode 100644
index 0000000..a12eea9
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterState.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct CounterState: Equatable {
+ var count = 0
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterViewController.swift b/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterViewController.swift
new file mode 100644
index 0000000..d00298a
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/CounterScreen/CounterViewController.swift
@@ -0,0 +1,129 @@
+import ComposableArchitecture
+import SwiftUI
+import UIKit
+import ReactiveCocoa
+
+final class CounterViewController: UIViewController {
+
+ private let store: Store
+
+ private let viewStore: ViewStore
+
+ init(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: CounterState(), reducer: CounterReducer, environment: CounterEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: CounterAction.init))
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ viewStore.send(.viewDidLoad)
+ view.backgroundColor = .systemBackground
+ let decrementButton = UIButton(type: .system)
+ decrementButton.setTitle("−", for: .normal)
+ let countLabel = UILabel()
+ countLabel.font = .monospacedDigitSystemFont(ofSize: 17, weight: .regular)
+ let incrementButton = UIButton(type: .system)
+ incrementButton.setTitle("+", for: .normal)
+ let rootStackView = UIStackView(arrangedSubviews: [
+ decrementButton,
+ countLabel,
+ incrementButton,
+ ])
+ rootStackView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(rootStackView)
+ NSLayoutConstraint.activate([
+ rootStackView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor),
+ rootStackView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor),
+ ])
+ //binding view to store
+ viewStore.action <~ incrementButton.reactive.controlEvents(.touchUpInside).map {_ in ViewAction.incrementButtonTapped}
+ viewStore.action <~ decrementButton.reactive.controlEvents(.touchUpInside).map {_ in ViewAction.decrementButtonTapped}
+ //bind store to view
+ countLabel.reactive.text <~ viewStore.publisher.count.map {String($0)}
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ viewStore.send(.viewWillAppear)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ viewStore.send(.viewWillDisappear)
+ }
+
+}
+
+struct CounterViewController_Previews: PreviewProvider {
+ static var previews: some View {
+ let vc = CounterViewController()
+ UIViewRepresented(makeUIView: { _ in vc.view })
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+ var count = 0
+ init(state: CounterState) {
+ count = state.count
+ }
+}
+
+fileprivate enum ViewAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case decrementButtonTapped
+ case incrementButtonTapped
+
+ init(action: CounterAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension CounterState {
+
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+
+ }
+ }
+}
+
+fileprivate extension CounterAction {
+
+ init(action: ViewAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ case .decrementButtonTapped:
+ self = .decrementButtonTapped
+ case .incrementButtonTapped:
+ self = .incrementButtonTapped
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableAction.swift b/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableAction.swift
new file mode 100644
index 0000000..402ae30
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableAction.swift
@@ -0,0 +1,10 @@
+import ComposableArchitecture
+import Foundation
+
+enum CountersTableAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case counter(index: Int, action: CounterAction)
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableEnvironment.swift b/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableEnvironment.swift
new file mode 100644
index 0000000..6d14050
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct CountersTableEnvironment {
+
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableReducer.swift b/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableReducer.swift
new file mode 100644
index 0000000..b7a5429
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableReducer.swift
@@ -0,0 +1,22 @@
+import ComposableArchitecture
+import Foundation
+
+let CountersTableReducer = Reducer.combine(
+ CounterReducer.forEach(state: \.counters, action: /CountersTableAction.counter(index:action:), environment: { _ in
+ .init()
+ }),
+ Reducer { state, action, enviroment in
+ switch action {
+ case .viewDidLoad:
+ break
+ case .viewWillAppear:
+ break
+ case .viewWillDisappear:
+ break
+ default:
+ break
+ }
+ return .none
+ }
+)
+.debug()
diff --git a/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableState.swift b/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableState.swift
new file mode 100644
index 0000000..5fef0b9
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableState.swift
@@ -0,0 +1,11 @@
+import ComposableArchitecture
+import Foundation
+
+struct CountersTableState: Equatable {
+ var counters: [CounterState] = [CounterState(),
+ CounterState(),
+ CounterState(),
+ CounterState(),
+ CounterState()]
+
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableViewController.swift b/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableViewController.swift
new file mode 100644
index 0000000..6117091
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/CountersListScreen/CountersTableViewController.swift
@@ -0,0 +1,133 @@
+import ComposableArchitecture
+import SwiftUI
+import UIKit
+
+
+final class CountersTableViewController: UITableViewController {
+
+ private let store: Store
+
+ private let viewStore: ViewStore
+
+ private let cellIdentifier = "Cell"
+
+ private var dataSource: [CounterState] = []
+
+ init(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: CountersTableState(), reducer: CountersTableReducer, environment: CountersTableEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: CountersTableAction.init))
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ viewStore.send(.viewDidLoad)
+ self.title = "Lists"
+ self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
+ self.viewStore.publisher.counters.startWithValues { [weak self] in
+ guard let self = self else {return}
+ self.dataSource = $0
+ self.tableView.reloadData()
+ }
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ viewStore.send(.viewWillAppear)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ viewStore.send(.viewWillDisappear)
+ }
+
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ self.dataSource.count
+ }
+
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
+ cell.accessoryType = .disclosureIndicator
+ cell.textLabel?.text = "\(self.dataSource[indexPath.row].count)"
+ return cell
+ }
+
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ navigationController?.pushViewController(
+ CounterViewController(
+ store: store.scope(
+ state: { $0.counters[indexPath.row] },
+ action: { .counter(index: indexPath.row, action: $0) }
+ )
+ ),
+ animated: true
+ )
+ }
+}
+
+struct CountersTableViewController_Previews: PreviewProvider {
+ static var previews: some View {
+ let vc = CountersTableViewController()
+ UIViewRepresented(makeUIView: { _ in vc.view })
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+ var counters: [CounterState] = []
+ init(state: CountersTableState) {
+ self.counters = state.counters
+ }
+}
+
+fileprivate enum ViewAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+
+ init(action: CountersTableAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension CountersTableState {
+
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+
+ }
+ }
+}
+
+fileprivate extension CountersTableAction {
+
+ init(action: ViewAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationAction.swift b/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationAction.swift
new file mode 100644
index 0000000..df66182
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationAction.swift
@@ -0,0 +1,12 @@
+import ComposableArchitecture
+import Foundation
+
+enum EagerNavigationAction: Equatable {
+ case optionalCounter(CounterAction)
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case setNavigation(isActive: Bool)
+ case setNavigationIsActiveDelayCompleted
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationEnvironment.swift b/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationEnvironment.swift
new file mode 100644
index 0000000..53bbd5c
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct EagerNavigationEnvironment {
+
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationReducer.swift b/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationReducer.swift
new file mode 100644
index 0000000..87f544c
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationReducer.swift
@@ -0,0 +1,38 @@
+import ComposableArchitecture
+import Foundation
+
+let EagerNavigationReducer = Reducer.combine(
+ CounterReducer
+ .optional()
+ .pullback(state: \.optionalCounter, action: /EagerNavigationAction.optionalCounter, environment: { _ in
+ .init()
+ }),
+ Reducer { state, action, environment in
+ struct CancelId: Hashable {}
+ switch action {
+ case .viewDidLoad:
+ break
+ case .viewWillAppear:
+ break
+ case .viewWillDisappear:
+ break
+ case .setNavigation(isActive: true):
+ state.isNavigationActive = true
+ return Effect(value: .setNavigationIsActiveDelayCompleted)
+ .delay(1, on: QueueScheduler.main)
+ case .setNavigation(isActive: false):
+ state.isNavigationActive = false
+ state.optionalCounter = nil
+ return .none
+ case .setNavigationIsActiveDelayCompleted:
+ state.optionalCounter = CounterState()
+ return .none
+ case .optionalCounter:
+ return .none
+ default:
+ break
+ }
+ return .none
+ }
+)
+ .debug()
diff --git a/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationState.swift b/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationState.swift
new file mode 100644
index 0000000..7b1148e
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationState.swift
@@ -0,0 +1,7 @@
+import ComposableArchitecture
+import Foundation
+
+struct EagerNavigationState: Equatable {
+ var isNavigationActive = false
+ var optionalCounter: CounterState?
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationViewController.swift b/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationViewController.swift
new file mode 100644
index 0000000..25461b2
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/EagerNavigation/EagerNavigationViewController.swift
@@ -0,0 +1,144 @@
+import ComposableArchitecture
+import SwiftUI
+import UIKit
+
+
+final class EagerNavigationViewController: UIViewController {
+
+ private let store: Store
+
+ private let viewStore: ViewStore
+
+ init(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: EagerNavigationState(), reducer: EagerNavigationReducer, environment: EagerNavigationEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: EagerNavigationAction.init))
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ viewStore.send(.viewDidLoad)
+ title = "Navigate and load"
+ view.backgroundColor = .systemBackground
+ let button = UIButton(type: .system)
+ button.setTitle("Load optional counter", for: .normal)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(button)
+ NSLayoutConstraint.activate([
+ button.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor),
+ button.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor),
+ ])
+ viewStore.action <~ button.reactive.controlEvents(.touchUpInside).map {_ in ViewAction.setNavigation(isActive: true)}
+ viewStore.publisher.isNavigationActive.startWithValues { [weak self] isNavigationActive in
+ guard let self = self else { return }
+ if isNavigationActive {
+ self.navigationController?.pushViewController(
+ IfLetStoreController(
+ store: self.store
+ .scope(state: \.optionalCounter, action: EagerNavigationAction.optionalCounter),
+ then: CounterViewController.init(store:),
+ else: ActivityIndicatorViewController.init
+ ),
+ animated: true
+ )
+ } else {
+ self.navigationController?.popToViewController(self, animated: true)
+ }
+ }
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+ if !self.isMovingToParent {
+ self.viewStore.send(.setNavigation(isActive: false))
+ }
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ viewStore.send(.viewWillAppear)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ viewStore.send(.viewWillDisappear)
+ }
+}
+
+struct EagerNavigationViewController_Previews: PreviewProvider {
+ static var previews: some View {
+ let vc = EagerNavigationViewController()
+ UIViewRepresented(makeUIView: { _ in vc.view })
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+ var isNavigationActive = false
+ var optionalCounter: CounterState?
+ init(state: EagerNavigationState) {
+ self.isNavigationActive = state.isNavigationActive
+ self.optionalCounter = state.optionalCounter
+ }
+}
+
+fileprivate enum ViewAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case optionalCounter(CounterAction)
+ case setNavigation(isActive: Bool)
+ case setNavigationIsActiveDelayCompleted
+
+ init(action: EagerNavigationAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension EagerNavigationState {
+
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+
+ }
+ }
+}
+
+fileprivate extension EagerNavigationAction {
+
+ init(action: ViewAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ case .optionalCounter(let counterAction):
+ self = .optionalCounter(counterAction)
+ case .setNavigation(let isActive):
+ self = .setNavigation(isActive: isActive)
+ case .setNavigationIsActiveDelayCompleted:
+ self = .setNavigationIsActiveDelayCompleted
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/Info.plist b/Examples/CaseStudies/UIKitCaseStudies/Info.plist
new file mode 100644
index 0000000..dd3c9af
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/Info.plist
@@ -0,0 +1,25 @@
+
+
+
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+ UIWindowSceneSessionRoleApplication
+
+
+ UISceneConfigurationName
+ Default Configuration
+ UISceneDelegateClassName
+ $(PRODUCT_MODULE_NAME).SceneDelegate
+ UISceneStoryboardFile
+ Main
+
+
+
+
+
+
diff --git a/Examples/CaseStudies/UIKitCaseStudies/Internal/ActivityIndicatorViewController.swift b/Examples/CaseStudies/UIKitCaseStudies/Internal/ActivityIndicatorViewController.swift
new file mode 100644
index 0000000..940151f
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/Internal/ActivityIndicatorViewController.swift
@@ -0,0 +1,20 @@
+import UIKit
+
+final class ActivityIndicatorViewController: UIViewController {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ view.backgroundColor = .white
+ let activityIndicator = UIActivityIndicatorView()
+ activityIndicator.startAnimating()
+ activityIndicator.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(activityIndicator)
+
+ NSLayoutConstraint.activate([
+ activityIndicator.centerXAnchor.constraint(
+ equalTo: view.safeAreaLayoutGuide.centerXAnchor),
+ activityIndicator.centerYAnchor.constraint(
+ equalTo: view.safeAreaLayoutGuide.centerYAnchor),
+ ])
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/Internal/IfLetStoreController.swift b/Examples/CaseStudies/UIKitCaseStudies/Internal/IfLetStoreController.swift
new file mode 100644
index 0000000..9fe1209
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/Internal/IfLetStoreController.swift
@@ -0,0 +1,50 @@
+import ComposableArchitecture
+import UIKit
+import ReactiveSwift
+import ReactiveCocoa
+
+final class IfLetStoreController: UIViewController {
+ let store: Store
+ let ifDestination: (Store) -> UIViewController
+ let elseDestination: () -> UIViewController
+
+ private var viewController = UIViewController() {
+ willSet {
+ self.viewController.willMove(toParent: nil)
+ self.viewController.view.removeFromSuperview()
+ self.viewController.removeFromParent()
+ self.addChild(newValue)
+ self.view.addSubview(newValue.view)
+ newValue.didMove(toParent: self)
+ }
+ }
+
+ init(store: Store,
+ then ifDestination: @escaping (Store) -> UIViewController,
+ else elseDestination: @escaping () -> UIViewController
+ ) {
+ self.store = store
+ self.ifDestination = ifDestination
+ self.elseDestination = elseDestination
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ self.store.ifLet(
+ then: { [weak self] store in
+ guard let self = self else { return }
+ self.viewController = self.ifDestination(store)
+ },
+ else: { [weak self] in
+ guard let self = self else { return }
+ self.viewController = self.elseDestination()
+ }
+ )
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationAction.swift b/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationAction.swift
new file mode 100644
index 0000000..283186a
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationAction.swift
@@ -0,0 +1,12 @@
+import ComposableArchitecture
+import Foundation
+
+enum LazyNavigationAction: Equatable {
+ case optionalCounter(CounterAction)
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case setNavigation(isActive: Bool)
+ case setNavigationIsActiveDelayCompleted
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationEnvironment.swift b/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationEnvironment.swift
new file mode 100644
index 0000000..69c27f4
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct LazyNavigationEnvironment {
+
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationReducer.swift b/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationReducer.swift
new file mode 100644
index 0000000..6d84ee6
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationReducer.swift
@@ -0,0 +1,38 @@
+import ComposableArchitecture
+import Foundation
+
+let LazyNavigationReducer = Reducer.combine(
+ CounterReducer
+ .optional()
+ .pullback(state: \.optionalCounter,action: /LazyNavigationAction.optionalCounter,environment: { _ in
+ .init()
+ }),
+ Reducer { state, action, environment in
+ struct CancelId: Hashable {}
+ switch action {
+ case .viewDidLoad:
+ break
+ case .viewWillAppear:
+ break
+ case .viewWillDisappear:
+ break
+ case .setNavigation(isActive: true):
+ state.isActivityIndicatorHidden = false
+ return Effect(value: .setNavigationIsActiveDelayCompleted)
+ .delay(1, on: QueueScheduler.main)
+ case .setNavigation(isActive: false):
+ state.optionalCounter = nil
+ return .none
+ case .setNavigationIsActiveDelayCompleted:
+ state.isActivityIndicatorHidden = true
+ state.optionalCounter = CounterState()
+ return .none
+ case .optionalCounter:
+ return .none
+ default:
+ break
+ }
+ return .none
+ }
+)
+ .debug()
diff --git a/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationState.swift b/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationState.swift
new file mode 100644
index 0000000..b93e820
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationState.swift
@@ -0,0 +1,7 @@
+import ComposableArchitecture
+import Foundation
+
+struct LazyNavigationState: Equatable {
+ var optionalCounter: CounterState?
+ var isActivityIndicatorHidden = true
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationViewController.swift b/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationViewController.swift
new file mode 100644
index 0000000..ef23bb5
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/LazyNavigation/LazyNavigationViewController.swift
@@ -0,0 +1,146 @@
+import ComposableArchitecture
+import SwiftUI
+import UIKit
+
+
+final class LazyNavigationViewController: UIViewController {
+
+ private let store: Store
+
+ private let viewStore: ViewStore
+
+ init(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: LazyNavigationState(), reducer: LazyNavigationReducer, environment: LazyNavigationEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: LazyNavigationAction.init))
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ viewStore.send(.viewDidLoad)
+ title = "Load then navigate"
+ view.backgroundColor = .systemBackground
+ let button = UIButton(type: .system)
+ button.setTitle("Load optional counter", for: .normal)
+ let activityIndicator = UIActivityIndicatorView()
+ activityIndicator.startAnimating()
+ let rootStackView = UIStackView(arrangedSubviews: [
+ button,
+ activityIndicator,
+ ])
+ rootStackView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(rootStackView)
+ NSLayoutConstraint.activate([
+ rootStackView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor),
+ rootStackView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor),
+ ])
+ viewStore.action <~ button.reactive.controlEvents(.touchUpInside).map{_ in ViewAction.setNavigation(isActive: true)}
+ viewStore.publisher.isActivityIndicatorHidden
+ .assign(to: \.isHidden, on: activityIndicator)
+ store.scope(state: \.optionalCounter, action: LazyNavigationAction.optionalCounter)
+ .ifLet(
+ then: { [weak self] store in
+ self?.navigationController?.pushViewController(
+ CounterViewController(store: store), animated: true)
+ },
+ else: { [weak self] in
+ guard let self = self else { return }
+ self.navigationController?.popToViewController(self, animated: true)
+ }
+ )
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+ if !self.isMovingToParent {
+ self.viewStore.send(.setNavigation(isActive: false))
+ }
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ viewStore.send(.viewWillAppear)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ viewStore.send(.viewWillDisappear)
+ }
+}
+
+struct LazyNavigationViewController_Previews: PreviewProvider {
+ static var previews: some View {
+ let vc = LazyNavigationViewController()
+ UIViewRepresented(makeUIView: { _ in vc.view })
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+ var optionalCounter: CounterState?
+ var isActivityIndicatorHidden = true
+ init(state: LazyNavigationState) {
+ self.optionalCounter = state.optionalCounter
+ self.isActivityIndicatorHidden = state.isActivityIndicatorHidden
+ }}
+
+fileprivate enum ViewAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case optionalCounter(CounterAction)
+ case setNavigation(isActive: Bool)
+ case setNavigationIsActiveDelayCompleted
+
+ init(action: LazyNavigationAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension LazyNavigationState {
+
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+
+ }
+ }
+}
+
+fileprivate extension LazyNavigationAction {
+
+ init(action: ViewAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ case .optionalCounter(let counterAction):
+ self = .optionalCounter(counterAction)
+ case .setNavigation(let isActive):
+ self = .setNavigation(isActive: isActive)
+ case .setNavigationIsActiveDelayCompleted:
+ self = .setNavigationIsActiveDelayCompleted
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainAction.swift b/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainAction.swift
new file mode 100644
index 0000000..1d33db4
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainAction.swift
@@ -0,0 +1,11 @@
+import ComposableArchitecture
+import Foundation
+
+enum MainAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case logout
+ case changRootScreen(RootScreen)
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainEnvironment.swift b/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainEnvironment.swift
new file mode 100644
index 0000000..19d428c
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct MainEnvironment {
+
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainReducer.swift b/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainReducer.swift
new file mode 100644
index 0000000..78a3864
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainReducer.swift
@@ -0,0 +1,22 @@
+import ComposableArchitecture
+import Foundation
+
+let MainReducer = Reducer.combine(
+
+ Reducer { state, action, environment in
+ switch action {
+ case .viewDidLoad:
+ break
+ case .viewWillAppear:
+ break
+ case .viewWillDisappear:
+ break
+ case .logout:
+ return Effect(value: MainAction.changRootScreen(.auth))
+ default:
+ break
+ }
+ return .none
+ }
+)
+ .debug()
diff --git a/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainState.swift b/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainState.swift
new file mode 100644
index 0000000..11846cf
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainState.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct MainState: Equatable {
+
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainViewController.swift b/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainViewController.swift
new file mode 100644
index 0000000..47889ad
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/MainScreen/MainViewController.swift
@@ -0,0 +1,166 @@
+import ComposableArchitecture
+import SwiftUI
+import UIKit
+
+
+struct CaseStudy {
+ let title: String
+ let viewController: () -> UIViewController
+
+ init(title: String, viewController: @autoclosure @escaping () -> UIViewController) {
+ self.title = title
+ self.viewController = viewController
+ }
+}
+
+let dataSource: [CaseStudy] = [
+ CaseStudy(title: "Basics",viewController: CounterViewController()),
+ CaseStudy(title: "Lists",viewController: CountersTableViewController()),
+ CaseStudy(title: "Eager Navigation",viewController: EagerNavigationViewController()),
+ CaseStudy(title: "Lazy Navigation",viewController: LazyNavigationViewController()),
+]
+
+final class MainViewController: UIViewController {
+
+ private var store: Store!
+
+ private var viewStore: ViewStore!
+
+ @IBOutlet weak private var tableView: UITableView!
+
+ init(store: Store? = nil) {
+ super.init(nibName: nil, bundle: nil)
+ setStore(store: store)
+ }
+
+ static func fromStoryboard(store: Store? = nil) -> MainViewController {
+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
+ let vc = storyboard.instantiateViewController(withIdentifier: "MainViewController") as! MainViewController
+ vc.setStore(store: store)
+ return vc
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ private func setStore(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: MainState(), reducer: MainReducer, environment: MainEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: MainAction.init))
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ viewStore.send(.viewDidLoad)
+
+ //navigation
+ title = "Case Studies"
+ navigationController?.navigationBar.prefersLargeTitles = true
+ let buttonLogout = UIButton(type: .system)
+ buttonLogout.setTitle("Logout", for: .normal)
+ viewStore.action <~ buttonLogout.reactive.controlEvents(.touchUpInside).map{_ in ViewAction.logout}
+ let rightBarButtonItem = UIBarButtonItem(customView: buttonLogout)
+ navigationItem.rightBarButtonItem = rightBarButtonItem
+ tableView.delegate = self
+ tableView.dataSource = self
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ viewStore.send(.viewWillAppear)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ viewStore.send(.viewWillDisappear)
+ }
+}
+
+extension MainViewController: UITableViewDataSource {
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ dataSource.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)-> UITableViewCell {
+ let caseStudy = dataSource[indexPath.row]
+ let cell = UITableViewCell()
+ cell.accessoryType = .disclosureIndicator
+ cell.textLabel?.text = caseStudy.title
+ return cell
+ }
+}
+
+extension MainViewController: UITableViewDelegate {
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ let caseStudy = dataSource[indexPath.row]
+ self.navigationController?.pushViewController(caseStudy.viewController(), animated: true)
+ }
+}
+
+struct MainViewController_Previews: PreviewProvider {
+ static var previews: some View {
+ let vc = MainViewController()
+ UIViewRepresented(makeUIView: { _ in vc.view })
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+
+ init(state: MainState) {
+
+ }
+}
+
+fileprivate enum ViewAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+ case logout
+
+ init(action: MainAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension MainState {
+
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+
+ }
+ }
+}
+
+fileprivate extension MainAction {
+
+ init(action: ViewAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ case .logout:
+ self = .logout
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootAction.swift b/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootAction.swift
new file mode 100644
index 0000000..675506b
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootAction.swift
@@ -0,0 +1,11 @@
+import ComposableArchitecture
+import Foundation
+
+enum RootAction: Equatable {
+ case authAction(AuthAction)
+ case mainAction(MainAction)
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootEnvironment.swift b/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootEnvironment.swift
new file mode 100644
index 0000000..6b791ec
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct RootEnvironment {
+
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootReducer.swift b/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootReducer.swift
new file mode 100644
index 0000000..be85597
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootReducer.swift
@@ -0,0 +1,29 @@
+import ComposableArchitecture
+import Foundation
+
+let RootReducer = Reducer.combine(
+ AuthReducer.pullback(state: \.authState, action: /RootAction.authAction, environment: { _ in
+ .init()
+ }),
+ MainReducer.pullback(state: \.mainState, action: /RootAction.mainAction, environment: { _ in
+ .init()
+ }),
+ Reducer { state, action, environment in
+ switch action {
+ case .authAction(.changeRootScreen(let screen)):
+ state.rootScreen = screen
+ case .mainAction(.changRootScreen(let screen)):
+ state.rootScreen = screen
+ case .viewDidLoad:
+ break
+ case .viewWillAppear:
+ break
+ case .viewWillDisappear:
+ break
+ default:
+ break
+ }
+ return .none
+ }
+)
+ .debug()
diff --git a/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootState.swift b/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootState.swift
new file mode 100644
index 0000000..b0b3276
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootState.swift
@@ -0,0 +1,13 @@
+import ComposableArchitecture
+import Foundation
+
+struct RootState: Equatable {
+ var authState = AuthState()
+ var mainState = MainState()
+ var rootScreen: RootScreen = .main
+}
+
+enum RootScreen: Equatable {
+ case main
+ case auth
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootViewController.swift b/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootViewController.swift
new file mode 100644
index 0000000..dc7e0a0
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/RootScreen/RootViewController.swift
@@ -0,0 +1,126 @@
+import ComposableArchitecture
+import SwiftUI
+import UIKit
+
+
+final class RootViewController: UIViewController {
+
+ private let store: Store
+
+ private let viewStore: ViewStore
+
+ private var viewController = UIViewController() {
+ willSet {
+ self.viewController.willMove(toParent: nil)
+ self.viewController.view.removeFromSuperview()
+ self.viewController.removeFromParent()
+ self.addChild(newValue)
+ newValue.view.frame = self.view.frame
+ self.view.addSubview(newValue.view)
+ newValue.didMove(toParent: self)
+ }
+ }
+
+ init(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: RootState(), reducer: RootReducer, environment: RootEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: RootAction.init))
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ viewStore.send(.viewDidLoad)
+ view.backgroundColor = .white
+ viewStore.publisher.rootScreen.startWithValues { [weak self] screen in
+ guard let self = self else {return}
+ switch screen {
+ case .main:
+ let vc = MainViewController.fromStoryboard(store: self.store.scope(state: \.mainState, action: RootAction.mainAction))
+ let nav = UINavigationController(rootViewController: vc)
+ self.viewController = nav
+ case .auth:
+ let vc = AuthViewController.fromStoryboard(store: self.store.scope(state: \.authState, action: RootAction.authAction))
+ self.viewController = vc
+ }
+ }
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ viewStore.send(.viewWillAppear)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ viewStore.send(.viewWillDisappear)
+ }
+
+}
+
+struct RootViewController_Previews: PreviewProvider {
+ static var previews: some View {
+ let vc = RootViewController()
+ UIViewRepresented(makeUIView: { _ in vc.view })
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+ var rootScreen: RootScreen = .main
+ init(state: RootState) {
+ rootScreen = state.rootScreen
+ }
+}
+
+fileprivate enum ViewAction: Equatable {
+ case viewDidLoad
+ case viewWillAppear
+ case viewWillDisappear
+ case none
+
+ init(action: RootAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension RootState {
+
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+
+ }
+ }
+
+}
+
+fileprivate extension RootAction {
+
+ init(action: ViewAction) {
+ switch action {
+ case .viewDidLoad:
+ self = .viewDidLoad
+ case .viewWillAppear:
+ self = .viewWillAppear
+ case .viewWillDisappear:
+ self = .viewWillDisappear
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/CaseStudies/UIKitCaseStudies/SceneDelegate.swift b/Examples/CaseStudies/UIKitCaseStudies/SceneDelegate.swift
new file mode 100644
index 0000000..0fa5fab
--- /dev/null
+++ b/Examples/CaseStudies/UIKitCaseStudies/SceneDelegate.swift
@@ -0,0 +1,13 @@
+import UIKit
+
+class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+ var window: UIWindow?
+ func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+ guard let _ = (scene as? UIWindowScene) else { return }
+ self.window = (scene as? UIWindowScene).map(UIWindow.init(windowScene:))
+ self.window?.rootViewController = UINavigationController(
+ rootViewController: RootViewController())
+ self.window?.makeKeyAndVisible()
+ }
+}
+
diff --git a/Examples/Package.swift b/Examples/Package.swift
new file mode 100644
index 0000000..cd451c3
--- /dev/null
+++ b/Examples/Package.swift
@@ -0,0 +1,9 @@
+// swift-tools-version:5.2
+
+import PackageDescription
+
+let package = Package(
+ name: "Examples",
+ products: [],
+ targets: []
+)
diff --git a/Examples/Todos/Shared/Assets.xcassets/AccentColor.colorset/Contents.json b/Examples/Todos/Shared/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Examples/Todos/Shared/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/Todos/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/Todos/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..c136eaf
--- /dev/null
+++ b/Examples/Todos/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,148 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/Todos/Shared/Assets.xcassets/Contents.json b/Examples/Todos/Shared/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Examples/Todos/Shared/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/Todos/Shared/TodoApp/AuthScreen/AuthAction.swift b/Examples/Todos/Shared/TodoApp/AuthScreen/AuthAction.swift
new file mode 100644
index 0000000..1c13ae7
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/AuthScreen/AuthAction.swift
@@ -0,0 +1,10 @@
+import ComposableArchitecture
+import Foundation
+
+enum AuthAction: Equatable {
+ case viewOnAppear
+ case viewOnDisappear
+ case none
+ case login
+ case changeRootScreen(RootScreen)
+}
diff --git a/Examples/Todos/Shared/TodoApp/AuthScreen/AuthEnvironment.swift b/Examples/Todos/Shared/TodoApp/AuthScreen/AuthEnvironment.swift
new file mode 100644
index 0000000..433425c
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/AuthScreen/AuthEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct AuthEnvironment {
+
+}
diff --git a/Examples/Todos/Shared/TodoApp/AuthScreen/AuthReducer.swift b/Examples/Todos/Shared/TodoApp/AuthScreen/AuthReducer.swift
new file mode 100644
index 0000000..8a7a1c2
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/AuthScreen/AuthReducer.swift
@@ -0,0 +1,20 @@
+import ComposableArchitecture
+import Foundation
+
+let AuthReducer = Reducer.combine(
+
+ Reducer { state, action, environment in
+ switch action {
+ case .viewOnAppear:
+ break
+ case .viewOnDisappear:
+ break
+ case .login:
+ return Effect(value: AuthAction.changeRootScreen(.main))
+ default:
+ break
+ }
+ return .none
+ }
+)
+ .debug()
diff --git a/Examples/Todos/Shared/TodoApp/AuthScreen/AuthState.swift b/Examples/Todos/Shared/TodoApp/AuthScreen/AuthState.swift
new file mode 100644
index 0000000..fe78e09
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/AuthScreen/AuthState.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct AuthState: Equatable {
+
+}
diff --git a/Examples/Todos/Shared/TodoApp/AuthScreen/AuthView.swift b/Examples/Todos/Shared/TodoApp/AuthScreen/AuthView.swift
new file mode 100644
index 0000000..356d855
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/AuthScreen/AuthView.swift
@@ -0,0 +1,89 @@
+import ComposableArchitecture
+import SwiftUI
+
+struct AuthView: View {
+
+ private let store: Store
+
+ @ObservedObject
+ private var viewStore: ViewStore
+
+ init(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: AuthState(), reducer: AuthReducer, environment: AuthEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: AuthAction.init))
+ }
+
+ var body: some View {
+ ZStack {
+ Button("Login") {
+ viewStore.send(.login)
+ }
+ }
+ .onAppear {
+ viewStore.send(.viewOnAppear)
+ }
+ .onDisappear {
+ viewStore.send(.viewOnDisappear)
+ }
+ }
+}
+
+struct AuthView_Previews: PreviewProvider {
+ static var previews: some View {
+ AuthView()
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+
+ init(state: AuthState) {
+
+ }
+}
+
+fileprivate enum ViewAction: Equatable {
+ case viewOnAppear
+ case viewOnDisappear
+ case none
+ case login
+ init(action: AuthAction) {
+ switch action {
+ case .viewOnAppear:
+ self = .viewOnAppear
+ case .viewOnDisappear:
+ self = .viewOnDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension AuthState {
+
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+
+ }
+ }
+
+}
+
+fileprivate extension AuthAction {
+
+ init(action: ViewAction) {
+ switch action {
+ case .viewOnAppear:
+ self = .viewOnAppear
+ case .viewOnDisappear:
+ self = .viewOnDisappear
+ case .login:
+ self = .login
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/Todos/Shared/TodoApp/CounterScreen/CounterAction.swift b/Examples/Todos/Shared/TodoApp/CounterScreen/CounterAction.swift
new file mode 100644
index 0000000..333a112
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/CounterScreen/CounterAction.swift
@@ -0,0 +1,10 @@
+import ComposableArchitecture
+import Foundation
+
+enum CounterAction: Equatable {
+ case viewOnAppear
+ case viewOnDisappear
+ case none
+ case increment
+ case decrement
+}
diff --git a/Examples/Todos/Shared/TodoApp/CounterScreen/CounterEnvironment.swift b/Examples/Todos/Shared/TodoApp/CounterScreen/CounterEnvironment.swift
new file mode 100644
index 0000000..824cb72
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/CounterScreen/CounterEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct CounterEnvironment {
+
+}
diff --git a/Examples/Todos/Shared/TodoApp/CounterScreen/CounterReducer.swift b/Examples/Todos/Shared/TodoApp/CounterScreen/CounterReducer.swift
new file mode 100644
index 0000000..11d83fe
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/CounterScreen/CounterReducer.swift
@@ -0,0 +1,21 @@
+import ComposableArchitecture
+import Foundation
+
+let CounterReducer = Reducer.combine(
+ Reducer { state, action, environment in
+ switch action {
+ case .viewOnAppear:
+ break
+ case .viewOnDisappear:
+ break
+ case .decrement:
+ state.count -= 1
+ case .increment:
+ state.count += 1
+ default:
+ break
+ }
+ return .none
+ }
+)
+ .debug()
diff --git a/Examples/Todos/Shared/TodoApp/CounterScreen/CounterState.swift b/Examples/Todos/Shared/TodoApp/CounterScreen/CounterState.swift
new file mode 100644
index 0000000..3010941
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/CounterScreen/CounterState.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct CounterState: Equatable {
+ var count: Int = 0
+}
diff --git a/Examples/Todos/Shared/TodoApp/CounterScreen/CounterView.swift b/Examples/Todos/Shared/TodoApp/CounterScreen/CounterView.swift
new file mode 100644
index 0000000..9021b3b
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/CounterScreen/CounterView.swift
@@ -0,0 +1,99 @@
+import ComposableArchitecture
+import SwiftUI
+
+struct CounterView: View {
+
+ private let store: Store
+
+ @ObservedObject
+ private var viewStore: ViewStore
+
+ init(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: CounterState(), reducer: CounterReducer, environment: CounterEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: CounterAction.init))
+ }
+
+ var body: some View {
+ ZStack {
+ HStack {
+ Button {
+ viewStore.send(.increment)
+ } label: {
+ Text("+")
+ }
+ Text("\(viewStore.count)")
+ Button {
+ viewStore.send(.decrement)
+ } label: {
+ Text("-")
+ }
+ }
+ }
+ .onAppear {
+ viewStore.send(.viewOnAppear)
+ }
+ .onDisappear {
+ viewStore.send(.viewOnDisappear)
+ }
+ }
+}
+
+struct CounterView_Previews: PreviewProvider {
+ static var previews: some View {
+ CounterView()
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+ var count: String = ""
+ init(state: CounterState) {
+ count = state.count.toString()
+ }
+}
+
+fileprivate enum ViewAction: Equatable {
+ case viewOnAppear
+ case viewOnDisappear
+ case none
+ case increment
+ case decrement
+ init(action: CounterAction) {
+ switch action {
+ case .viewOnAppear:
+ self = .viewOnAppear
+ case .viewOnDisappear:
+ self = .viewOnDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension CounterState {
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+
+ }
+ }
+}
+
+fileprivate extension CounterAction {
+ init(action: ViewAction) {
+ switch action {
+ case .viewOnAppear:
+ self = .viewOnAppear
+ case .viewOnDisappear:
+ self = .viewOnDisappear
+ case .decrement:
+ self = .decrement
+ case .increment:
+ self = .increment
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/Todos/Shared/TodoApp/MainScreen/MainAction.swift b/Examples/Todos/Shared/TodoApp/MainScreen/MainAction.swift
new file mode 100644
index 0000000..1042dfc
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/MainScreen/MainAction.swift
@@ -0,0 +1,21 @@
+import ComposableArchitecture
+import Foundation
+
+enum MainAction: BindableAction, Equatable {
+ case counterAction(CounterAction)
+ case viewOnAppear
+ case viewOnDisappear
+ case none
+ case binding(_ action: BindingAction)
+ case getTodo
+ case toggleTodo(Todo)
+ case responseTodo(Data)
+ case createTodo
+ case responseCreateTodo(Data)
+ case updateTodo(Todo)
+ case responseUpdateTodo(Data)
+ case deleteTodo(Todo)
+ case reponseDeleteTodo(Data)
+ case logout
+ case changeRootScreen(RootScreen)
+}
diff --git a/Examples/Todos/Shared/TodoApp/MainScreen/MainEnvironment.swift b/Examples/Todos/Shared/TodoApp/MainScreen/MainEnvironment.swift
new file mode 100644
index 0000000..19d428c
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/MainScreen/MainEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct MainEnvironment {
+
+}
diff --git a/Examples/Todos/Shared/TodoApp/MainScreen/MainReducer.swift b/Examples/Todos/Shared/TodoApp/MainScreen/MainReducer.swift
new file mode 100644
index 0000000..f8f395f
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/MainScreen/MainReducer.swift
@@ -0,0 +1,99 @@
+import ComposableArchitecture
+import Foundation
+import AnyRequest
+import ConvertSwift
+
+let MainReducer = Reducer.combine(
+ CounterReducer.pullback(state: \.counterState, action: /MainAction.counterAction, environment: { _ in
+ .init()
+ }),
+ Reducer { state, action, environment in
+ let urlString = "http://127.0.0.1:8080/todos"
+ switch action {
+ /// update state
+ case .binding(let bindingAction):
+ break
+ case .toggleTodo(let todo):
+ if var todo = state.todos.filter({$0 == todo}).first {
+ todo.isCompleted.toggle()
+ return Effect(value: MainAction.updateTodo(todo))
+ }
+ /// networking
+ case .getTodo:
+ state.isLoading = true
+ state.todos.removeAll()
+ let request = Request {
+ RMethod(.get)
+ RUrl(urlString: urlString)
+ }
+ return request
+ .delay(for: .seconds(1), scheduler: UIScheduler.shared) // fake loading
+ .compactMap {$0.data}
+ .map(MainAction.responseTodo).eraseToEffect()
+ case .responseTodo(let json):
+ state.isLoading = false
+ if let todos = json.toModel([Todo].self) {
+ for todo in todos {
+ state.todos.append(todo)
+ }
+ }
+ case .createTodo:
+ if state.title.isEmpty {
+ return .none
+ }
+ var title = state.title
+ state.title = ""
+ let todo = Todo(id: nil, title: title, isCompleted: false)
+ let request = Request {
+ RUrl(urlString: urlString)
+ REncoding(.json)
+ RMethod(.post)
+ Rbody(todo.toData())
+ }
+ return request
+ .compactMap {$0.data}
+ .map(MainAction.responseCreateTodo).eraseToEffect()
+ case .responseCreateTodo(let data):
+ if let todo = data.toModel(Todo.self) {
+ state.todos.append(todo)
+ }
+ case .updateTodo(let todo):
+ let request = Request {
+ REncoding(.json)
+ RUrl(urlString: urlString).withPath(todo.id)
+ RMethod(.post)
+ Rbody(todo.toData())
+ }
+ return request
+ .compactMap {$0.data}
+ .map(MainAction.responseUpdateTodo).eraseToEffect()
+ case .responseUpdateTodo(let data):
+ if let todo = data.toModel(Todo.self) {
+ state.todos.updateOrAppend(todo)
+ }
+ case .deleteTodo(let todo):
+ let request = Request {
+ RUrl(urlString: urlString).withPath(todo.id)
+ RMethod(.delete)
+ }
+ return request
+ .compactMap {$0.data}
+ .map(MainAction.reponseDeleteTodo).eraseToEffect()
+ case .reponseDeleteTodo(let data):
+ if let todo = data.toModel(Todo.self) {
+ state.todos.remove(todo)
+ }
+ case .viewOnAppear:
+ return Effect(value: MainAction.getTodo)
+ case .viewOnDisappear:
+ state = MainState()
+ case .logout:
+ return Effect(value: MainAction.changeRootScreen(.auth))
+ default:
+ break
+ }
+ return .none
+ }
+)
+ .binding()
+ .debug()
diff --git a/Examples/Todos/Shared/TodoApp/MainScreen/MainState.swift b/Examples/Todos/Shared/TodoApp/MainScreen/MainState.swift
new file mode 100644
index 0000000..07282d0
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/MainScreen/MainState.swift
@@ -0,0 +1,9 @@
+import ComposableArchitecture
+import Foundation
+
+struct MainState: Equatable {
+ var counterState = CounterState()
+ @BindableState var title: String = ""
+ var todos: IdentifiedArrayOf = IdentifiedArray()
+ var isLoading: Bool = false
+}
diff --git a/Examples/Todos/Shared/TodoApp/MainScreen/MainView.swift b/Examples/Todos/Shared/TodoApp/MainScreen/MainView.swift
new file mode 100644
index 0000000..b6cf5a3
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/MainScreen/MainView.swift
@@ -0,0 +1,193 @@
+import ComposableArchitecture
+import SwiftUI
+
+struct MainView: View {
+
+ private let store: Store
+
+ @ObservedObject
+ private var viewStore: ViewStore
+
+ init(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: MainState(), reducer: MainReducer, environment: MainEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: MainAction.init))
+ }
+
+ var body: some View {
+ ZStack {
+ List {
+ HStack {
+ Spacer()
+ Text(viewStore.isLoading ? "Loading" : "Reload")
+ .bold()
+ Spacer()
+ }
+ .onTapGesture {
+ viewStore.send(.getTodo)
+ }
+ HStack {
+ TextField("title", text: viewStore.binding(\.$title))
+ Button(action: {
+ viewStore.send(.createTodo)
+ }, label: {
+ Text("Create")
+ .bold()
+ .foregroundColor(viewStore.title.isEmpty ? Color.gray : Color.green)
+ })
+ .disabled(viewStore.title.isEmpty)
+ }
+
+ ForEach(viewStore.todos) { todo in
+ HStack {
+ HStack {
+ Image(systemName: todo.isCompleted ? "checkmark.square" : "square")
+ Text(todo.title)
+ .underline(todo.isCompleted, color: Color.black)
+ Spacer()
+ }
+ .contentShape(Rectangle())
+ .onTapGesture {
+ viewStore.send(.toggleTodo(todo))
+ }
+ Button(action: {
+ viewStore.send(.deleteTodo(todo))
+ }, label: {
+ Text("Delete")
+ .foregroundColor(Color.gray)
+ })
+ }
+ }
+#if os(iOS)
+ .listStyle(PlainListStyle())
+#else
+ .listStyle(PlainListStyle())
+#endif
+ .padding(.all, 0)
+ }
+ .padding(.all, 0)
+#if os(macOS)
+ .toolbar {
+ ToolbarItem(placement: .status) {
+ HStack {
+ CounterView(store: store.scope(state: \.counterState, action: MainAction.counterAction))
+ Spacer()
+ Button(action: {
+ viewStore.send(.logout)
+ }, label: {
+ Text("Logout")
+ .foregroundColor(Color.blue)
+ })
+ }
+ }
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+#endif
+#if os(iOS)
+ .navigationTitle("Todos")
+ .navigationViewStyle(.stack)
+ .navigationBarItems(leading: leadingBarItems, trailing: trailingBarItems)
+ .embedNavigationView()
+#endif
+ }
+ .onAppear {
+ viewStore.send(.viewOnAppear)
+ }
+ .onDisappear {
+ viewStore.send(.viewOnDisappear)
+ }
+ }
+}
+
+extension MainView {
+
+ private var leadingBarItems: some View {
+ CounterView(store: store.scope(state: \.counterState, action: MainAction.counterAction))
+ }
+
+ private var trailingBarItems: some View {
+ Button(action: {
+ viewStore.send(.logout)
+ }, label: {
+ Text("Logout")
+ .foregroundColor(Color.blue)
+ })
+ }
+}
+
+struct MainView_Previews: PreviewProvider {
+ static var previews: some View {
+ MainView()
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+ @BindableState var title: String = ""
+ var todos: IdentifiedArrayOf = []
+ var isLoading: Bool = false
+ init(state: MainState) {
+ self.title = state.title
+ self.todos = state.todos
+ self.isLoading = state.isLoading
+ }
+}
+
+fileprivate enum ViewAction: BindableAction, Equatable {
+ case binding(_ action: BindingAction)
+ case getTodo
+ case toggleTodo(Todo)
+ case createTodo
+ case updateTodo(Todo)
+ case deleteTodo(Todo)
+ case logout
+ case viewOnAppear
+ case viewOnDisappear
+ case none
+
+ init(action: MainAction) {
+ switch action {
+ case .viewOnAppear:
+ self = .viewOnAppear
+ case .viewOnDisappear:
+ self = .viewOnDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension MainState {
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+ self.title = newValue.title
+ }
+ }
+}
+
+fileprivate extension MainAction {
+ init(action: ViewAction) {
+ switch action {
+ case .viewOnAppear:
+ self = .viewOnAppear
+ case .viewOnDisappear:
+ self = .viewOnDisappear
+ case .binding(let bindingAction):
+ self = .binding(bindingAction.pullback(\.viewState))
+ case .getTodo:
+ self = .getTodo
+ case .toggleTodo(let todo):
+ self = .toggleTodo(todo)
+ case .createTodo:
+ self = .createTodo
+ case .deleteTodo(let todo):
+ self = .deleteTodo(todo)
+ case .logout:
+ self = .logout
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/Todos/Shared/TodoApp/Models/Todo.swift b/Examples/Todos/Shared/TodoApp/Models/Todo.swift
new file mode 100644
index 0000000..db47fe3
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/Models/Todo.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+struct Todo: Codable, Identifiable, Equatable {
+ var id: String?
+ var title: String
+ var isCompleted: Bool
+}
diff --git a/Examples/Todos/Shared/TodoApp/RootScreen/RootAction.swift b/Examples/Todos/Shared/TodoApp/RootScreen/RootAction.swift
new file mode 100644
index 0000000..e8f194e
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/RootScreen/RootAction.swift
@@ -0,0 +1,10 @@
+import ComposableArchitecture
+import Foundation
+
+enum RootAction: Equatable {
+ case authAction(AuthAction)
+ case mainAction(MainAction)
+ case viewOnAppear
+ case viewOnDisappear
+ case none
+}
diff --git a/Examples/Todos/Shared/TodoApp/RootScreen/RootEnvironment.swift b/Examples/Todos/Shared/TodoApp/RootScreen/RootEnvironment.swift
new file mode 100644
index 0000000..6b791ec
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/RootScreen/RootEnvironment.swift
@@ -0,0 +1,6 @@
+import ComposableArchitecture
+import Foundation
+
+struct RootEnvironment {
+
+}
diff --git a/Examples/Todos/Shared/TodoApp/RootScreen/RootReducer.swift b/Examples/Todos/Shared/TodoApp/RootScreen/RootReducer.swift
new file mode 100644
index 0000000..ed56b31
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/RootScreen/RootReducer.swift
@@ -0,0 +1,27 @@
+import ComposableArchitecture
+import Foundation
+
+let RootReducer = Reducer.combine(
+ MainReducer.pullback(state: \.mainState, action: /RootAction.mainAction, environment: { _ in
+ .init()
+ }),
+ AuthReducer.pullback(state: \.authState, action: /RootAction.authAction, environment: { _ in
+ .init()
+ }),
+ Reducer { state, action, environment in
+ switch action {
+ case .authAction(.changeRootScreen(let screen)):
+ state.rootScreen = screen
+ case .mainAction(.changeRootScreen(let screen)):
+ state.rootScreen = screen
+ case .viewOnAppear:
+ break
+ case .viewOnDisappear:
+ break
+ default:
+ break
+ }
+ return .none
+ }
+)
+ .debug()
diff --git a/Examples/Todos/Shared/TodoApp/RootScreen/RootState.swift b/Examples/Todos/Shared/TodoApp/RootScreen/RootState.swift
new file mode 100644
index 0000000..b0b3276
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/RootScreen/RootState.swift
@@ -0,0 +1,13 @@
+import ComposableArchitecture
+import Foundation
+
+struct RootState: Equatable {
+ var authState = AuthState()
+ var mainState = MainState()
+ var rootScreen: RootScreen = .main
+}
+
+enum RootScreen: Equatable {
+ case main
+ case auth
+}
diff --git a/Examples/Todos/Shared/TodoApp/RootScreen/RootView.swift b/Examples/Todos/Shared/TodoApp/RootScreen/RootView.swift
new file mode 100644
index 0000000..4b9acf3
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/RootScreen/RootView.swift
@@ -0,0 +1,90 @@
+import ComposableArchitecture
+import SwiftUI
+
+struct RootView: View {
+
+ private let store: Store
+
+ @ObservedObject
+ private var viewStore: ViewStore
+
+ init(store: Store? = nil) {
+ let unwrapStore = store ?? Store(initialState: RootState(), reducer: RootReducer, environment: RootEnvironment())
+ self.store = unwrapStore
+ self.viewStore = ViewStore(unwrapStore.scope(state: ViewState.init, action: RootAction.init))
+ }
+
+ var body: some View {
+ ZStack {
+ switch viewStore.rootScreen {
+ case .main:
+ MainView(store: store.scope(state: \.mainState, action: RootAction.mainAction))
+ case .auth:
+ AuthView(store: store.scope(state: \.authState, action: RootAction.authAction))
+ }
+ }
+ .onAppear {
+ viewStore.send(.viewOnAppear)
+ }
+ .onDisappear {
+ viewStore.send(.viewOnDisappear)
+ }
+ }
+}
+
+struct RootView_Previews: PreviewProvider {
+ static var previews: some View {
+ RootView()
+ }
+}
+
+fileprivate struct ViewState: Equatable {
+ var rootScreen: RootScreen = .auth
+ init(state: RootState) {
+ self.rootScreen = state.rootScreen
+ }
+}
+
+fileprivate enum ViewAction: Equatable {
+ case viewOnAppear
+ case viewOnDisappear
+ case none
+
+ init(action: RootAction) {
+ switch action {
+ case .viewOnAppear:
+ self = .viewOnAppear
+ case .viewOnDisappear:
+ self = .viewOnDisappear
+ default:
+ self = .none
+ }
+ }
+}
+
+fileprivate extension RootState {
+
+ var viewState: ViewState {
+ get {
+ ViewState(state: self)
+ }
+ set {
+
+ }
+ }
+
+}
+
+fileprivate extension RootAction {
+
+ init(action: ViewAction) {
+ switch action {
+ case .viewOnAppear:
+ self = .viewOnAppear
+ case .viewOnDisappear:
+ self = .viewOnDisappear
+ default:
+ self = .none
+ }
+ }
+}
diff --git a/Examples/Todos/Shared/TodoApp/View+/View+.swift b/Examples/Todos/Shared/TodoApp/View+/View+.swift
new file mode 100644
index 0000000..313f571
--- /dev/null
+++ b/Examples/Todos/Shared/TodoApp/View+/View+.swift
@@ -0,0 +1,9 @@
+import SwiftUI
+
+extension View {
+ func embedNavigationView() -> some View {
+ NavigationView {
+ self
+ }
+ }
+}
diff --git a/Examples/Todos/Shared/TodosApp.swift b/Examples/Todos/Shared/TodosApp.swift
new file mode 100644
index 0000000..117c539
--- /dev/null
+++ b/Examples/Todos/Shared/TodosApp.swift
@@ -0,0 +1,13 @@
+import SwiftUI
+
+@main
+struct TodosApp: App {
+ var body: some Scene {
+ WindowGroup {
+ RootView()
+#if os(macOS)
+ .frame(minWidth: 700, idealWidth: 700, maxWidth: .infinity, minHeight: 500, idealHeight: 500, maxHeight: .infinity, alignment: .center)
+#endif
+ }
+ }
+}
diff --git a/Examples/Todos/Todos.xcodeproj/project.pbxproj b/Examples/Todos/Todos.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..926855a
--- /dev/null
+++ b/Examples/Todos/Todos.xcodeproj/project.pbxproj
@@ -0,0 +1,750 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 55;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 8724584A270603C800A88871 /* TodosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8724583A270603C700A88871 /* TodosApp.swift */; };
+ 8724584B270603C800A88871 /* TodosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8724583A270603C700A88871 /* TodosApp.swift */; };
+ 8724584E270603C800A88871 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8724583C270603C800A88871 /* Assets.xcassets */; };
+ 8724584F270603C800A88871 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8724583C270603C800A88871 /* Assets.xcassets */; };
+ 875BCFB32710EAD50015E662 /* AnyRequest in Frameworks */ = {isa = PBXBuildFile; productRef = 875BCFB22710EAD50015E662 /* AnyRequest */; };
+ 875BCFB62710EAEF0015E662 /* ConvertSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 875BCFB52710EAEF0015E662 /* ConvertSwift */; };
+ 875BCFB92710EB0C0015E662 /* Json in Frameworks */ = {isa = PBXBuildFile; productRef = 875BCFB82710EB0C0015E662 /* Json */; };
+ 875BCFBB2710ED8E0015E662 /* AnyRequest in Frameworks */ = {isa = PBXBuildFile; productRef = 875BCFBA2710ED8E0015E662 /* AnyRequest */; };
+ 875BCFBD2710ED8E0015E662 /* ConvertSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 875BCFBC2710ED8E0015E662 /* ConvertSwift */; };
+ 875BCFBF2710ED8E0015E662 /* Json in Frameworks */ = {isa = PBXBuildFile; productRef = 875BCFBE2710ED8E0015E662 /* Json */; };
+ 8769538A2706078100EB9008 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 876953892706078100EB9008 /* ComposableArchitecture */; };
+ 8769538C2706078600EB9008 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 8769538B2706078600EB9008 /* ComposableArchitecture */; };
+ 878AF95927110B020066E71C /* CounterAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF95427110B020066E71C /* CounterAction.swift */; };
+ 878AF95A27110B020066E71C /* CounterAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF95427110B020066E71C /* CounterAction.swift */; };
+ 878AF95B27110B020066E71C /* CounterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF95527110B020066E71C /* CounterView.swift */; };
+ 878AF95C27110B020066E71C /* CounterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF95527110B020066E71C /* CounterView.swift */; };
+ 878AF95D27110B020066E71C /* CounterEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF95627110B020066E71C /* CounterEnvironment.swift */; };
+ 878AF95E27110B020066E71C /* CounterEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF95627110B020066E71C /* CounterEnvironment.swift */; };
+ 878AF95F27110B020066E71C /* CounterReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF95727110B020066E71C /* CounterReducer.swift */; };
+ 878AF96027110B020066E71C /* CounterReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF95727110B020066E71C /* CounterReducer.swift */; };
+ 878AF96127110B020066E71C /* CounterState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF95827110B020066E71C /* CounterState.swift */; };
+ 878AF96227110B020066E71C /* CounterState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF95827110B020066E71C /* CounterState.swift */; };
+ 878AF96527110B4C0066E71C /* View+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF96427110B4C0066E71C /* View+.swift */; };
+ 878AF96627110B4C0066E71C /* View+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878AF96427110B4C0066E71C /* View+.swift */; };
+ 87A8E14E27065E9500E2D797 /* MainReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A8E14927065E9500E2D797 /* MainReducer.swift */; };
+ 87A8E14F27065E9500E2D797 /* MainReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A8E14927065E9500E2D797 /* MainReducer.swift */; };
+ 87A8E15027065E9500E2D797 /* MainEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A8E14A27065E9500E2D797 /* MainEnvironment.swift */; };
+ 87A8E15127065E9500E2D797 /* MainEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A8E14A27065E9500E2D797 /* MainEnvironment.swift */; };
+ 87A8E15227065E9500E2D797 /* MainState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A8E14B27065E9500E2D797 /* MainState.swift */; };
+ 87A8E15327065E9500E2D797 /* MainState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A8E14B27065E9500E2D797 /* MainState.swift */; };
+ 87A8E15427065E9500E2D797 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A8E14C27065E9500E2D797 /* MainView.swift */; };
+ 87A8E15527065E9500E2D797 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A8E14C27065E9500E2D797 /* MainView.swift */; };
+ 87A8E15627065E9500E2D797 /* MainAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A8E14D27065E9500E2D797 /* MainAction.swift */; };
+ 87A8E15727065E9500E2D797 /* MainAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A8E14D27065E9500E2D797 /* MainAction.swift */; };
+ 87CB58D727064CF50060BF75 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58C427064CF40060BF75 /* Todo.swift */; };
+ 87CB58D827064CF50060BF75 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58C427064CF40060BF75 /* Todo.swift */; };
+ 87CB58E327064CF50060BF75 /* AuthState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58CC27064CF40060BF75 /* AuthState.swift */; };
+ 87CB58E427064CF50060BF75 /* AuthState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58CC27064CF40060BF75 /* AuthState.swift */; };
+ 87CB58E527064CF50060BF75 /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58CD27064CF40060BF75 /* AuthView.swift */; };
+ 87CB58E627064CF50060BF75 /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58CD27064CF40060BF75 /* AuthView.swift */; };
+ 87CB58E727064CF50060BF75 /* AuthReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58CE27064CF40060BF75 /* AuthReducer.swift */; };
+ 87CB58E827064CF50060BF75 /* AuthReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58CE27064CF40060BF75 /* AuthReducer.swift */; };
+ 87CB58E927064CF50060BF75 /* AuthAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58CF27064CF40060BF75 /* AuthAction.swift */; };
+ 87CB58EA27064CF50060BF75 /* AuthAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58CF27064CF40060BF75 /* AuthAction.swift */; };
+ 87CB58EB27064CF50060BF75 /* AuthEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D027064CF40060BF75 /* AuthEnvironment.swift */; };
+ 87CB58EC27064CF50060BF75 /* AuthEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D027064CF40060BF75 /* AuthEnvironment.swift */; };
+ 87CB58ED27064CF50060BF75 /* RootAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D227064CF40060BF75 /* RootAction.swift */; };
+ 87CB58EE27064CF50060BF75 /* RootAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D227064CF40060BF75 /* RootAction.swift */; };
+ 87CB58EF27064CF50060BF75 /* RootEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D327064CF50060BF75 /* RootEnvironment.swift */; };
+ 87CB58F027064CF50060BF75 /* RootEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D327064CF50060BF75 /* RootEnvironment.swift */; };
+ 87CB58F127064CF50060BF75 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D427064CF50060BF75 /* RootView.swift */; };
+ 87CB58F227064CF50060BF75 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D427064CF50060BF75 /* RootView.swift */; };
+ 87CB58F327064CF50060BF75 /* RootState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D527064CF50060BF75 /* RootState.swift */; };
+ 87CB58F427064CF50060BF75 /* RootState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D527064CF50060BF75 /* RootState.swift */; };
+ 87CB58F527064CF50060BF75 /* RootReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D627064CF50060BF75 /* RootReducer.swift */; };
+ 87CB58F627064CF50060BF75 /* RootReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CB58D627064CF50060BF75 /* RootReducer.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 8724583A270603C700A88871 /* TodosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodosApp.swift; sourceTree = ""; };
+ 8724583C270603C800A88871 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 87245841270603C800A88871 /* Todos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Todos.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 87245847270603C800A88871 /* Todos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Todos.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 87245849270603C800A88871 /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; };
+ 878AF95427110B020066E71C /* CounterAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterAction.swift; sourceTree = ""; };
+ 878AF95527110B020066E71C /* CounterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterView.swift; sourceTree = ""; };
+ 878AF95627110B020066E71C /* CounterEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterEnvironment.swift; sourceTree = ""; };
+ 878AF95727110B020066E71C /* CounterReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterReducer.swift; sourceTree = ""; };
+ 878AF95827110B020066E71C /* CounterState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterState.swift; sourceTree = ""; };
+ 878AF96427110B4C0066E71C /* View+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+.swift"; sourceTree = ""; };
+ 87A8E14927065E9500E2D797 /* MainReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainReducer.swift; sourceTree = ""; };
+ 87A8E14A27065E9500E2D797 /* MainEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainEnvironment.swift; sourceTree = ""; };
+ 87A8E14B27065E9500E2D797 /* MainState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainState.swift; sourceTree = ""; };
+ 87A8E14C27065E9500E2D797 /* MainView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; };
+ 87A8E14D27065E9500E2D797 /* MainAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainAction.swift; sourceTree = ""; };
+ 87CB58C427064CF40060BF75 /* Todo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; };
+ 87CB58CC27064CF40060BF75 /* AuthState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthState.swift; sourceTree = ""; };
+ 87CB58CD27064CF40060BF75 /* AuthView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthView.swift; sourceTree = ""; };
+ 87CB58CE27064CF40060BF75 /* AuthReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthReducer.swift; sourceTree = ""; };
+ 87CB58CF27064CF40060BF75 /* AuthAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthAction.swift; sourceTree = ""; };
+ 87CB58D027064CF40060BF75 /* AuthEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthEnvironment.swift; sourceTree = ""; };
+ 87CB58D227064CF40060BF75 /* RootAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootAction.swift; sourceTree = ""; };
+ 87CB58D327064CF50060BF75 /* RootEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootEnvironment.swift; sourceTree = ""; };
+ 87CB58D427064CF50060BF75 /* RootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; };
+ 87CB58D527064CF50060BF75 /* RootState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootState.swift; sourceTree = ""; };
+ 87CB58D627064CF50060BF75 /* RootReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootReducer.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 8724583E270603C800A88871 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8769538A2706078100EB9008 /* ComposableArchitecture in Frameworks */,
+ 875BCFB32710EAD50015E662 /* AnyRequest in Frameworks */,
+ 875BCFB92710EB0C0015E662 /* Json in Frameworks */,
+ 875BCFB62710EAEF0015E662 /* ConvertSwift in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 87245844270603C800A88871 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8769538C2706078600EB9008 /* ComposableArchitecture in Frameworks */,
+ 875BCFBB2710ED8E0015E662 /* AnyRequest in Frameworks */,
+ 875BCFBF2710ED8E0015E662 /* Json in Frameworks */,
+ 875BCFBD2710ED8E0015E662 /* ConvertSwift in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 87245834270603C600A88871 = {
+ isa = PBXGroup;
+ children = (
+ 87245839270603C700A88871 /* Shared */,
+ 87245848270603C800A88871 /* macOS */,
+ 87245842270603C800A88871 /* Products */,
+ 876953882706078100EB9008 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 87245839270603C700A88871 /* Shared */ = {
+ isa = PBXGroup;
+ children = (
+ 8724583A270603C700A88871 /* TodosApp.swift */,
+ 878AF92E2710F90C0066E71C /* TodoApp */,
+ 8724583C270603C800A88871 /* Assets.xcassets */,
+ );
+ path = Shared;
+ sourceTree = "";
+ };
+ 87245842270603C800A88871 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 87245841270603C800A88871 /* Todos.app */,
+ 87245847270603C800A88871 /* Todos.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 87245848270603C800A88871 /* macOS */ = {
+ isa = PBXGroup;
+ children = (
+ 87245849270603C800A88871 /* macOS.entitlements */,
+ );
+ path = macOS;
+ sourceTree = "";
+ };
+ 876953882706078100EB9008 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 878AF92E2710F90C0066E71C /* TodoApp */ = {
+ isa = PBXGroup;
+ children = (
+ 878AF96327110B3D0066E71C /* View+ */,
+ 87CB58C327064CF40060BF75 /* Models */,
+ 878AF95327110AEE0066E71C /* CounterScreen */,
+ 87A8E14827065E9500E2D797 /* MainScreen */,
+ 87CB58CB27064CF40060BF75 /* AuthScreen */,
+ 87CB58D127064CF40060BF75 /* RootScreen */,
+ );
+ path = TodoApp;
+ sourceTree = "";
+ };
+ 878AF95327110AEE0066E71C /* CounterScreen */ = {
+ isa = PBXGroup;
+ children = (
+ 878AF95527110B020066E71C /* CounterView.swift */,
+ 878AF95827110B020066E71C /* CounterState.swift */,
+ 878AF95427110B020066E71C /* CounterAction.swift */,
+ 878AF95627110B020066E71C /* CounterEnvironment.swift */,
+ 878AF95727110B020066E71C /* CounterReducer.swift */,
+ );
+ path = CounterScreen;
+ sourceTree = "";
+ };
+ 878AF96327110B3D0066E71C /* View+ */ = {
+ isa = PBXGroup;
+ children = (
+ 878AF96427110B4C0066E71C /* View+.swift */,
+ );
+ path = "View+";
+ sourceTree = "";
+ };
+ 87A8E14827065E9500E2D797 /* MainScreen */ = {
+ isa = PBXGroup;
+ children = (
+ 87A8E14C27065E9500E2D797 /* MainView.swift */,
+ 87A8E14B27065E9500E2D797 /* MainState.swift */,
+ 87A8E14D27065E9500E2D797 /* MainAction.swift */,
+ 87A8E14A27065E9500E2D797 /* MainEnvironment.swift */,
+ 87A8E14927065E9500E2D797 /* MainReducer.swift */,
+ );
+ path = MainScreen;
+ sourceTree = "";
+ };
+ 87CB58C327064CF40060BF75 /* Models */ = {
+ isa = PBXGroup;
+ children = (
+ 87CB58C427064CF40060BF75 /* Todo.swift */,
+ );
+ path = Models;
+ sourceTree = "";
+ };
+ 87CB58CB27064CF40060BF75 /* AuthScreen */ = {
+ isa = PBXGroup;
+ children = (
+ 87CB58CD27064CF40060BF75 /* AuthView.swift */,
+ 87CB58CC27064CF40060BF75 /* AuthState.swift */,
+ 87CB58CF27064CF40060BF75 /* AuthAction.swift */,
+ 87CB58D027064CF40060BF75 /* AuthEnvironment.swift */,
+ 87CB58CE27064CF40060BF75 /* AuthReducer.swift */,
+ );
+ path = AuthScreen;
+ sourceTree = "";
+ };
+ 87CB58D127064CF40060BF75 /* RootScreen */ = {
+ isa = PBXGroup;
+ children = (
+ 87CB58D427064CF50060BF75 /* RootView.swift */,
+ 87CB58D527064CF50060BF75 /* RootState.swift */,
+ 87CB58D227064CF40060BF75 /* RootAction.swift */,
+ 87CB58D327064CF50060BF75 /* RootEnvironment.swift */,
+ 87CB58D627064CF50060BF75 /* RootReducer.swift */,
+ );
+ path = RootScreen;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 87245840270603C800A88871 /* Todos (iOS) */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 87245852270603C800A88871 /* Build configuration list for PBXNativeTarget "Todos (iOS)" */;
+ buildPhases = (
+ 8724583D270603C800A88871 /* Sources */,
+ 8724583E270603C800A88871 /* Frameworks */,
+ 8724583F270603C800A88871 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Todos (iOS)";
+ packageProductDependencies = (
+ 876953892706078100EB9008 /* ComposableArchitecture */,
+ 875BCFB22710EAD50015E662 /* AnyRequest */,
+ 875BCFB52710EAEF0015E662 /* ConvertSwift */,
+ 875BCFB82710EB0C0015E662 /* Json */,
+ );
+ productName = "Todos (iOS)";
+ productReference = 87245841270603C800A88871 /* Todos.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 87245846270603C800A88871 /* Todos (macOS) */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 87245855270603C800A88871 /* Build configuration list for PBXNativeTarget "Todos (macOS)" */;
+ buildPhases = (
+ 87245843270603C800A88871 /* Sources */,
+ 87245844270603C800A88871 /* Frameworks */,
+ 87245845270603C800A88871 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Todos (macOS)";
+ packageProductDependencies = (
+ 8769538B2706078600EB9008 /* ComposableArchitecture */,
+ 875BCFBA2710ED8E0015E662 /* AnyRequest */,
+ 875BCFBC2710ED8E0015E662 /* ConvertSwift */,
+ 875BCFBE2710ED8E0015E662 /* Json */,
+ );
+ productName = "Todos (macOS)";
+ productReference = 87245847270603C800A88871 /* Todos.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 87245835270603C600A88871 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1300;
+ LastUpgradeCheck = 1300;
+ TargetAttributes = {
+ 87245840270603C800A88871 = {
+ CreatedOnToolsVersion = 13.0;
+ };
+ 87245846270603C800A88871 = {
+ CreatedOnToolsVersion = 13.0;
+ };
+ };
+ };
+ buildConfigurationList = 87245838270603C600A88871 /* Build configuration list for PBXProject "Todos" */;
+ compatibilityVersion = "Xcode 13.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 87245834270603C600A88871;
+ packageReferences = (
+ 875BCFB12710EAD50015E662 /* XCRemoteSwiftPackageReference "AnyRequest" */,
+ 875BCFB42710EAEF0015E662 /* XCRemoteSwiftPackageReference "SwiftConvert" */,
+ 875BCFB72710EB0C0015E662 /* XCRemoteSwiftPackageReference "Json" */,
+ );
+ productRefGroup = 87245842270603C800A88871 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 87245840270603C800A88871 /* Todos (iOS) */,
+ 87245846270603C800A88871 /* Todos (macOS) */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 8724583F270603C800A88871 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8724584E270603C800A88871 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 87245845270603C800A88871 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8724584F270603C800A88871 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 8724583D270603C800A88871 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 878AF95D27110B020066E71C /* CounterEnvironment.swift in Sources */,
+ 87A8E15627065E9500E2D797 /* MainAction.swift in Sources */,
+ 878AF96127110B020066E71C /* CounterState.swift in Sources */,
+ 87CB58E527064CF50060BF75 /* AuthView.swift in Sources */,
+ 878AF96527110B4C0066E71C /* View+.swift in Sources */,
+ 87A8E15227065E9500E2D797 /* MainState.swift in Sources */,
+ 87A8E14E27065E9500E2D797 /* MainReducer.swift in Sources */,
+ 87CB58D727064CF50060BF75 /* Todo.swift in Sources */,
+ 87CB58F327064CF50060BF75 /* RootState.swift in Sources */,
+ 87CB58F127064CF50060BF75 /* RootView.swift in Sources */,
+ 87CB58E727064CF50060BF75 /* AuthReducer.swift in Sources */,
+ 878AF95B27110B020066E71C /* CounterView.swift in Sources */,
+ 87CB58ED27064CF50060BF75 /* RootAction.swift in Sources */,
+ 87CB58F527064CF50060BF75 /* RootReducer.swift in Sources */,
+ 87A8E15427065E9500E2D797 /* MainView.swift in Sources */,
+ 87CB58EB27064CF50060BF75 /* AuthEnvironment.swift in Sources */,
+ 878AF95927110B020066E71C /* CounterAction.swift in Sources */,
+ 87CB58EF27064CF50060BF75 /* RootEnvironment.swift in Sources */,
+ 87A8E15027065E9500E2D797 /* MainEnvironment.swift in Sources */,
+ 87CB58E927064CF50060BF75 /* AuthAction.swift in Sources */,
+ 8724584A270603C800A88871 /* TodosApp.swift in Sources */,
+ 878AF95F27110B020066E71C /* CounterReducer.swift in Sources */,
+ 87CB58E327064CF50060BF75 /* AuthState.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 87245843270603C800A88871 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 878AF95E27110B020066E71C /* CounterEnvironment.swift in Sources */,
+ 87A8E15727065E9500E2D797 /* MainAction.swift in Sources */,
+ 878AF96227110B020066E71C /* CounterState.swift in Sources */,
+ 87CB58E627064CF50060BF75 /* AuthView.swift in Sources */,
+ 878AF96627110B4C0066E71C /* View+.swift in Sources */,
+ 87A8E15327065E9500E2D797 /* MainState.swift in Sources */,
+ 87A8E14F27065E9500E2D797 /* MainReducer.swift in Sources */,
+ 87CB58D827064CF50060BF75 /* Todo.swift in Sources */,
+ 87CB58F427064CF50060BF75 /* RootState.swift in Sources */,
+ 87CB58F227064CF50060BF75 /* RootView.swift in Sources */,
+ 87CB58E827064CF50060BF75 /* AuthReducer.swift in Sources */,
+ 878AF95C27110B020066E71C /* CounterView.swift in Sources */,
+ 87CB58EE27064CF50060BF75 /* RootAction.swift in Sources */,
+ 87CB58F627064CF50060BF75 /* RootReducer.swift in Sources */,
+ 87A8E15527065E9500E2D797 /* MainView.swift in Sources */,
+ 87CB58EC27064CF50060BF75 /* AuthEnvironment.swift in Sources */,
+ 878AF95A27110B020066E71C /* CounterAction.swift in Sources */,
+ 87CB58F027064CF50060BF75 /* RootEnvironment.swift in Sources */,
+ 87A8E15127065E9500E2D797 /* MainEnvironment.swift in Sources */,
+ 87CB58EA27064CF50060BF75 /* AuthAction.swift in Sources */,
+ 8724584B270603C800A88871 /* TodosApp.swift in Sources */,
+ 878AF96027110B020066E71C /* CounterReducer.swift in Sources */,
+ 87CB58E427064CF50060BF75 /* AuthState.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 87245850270603C800A88871 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 87245851270603C800A88871 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ };
+ name = Release;
+ };
+ 87245853270603C800A88871 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 2H5PN3F9B6;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = mike.fullstack.swift.Todos;
+ PRODUCT_NAME = Todos;
+ SDKROOT = iphoneos;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 87245854270603C800A88871 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 2H5PN3F9B6;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = mike.fullstack.swift.Todos;
+ PRODUCT_NAME = Todos;
+ SDKROOT = iphoneos;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 87245856270603C800A88871 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements;
+ CODE_SIGN_IDENTITY = "-";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 2H5PN3F9B6;
+ ENABLE_HARDENED_RUNTIME = YES;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = mike.fullstack.swift.Todos;
+ PRODUCT_NAME = Todos;
+ SDKROOT = macosx;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 87245857270603C800A88871 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements;
+ CODE_SIGN_IDENTITY = "-";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 2H5PN3F9B6;
+ ENABLE_HARDENED_RUNTIME = YES;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = mike.fullstack.swift.Todos;
+ PRODUCT_NAME = Todos;
+ SDKROOT = macosx;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 87245838270603C600A88871 /* Build configuration list for PBXProject "Todos" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 87245850270603C800A88871 /* Debug */,
+ 87245851270603C800A88871 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 87245852270603C800A88871 /* Build configuration list for PBXNativeTarget "Todos (iOS)" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 87245853270603C800A88871 /* Debug */,
+ 87245854270603C800A88871 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 87245855270603C800A88871 /* Build configuration list for PBXNativeTarget "Todos (macOS)" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 87245856270603C800A88871 /* Debug */,
+ 87245857270603C800A88871 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ 875BCFB12710EAD50015E662 /* XCRemoteSwiftPackageReference "AnyRequest" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/FullStack-Swift/AnyRequest";
+ requirement = {
+ branch = main;
+ kind = branch;
+ };
+ };
+ 875BCFB42710EAEF0015E662 /* XCRemoteSwiftPackageReference "SwiftConvert" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/FullStack-Swift/SwiftConvert";
+ requirement = {
+ branch = main;
+ kind = branch;
+ };
+ };
+ 875BCFB72710EB0C0015E662 /* XCRemoteSwiftPackageReference "Json" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/FullStack-Swift/Json";
+ requirement = {
+ branch = main;
+ kind = branch;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 875BCFB22710EAD50015E662 /* AnyRequest */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 875BCFB12710EAD50015E662 /* XCRemoteSwiftPackageReference "AnyRequest" */;
+ productName = AnyRequest;
+ };
+ 875BCFB52710EAEF0015E662 /* ConvertSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 875BCFB42710EAEF0015E662 /* XCRemoteSwiftPackageReference "SwiftConvert" */;
+ productName = ConvertSwift;
+ };
+ 875BCFB82710EB0C0015E662 /* Json */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 875BCFB72710EB0C0015E662 /* XCRemoteSwiftPackageReference "Json" */;
+ productName = Json;
+ };
+ 875BCFBA2710ED8E0015E662 /* AnyRequest */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 875BCFB12710EAD50015E662 /* XCRemoteSwiftPackageReference "AnyRequest" */;
+ productName = AnyRequest;
+ };
+ 875BCFBC2710ED8E0015E662 /* ConvertSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 875BCFB42710EAEF0015E662 /* XCRemoteSwiftPackageReference "SwiftConvert" */;
+ productName = ConvertSwift;
+ };
+ 875BCFBE2710ED8E0015E662 /* Json */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 875BCFB72710EB0C0015E662 /* XCRemoteSwiftPackageReference "Json" */;
+ productName = Json;
+ };
+ 876953892706078100EB9008 /* ComposableArchitecture */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = ComposableArchitecture;
+ };
+ 8769538B2706078600EB9008 /* ComposableArchitecture */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = ComposableArchitecture;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 87245835270603C600A88871 /* Project object */;
+}
diff --git a/Examples/Todos/Todos.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/Todos/Todos.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/Examples/Todos/Todos.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Examples/Todos/Todos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/Todos/Todos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/Examples/Todos/Todos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Examples/Todos/Todos.xcodeproj/xcshareddata/xcschemes/Todos (iOS).xcscheme b/Examples/Todos/Todos.xcodeproj/xcshareddata/xcschemes/Todos (iOS).xcscheme
new file mode 100644
index 0000000..80a7221
--- /dev/null
+++ b/Examples/Todos/Todos.xcodeproj/xcshareddata/xcschemes/Todos (iOS).xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Todos/Todos.xcodeproj/xcshareddata/xcschemes/Todos (macOS).xcscheme b/Examples/Todos/Todos.xcodeproj/xcshareddata/xcschemes/Todos (macOS).xcscheme
new file mode 100644
index 0000000..caa2a17
--- /dev/null
+++ b/Examples/Todos/Todos.xcodeproj/xcshareddata/xcschemes/Todos (macOS).xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Todos/macOS/macOS.entitlements b/Examples/Todos/macOS/macOS.entitlements
new file mode 100644
index 0000000..40b639e
--- /dev/null
+++ b/Examples/Todos/macOS/macOS.entitlements
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.files.user-selected.read-only
+
+ com.apple.security.network.client
+
+ com.apple.security.network.server
+
+
+
diff --git a/Package.resolved b/Package.resolved
new file mode 100644
index 0000000..4b1173d
--- /dev/null
+++ b/Package.resolved
@@ -0,0 +1,88 @@
+{
+ "object": {
+ "pins": [
+ {
+ "package": "combine-schedulers",
+ "repositoryURL": "https://github.com/pointfreeco/combine-schedulers",
+ "state": {
+ "branch": null,
+ "revision": "4cf088c29a20f52be0f2ca54992b492c54e0076b",
+ "version": "0.5.3"
+ }
+ },
+ {
+ "package": "ReactiveSwift",
+ "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift.git",
+ "state": {
+ "branch": null,
+ "revision": "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c",
+ "version": "6.7.0"
+ }
+ },
+ {
+ "package": "swift-argument-parser",
+ "repositoryURL": "https://github.com/apple/swift-argument-parser",
+ "state": {
+ "branch": null,
+ "revision": "6b2aa2748a7881eebb9f84fb10c01293e15b52ca",
+ "version": "0.5.0"
+ }
+ },
+ {
+ "package": "Benchmark",
+ "repositoryURL": "https://github.com/google/swift-benchmark",
+ "state": {
+ "branch": null,
+ "revision": "a0564bf88df5f94eec81348a2f089494c6b28d80",
+ "version": "0.1.1"
+ }
+ },
+ {
+ "package": "swift-case-paths",
+ "repositoryURL": "https://github.com/pointfreeco/swift-case-paths",
+ "state": {
+ "branch": null,
+ "revision": "d226d167bd4a68b51e352af5655c92bce8ee0463",
+ "version": "0.7.0"
+ }
+ },
+ {
+ "package": "swift-collections",
+ "repositoryURL": "https://github.com/apple/swift-collections",
+ "state": {
+ "branch": null,
+ "revision": "2d33a0ea89c961dcb2b3da2157963d9c0370347e",
+ "version": "1.0.1"
+ }
+ },
+ {
+ "package": "swift-custom-dump",
+ "repositoryURL": "https://github.com/pointfreeco/swift-custom-dump",
+ "state": {
+ "branch": null,
+ "revision": "c2dd2c64b753dda592f5619303e02f741cd3e862",
+ "version": "0.2.0"
+ }
+ },
+ {
+ "package": "swift-identified-collections",
+ "repositoryURL": "https://github.com/pointfreeco/swift-identified-collections",
+ "state": {
+ "branch": null,
+ "revision": "c8e6a40209650ab619853cd4ce89a0aa51792754",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "package": "xctest-dynamic-overlay",
+ "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay",
+ "state": {
+ "branch": null,
+ "revision": "50a70a9d3583fe228ce672e8923010c8df2deddd",
+ "version": "0.2.1"
+ }
+ }
+ ]
+ },
+ "version": 1
+}
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..ecff37f
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,50 @@
+// swift-tools-version:5.5
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "reactiveswift-composable-architecture",
+ platforms: [
+ .iOS(.v13),
+ .macOS(.v10_15),
+ .tvOS(.v13),
+ .watchOS(.v6),
+ ],
+ products: [
+ .library(
+ name: "ComposableArchitecture",
+ targets: ["ComposableArchitecture"]),
+ ],
+ dependencies: [
+ .package(url: "https://github.com/ReactiveCocoa/ReactiveSwift.git", from: "6.7.0"),
+ .package(name: "Benchmark", url: "https://github.com/google/swift-benchmark", from: "0.1.0"),
+ .package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.5.0"),
+ .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.4.0"),
+ .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "0.1.0"),
+ .package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "0.1.0"),
+ .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.2.0"),
+ ],
+ targets: [
+ .target(
+ name: "ComposableArchitecture",
+ dependencies: [
+ "ReactiveSwift",
+ .product(name: "CasePaths", package: "swift-case-paths"),
+ .product(name: "CombineSchedulers", package: "combine-schedulers"),
+ .product(name: "CustomDump", package: "swift-custom-dump"),
+ .product(name: "IdentifiedCollections", package: "swift-identified-collections"),
+ .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
+ ]),
+ .testTarget(
+ name: "ComposableArchitectureTests",
+ dependencies: ["ComposableArchitecture"]),
+ .executableTarget(
+ name: "swift-composable-architecture-benchmark",
+ dependencies: [
+ "ComposableArchitecture",
+ .product(name: "Benchmark", package: "Benchmark"),
+ ]
+ ),
+ ]
+)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..23b06b4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+# reactiveswift-composable-architecture
+
+A description of this package.
+
+## Combine
+
+https://github.com/pointfreeco/swift-composable-architecture
+
+## RxSwift
+
+https://github.com/FullStack-Swift/rxswift-composable-architecture
+
+## ReactiveSwift
+
+https://github.com/FullStack-Swift/reactiveswift-composable-architecture
+
+## Example
+
+https://github.com/FullStack-Swift/TodoFullStackSwift
diff --git a/Sources/ComposableArchitecture/Beta/Combine+ReactiveSwift.swift b/Sources/ComposableArchitecture/Beta/Combine+ReactiveSwift.swift
new file mode 100644
index 0000000..6e9d68d
--- /dev/null
+++ b/Sources/ComposableArchitecture/Beta/Combine+ReactiveSwift.swift
@@ -0,0 +1,28 @@
+#if canImport(Combine)
+import Combine
+import ReactiveSwift
+
+@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
+public extension Publisher {
+ func eraseToEffect() -> Effect