├── DataFlow.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── sarah.xcuserdatad │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist └── xcuserdata │ └── sarah.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── DataFlow ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── ContentView.swift ├── Environment │ ├── ChildView.swift │ ├── CustomModifiers.swift │ ├── GrandChildView.swift │ ├── NestingViews.swift │ └── UserSettings.swift ├── Info.plist ├── Observable 1 │ ├── ColorChooser.swift │ ├── ColorSet.swift │ └── ColorSetView.swift ├── Observable 2 │ ├── PersonDetailView.swift │ ├── PersonListModel.swift │ └── PersonListView.swift ├── Preview Content │ ├── PersonVMTestData.swift │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Property.swift ├── SceneDelegate.swift ├── State & Binding 1 │ └── Numbers.swift ├── State & Binding 2 │ ├── Pizza.swift │ └── PizzaView.swift └── UsingState.swift ├── README.md └── assets └── ContentView.png /DataFlow.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2628DB08232DE93000049FFD /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2628DB07232DE93000049FFD /* UserSettings.swift */; }; 11 | 2628DB0A232DE94D00049FFD /* CustomModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2628DB09232DE94D00049FFD /* CustomModifiers.swift */; }; 12 | 263DF0AD232C7F3E007635A4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263DF0AC232C7F3E007635A4 /* AppDelegate.swift */; }; 13 | 263DF0AF232C7F3E007635A4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263DF0AE232C7F3E007635A4 /* SceneDelegate.swift */; }; 14 | 263DF0B1232C7F3E007635A4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263DF0B0232C7F3E007635A4 /* ContentView.swift */; }; 15 | 263DF0B3232C7F41007635A4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 263DF0B2232C7F41007635A4 /* Assets.xcassets */; }; 16 | 263DF0B6232C7F41007635A4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 263DF0B5232C7F41007635A4 /* Preview Assets.xcassets */; }; 17 | 263DF0B9232C7F41007635A4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 263DF0B7232C7F41007635A4 /* LaunchScreen.storyboard */; }; 18 | 263DF0C1232C991E007635A4 /* PizzaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263DF0C0232C991E007635A4 /* PizzaView.swift */; }; 19 | 263DF0C3232C994B007635A4 /* Pizza.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263DF0C2232C994B007635A4 /* Pizza.swift */; }; 20 | 264663C2232EFC4200368113 /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264663C1232EFC4200368113 /* Property.swift */; }; 21 | 264663C4232EFD5300368113 /* UsingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264663C3232EFD5300368113 /* UsingState.swift */; }; 22 | 264663C7232EFF3B00368113 /* Numbers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264663C6232EFF3B00368113 /* Numbers.swift */; }; 23 | 264C426D232CD3EE00058CE0 /* PersonListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264C426C232CD3EE00058CE0 /* PersonListModel.swift */; }; 24 | 264C426F232CD57D00058CE0 /* PersonListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264C426E232CD57D00058CE0 /* PersonListView.swift */; }; 25 | 264C4271232CD87900058CE0 /* PersonDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264C4270232CD87900058CE0 /* PersonDetailView.swift */; }; 26 | 26834F892337544E0013D5DD /* PersonVMTestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26834F882337544E0013D5DD /* PersonVMTestData.swift */; }; 27 | 26BDC7DD232DD34200C0A2D9 /* NestingViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BDC7DC232DD34200C0A2D9 /* NestingViews.swift */; }; 28 | 26BDC7DF232DE63900C0A2D9 /* ChildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BDC7DE232DE63900C0A2D9 /* ChildView.swift */; }; 29 | 26BDC7E1232DE6CE00C0A2D9 /* GrandChildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BDC7E0232DE6CE00C0A2D9 /* GrandChildView.swift */; }; 30 | 26E47A642330988800186B82 /* ColorSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26E47A632330988800186B82 /* ColorSet.swift */; }; 31 | 26E47A6623309A0E00186B82 /* ColorSetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26E47A6523309A0E00186B82 /* ColorSetView.swift */; }; 32 | 26E47A6823309BDB00186B82 /* ColorChooser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26E47A6723309BDB00186B82 /* ColorChooser.swift */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 2628DB07232DE93000049FFD /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; 37 | 2628DB09232DE94D00049FFD /* CustomModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomModifiers.swift; sourceTree = ""; }; 38 | 263DF0A9232C7F3E007635A4 /* DataFlow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DataFlow.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 263DF0AC232C7F3E007635A4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | 263DF0AE232C7F3E007635A4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 41 | 263DF0B0232C7F3E007635A4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 42 | 263DF0B2232C7F41007635A4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 263DF0B5232C7F41007635A4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 44 | 263DF0B8232C7F41007635A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 45 | 263DF0BA232C7F41007635A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 263DF0C0232C991E007635A4 /* PizzaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PizzaView.swift; sourceTree = ""; }; 47 | 263DF0C2232C994B007635A4 /* Pizza.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pizza.swift; sourceTree = ""; }; 48 | 264663C1232EFC4200368113 /* Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Property.swift; sourceTree = ""; }; 49 | 264663C3232EFD5300368113 /* UsingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsingState.swift; sourceTree = ""; }; 50 | 264663C6232EFF3B00368113 /* Numbers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Numbers.swift; sourceTree = ""; }; 51 | 264C426C232CD3EE00058CE0 /* PersonListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonListModel.swift; sourceTree = ""; }; 52 | 264C426E232CD57D00058CE0 /* PersonListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonListView.swift; sourceTree = ""; }; 53 | 264C4270232CD87900058CE0 /* PersonDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonDetailView.swift; sourceTree = ""; }; 54 | 26834F882337544E0013D5DD /* PersonVMTestData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonVMTestData.swift; sourceTree = ""; }; 55 | 26BDC7DC232DD34200C0A2D9 /* NestingViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestingViews.swift; sourceTree = ""; }; 56 | 26BDC7DE232DE63900C0A2D9 /* ChildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChildView.swift; sourceTree = ""; }; 57 | 26BDC7E0232DE6CE00C0A2D9 /* GrandChildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrandChildView.swift; sourceTree = ""; }; 58 | 26E47A632330988800186B82 /* ColorSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSet.swift; sourceTree = ""; }; 59 | 26E47A6523309A0E00186B82 /* ColorSetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSetView.swift; sourceTree = ""; }; 60 | 26E47A6723309BDB00186B82 /* ColorChooser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorChooser.swift; sourceTree = ""; }; 61 | /* End PBXFileReference section */ 62 | 63 | /* Begin PBXFrameworksBuildPhase section */ 64 | 263DF0A6232C7F3E007635A4 /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 263DF0A0232C7F3E007635A4 = { 75 | isa = PBXGroup; 76 | children = ( 77 | 263DF0AB232C7F3E007635A4 /* DataFlow */, 78 | 263DF0AA232C7F3E007635A4 /* Products */, 79 | ); 80 | sourceTree = ""; 81 | }; 82 | 263DF0AA232C7F3E007635A4 /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 263DF0A9232C7F3E007635A4 /* DataFlow.app */, 86 | ); 87 | name = Products; 88 | sourceTree = ""; 89 | }; 90 | 263DF0AB232C7F3E007635A4 /* DataFlow */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 263DF0AC232C7F3E007635A4 /* AppDelegate.swift */, 94 | 263DF0AE232C7F3E007635A4 /* SceneDelegate.swift */, 95 | 263DF0B0232C7F3E007635A4 /* ContentView.swift */, 96 | 264663C1232EFC4200368113 /* Property.swift */, 97 | 264663C3232EFD5300368113 /* UsingState.swift */, 98 | 264663C5232EFF2300368113 /* State & Binding 1 */, 99 | 26BDC7D9232DB93900C0A2D9 /* State & Binding 2 */, 100 | 26E47A622330986700186B82 /* Observable 1 */, 101 | 26BDC7DA232DB94C00C0A2D9 /* Observable 2 */, 102 | 26BDC7DB232DD32E00C0A2D9 /* Environment */, 103 | 263DF0B2232C7F41007635A4 /* Assets.xcassets */, 104 | 263DF0B7232C7F41007635A4 /* LaunchScreen.storyboard */, 105 | 263DF0BA232C7F41007635A4 /* Info.plist */, 106 | 263DF0B4232C7F41007635A4 /* Preview Content */, 107 | ); 108 | path = DataFlow; 109 | sourceTree = ""; 110 | }; 111 | 263DF0B4232C7F41007635A4 /* Preview Content */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 263DF0B5232C7F41007635A4 /* Preview Assets.xcassets */, 115 | 26834F882337544E0013D5DD /* PersonVMTestData.swift */, 116 | ); 117 | path = "Preview Content"; 118 | sourceTree = ""; 119 | }; 120 | 264663C5232EFF2300368113 /* State & Binding 1 */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 264663C6232EFF3B00368113 /* Numbers.swift */, 124 | ); 125 | path = "State & Binding 1"; 126 | sourceTree = ""; 127 | }; 128 | 26BDC7D9232DB93900C0A2D9 /* State & Binding 2 */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 263DF0C0232C991E007635A4 /* PizzaView.swift */, 132 | 263DF0C2232C994B007635A4 /* Pizza.swift */, 133 | ); 134 | path = "State & Binding 2"; 135 | sourceTree = ""; 136 | }; 137 | 26BDC7DA232DB94C00C0A2D9 /* Observable 2 */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 264C426E232CD57D00058CE0 /* PersonListView.swift */, 141 | 264C4270232CD87900058CE0 /* PersonDetailView.swift */, 142 | 264C426C232CD3EE00058CE0 /* PersonListModel.swift */, 143 | ); 144 | path = "Observable 2"; 145 | sourceTree = ""; 146 | }; 147 | 26BDC7DB232DD32E00C0A2D9 /* Environment */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 26BDC7DC232DD34200C0A2D9 /* NestingViews.swift */, 151 | 26BDC7DE232DE63900C0A2D9 /* ChildView.swift */, 152 | 26BDC7E0232DE6CE00C0A2D9 /* GrandChildView.swift */, 153 | 2628DB07232DE93000049FFD /* UserSettings.swift */, 154 | 2628DB09232DE94D00049FFD /* CustomModifiers.swift */, 155 | ); 156 | path = Environment; 157 | sourceTree = ""; 158 | }; 159 | 26E47A622330986700186B82 /* Observable 1 */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 26E47A6523309A0E00186B82 /* ColorSetView.swift */, 163 | 26E47A6723309BDB00186B82 /* ColorChooser.swift */, 164 | 26E47A632330988800186B82 /* ColorSet.swift */, 165 | ); 166 | path = "Observable 1"; 167 | sourceTree = ""; 168 | }; 169 | /* End PBXGroup section */ 170 | 171 | /* Begin PBXNativeTarget section */ 172 | 263DF0A8232C7F3E007635A4 /* DataFlow */ = { 173 | isa = PBXNativeTarget; 174 | buildConfigurationList = 263DF0BD232C7F41007635A4 /* Build configuration list for PBXNativeTarget "DataFlow" */; 175 | buildPhases = ( 176 | 263DF0A5232C7F3E007635A4 /* Sources */, 177 | 263DF0A6232C7F3E007635A4 /* Frameworks */, 178 | 263DF0A7232C7F3E007635A4 /* Resources */, 179 | ); 180 | buildRules = ( 181 | ); 182 | dependencies = ( 183 | ); 184 | name = DataFlow; 185 | productName = DataFlow; 186 | productReference = 263DF0A9232C7F3E007635A4 /* DataFlow.app */; 187 | productType = "com.apple.product-type.application"; 188 | }; 189 | /* End PBXNativeTarget section */ 190 | 191 | /* Begin PBXProject section */ 192 | 263DF0A1232C7F3E007635A4 /* Project object */ = { 193 | isa = PBXProject; 194 | attributes = { 195 | LastSwiftUpdateCheck = 1100; 196 | LastUpgradeCheck = 1240; 197 | ORGANIZATIONNAME = TrozWare; 198 | TargetAttributes = { 199 | 263DF0A8232C7F3E007635A4 = { 200 | CreatedOnToolsVersion = 11.0; 201 | }; 202 | }; 203 | }; 204 | buildConfigurationList = 263DF0A4232C7F3E007635A4 /* Build configuration list for PBXProject "DataFlow" */; 205 | compatibilityVersion = "Xcode 9.3"; 206 | developmentRegion = en; 207 | hasScannedForEncodings = 0; 208 | knownRegions = ( 209 | en, 210 | Base, 211 | ); 212 | mainGroup = 263DF0A0232C7F3E007635A4; 213 | productRefGroup = 263DF0AA232C7F3E007635A4 /* Products */; 214 | projectDirPath = ""; 215 | projectRoot = ""; 216 | targets = ( 217 | 263DF0A8232C7F3E007635A4 /* DataFlow */, 218 | ); 219 | }; 220 | /* End PBXProject section */ 221 | 222 | /* Begin PBXResourcesBuildPhase section */ 223 | 263DF0A7232C7F3E007635A4 /* Resources */ = { 224 | isa = PBXResourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 263DF0B9232C7F41007635A4 /* LaunchScreen.storyboard in Resources */, 228 | 263DF0B6232C7F41007635A4 /* Preview Assets.xcassets in Resources */, 229 | 263DF0B3232C7F41007635A4 /* Assets.xcassets in Resources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | /* End PBXResourcesBuildPhase section */ 234 | 235 | /* Begin PBXSourcesBuildPhase section */ 236 | 263DF0A5232C7F3E007635A4 /* Sources */ = { 237 | isa = PBXSourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 264663C2232EFC4200368113 /* Property.swift in Sources */, 241 | 264C426D232CD3EE00058CE0 /* PersonListModel.swift in Sources */, 242 | 26E47A6823309BDB00186B82 /* ColorChooser.swift in Sources */, 243 | 2628DB08232DE93000049FFD /* UserSettings.swift in Sources */, 244 | 26E47A642330988800186B82 /* ColorSet.swift in Sources */, 245 | 26BDC7DD232DD34200C0A2D9 /* NestingViews.swift in Sources */, 246 | 264663C7232EFF3B00368113 /* Numbers.swift in Sources */, 247 | 264663C4232EFD5300368113 /* UsingState.swift in Sources */, 248 | 26BDC7E1232DE6CE00C0A2D9 /* GrandChildView.swift in Sources */, 249 | 263DF0AD232C7F3E007635A4 /* AppDelegate.swift in Sources */, 250 | 264C426F232CD57D00058CE0 /* PersonListView.swift in Sources */, 251 | 263DF0AF232C7F3E007635A4 /* SceneDelegate.swift in Sources */, 252 | 263DF0B1232C7F3E007635A4 /* ContentView.swift in Sources */, 253 | 26834F892337544E0013D5DD /* PersonVMTestData.swift in Sources */, 254 | 2628DB0A232DE94D00049FFD /* CustomModifiers.swift in Sources */, 255 | 263DF0C3232C994B007635A4 /* Pizza.swift in Sources */, 256 | 264C4271232CD87900058CE0 /* PersonDetailView.swift in Sources */, 257 | 26E47A6623309A0E00186B82 /* ColorSetView.swift in Sources */, 258 | 263DF0C1232C991E007635A4 /* PizzaView.swift in Sources */, 259 | 26BDC7DF232DE63900C0A2D9 /* ChildView.swift in Sources */, 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | /* End PBXSourcesBuildPhase section */ 264 | 265 | /* Begin PBXVariantGroup section */ 266 | 263DF0B7232C7F41007635A4 /* LaunchScreen.storyboard */ = { 267 | isa = PBXVariantGroup; 268 | children = ( 269 | 263DF0B8232C7F41007635A4 /* Base */, 270 | ); 271 | name = LaunchScreen.storyboard; 272 | sourceTree = ""; 273 | }; 274 | /* End PBXVariantGroup section */ 275 | 276 | /* Begin XCBuildConfiguration section */ 277 | 263DF0BB232C7F41007635A4 /* Debug */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ALWAYS_SEARCH_USER_PATHS = NO; 281 | CLANG_ANALYZER_NONNULL = YES; 282 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 283 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 284 | CLANG_CXX_LIBRARY = "libc++"; 285 | CLANG_ENABLE_MODULES = YES; 286 | CLANG_ENABLE_OBJC_ARC = YES; 287 | CLANG_ENABLE_OBJC_WEAK = YES; 288 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 289 | CLANG_WARN_BOOL_CONVERSION = YES; 290 | CLANG_WARN_COMMA = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 293 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 294 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 295 | CLANG_WARN_EMPTY_BODY = YES; 296 | CLANG_WARN_ENUM_CONVERSION = YES; 297 | CLANG_WARN_INFINITE_RECURSION = YES; 298 | CLANG_WARN_INT_CONVERSION = YES; 299 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 300 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 301 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 302 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 303 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 304 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 305 | CLANG_WARN_STRICT_PROTOTYPES = YES; 306 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 307 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 308 | CLANG_WARN_UNREACHABLE_CODE = YES; 309 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 310 | COPY_PHASE_STRIP = NO; 311 | DEBUG_INFORMATION_FORMAT = dwarf; 312 | ENABLE_STRICT_OBJC_MSGSEND = YES; 313 | ENABLE_TESTABILITY = YES; 314 | GCC_C_LANGUAGE_STANDARD = gnu11; 315 | GCC_DYNAMIC_NO_PIC = NO; 316 | GCC_NO_COMMON_BLOCKS = YES; 317 | GCC_OPTIMIZATION_LEVEL = 0; 318 | GCC_PREPROCESSOR_DEFINITIONS = ( 319 | "DEBUG=1", 320 | "$(inherited)", 321 | ); 322 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 323 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 324 | GCC_WARN_UNDECLARED_SELECTOR = YES; 325 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 326 | GCC_WARN_UNUSED_FUNCTION = YES; 327 | GCC_WARN_UNUSED_VARIABLE = YES; 328 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 329 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 330 | MTL_FAST_MATH = YES; 331 | ONLY_ACTIVE_ARCH = YES; 332 | SDKROOT = iphoneos; 333 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 334 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 335 | }; 336 | name = Debug; 337 | }; 338 | 263DF0BC232C7F41007635A4 /* Release */ = { 339 | isa = XCBuildConfiguration; 340 | buildSettings = { 341 | ALWAYS_SEARCH_USER_PATHS = NO; 342 | CLANG_ANALYZER_NONNULL = YES; 343 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 344 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 345 | CLANG_CXX_LIBRARY = "libc++"; 346 | CLANG_ENABLE_MODULES = YES; 347 | CLANG_ENABLE_OBJC_ARC = YES; 348 | CLANG_ENABLE_OBJC_WEAK = YES; 349 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 350 | CLANG_WARN_BOOL_CONVERSION = YES; 351 | CLANG_WARN_COMMA = YES; 352 | CLANG_WARN_CONSTANT_CONVERSION = YES; 353 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 355 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 356 | CLANG_WARN_EMPTY_BODY = YES; 357 | CLANG_WARN_ENUM_CONVERSION = YES; 358 | CLANG_WARN_INFINITE_RECURSION = YES; 359 | CLANG_WARN_INT_CONVERSION = YES; 360 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 361 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 362 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 363 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 364 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 365 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 366 | CLANG_WARN_STRICT_PROTOTYPES = YES; 367 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 368 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 369 | CLANG_WARN_UNREACHABLE_CODE = YES; 370 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 371 | COPY_PHASE_STRIP = NO; 372 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 373 | ENABLE_NS_ASSERTIONS = NO; 374 | ENABLE_STRICT_OBJC_MSGSEND = YES; 375 | GCC_C_LANGUAGE_STANDARD = gnu11; 376 | GCC_NO_COMMON_BLOCKS = YES; 377 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 378 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 379 | GCC_WARN_UNDECLARED_SELECTOR = YES; 380 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 381 | GCC_WARN_UNUSED_FUNCTION = YES; 382 | GCC_WARN_UNUSED_VARIABLE = YES; 383 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 384 | MTL_ENABLE_DEBUG_INFO = NO; 385 | MTL_FAST_MATH = YES; 386 | SDKROOT = iphoneos; 387 | SWIFT_COMPILATION_MODE = wholemodule; 388 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 389 | VALIDATE_PRODUCT = YES; 390 | }; 391 | name = Release; 392 | }; 393 | 263DF0BE232C7F41007635A4 /* Debug */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 397 | CODE_SIGN_STYLE = Automatic; 398 | CURRENT_PROJECT_VERSION = 2; 399 | DEVELOPMENT_ASSET_PATHS = "\"DataFlow/Preview Content\""; 400 | DEVELOPMENT_TEAM = TC28MCLCFA; 401 | ENABLE_PREVIEWS = YES; 402 | INFOPLIST_FILE = DataFlow/Info.plist; 403 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 404 | LD_RUNPATH_SEARCH_PATHS = ( 405 | "$(inherited)", 406 | "@executable_path/Frameworks", 407 | ); 408 | PRODUCT_BUNDLE_IDENTIFIER = net.troz.DataFlow; 409 | PRODUCT_NAME = "$(TARGET_NAME)"; 410 | SWIFT_VERSION = 5.0; 411 | TARGETED_DEVICE_FAMILY = "1,2"; 412 | }; 413 | name = Debug; 414 | }; 415 | 263DF0BF232C7F41007635A4 /* Release */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 419 | CODE_SIGN_STYLE = Automatic; 420 | CURRENT_PROJECT_VERSION = 2; 421 | DEVELOPMENT_ASSET_PATHS = "\"DataFlow/Preview Content\""; 422 | DEVELOPMENT_TEAM = TC28MCLCFA; 423 | ENABLE_PREVIEWS = YES; 424 | INFOPLIST_FILE = DataFlow/Info.plist; 425 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 426 | LD_RUNPATH_SEARCH_PATHS = ( 427 | "$(inherited)", 428 | "@executable_path/Frameworks", 429 | ); 430 | PRODUCT_BUNDLE_IDENTIFIER = net.troz.DataFlow; 431 | PRODUCT_NAME = "$(TARGET_NAME)"; 432 | SWIFT_VERSION = 5.0; 433 | TARGETED_DEVICE_FAMILY = "1,2"; 434 | }; 435 | name = Release; 436 | }; 437 | /* End XCBuildConfiguration section */ 438 | 439 | /* Begin XCConfigurationList section */ 440 | 263DF0A4232C7F3E007635A4 /* Build configuration list for PBXProject "DataFlow" */ = { 441 | isa = XCConfigurationList; 442 | buildConfigurations = ( 443 | 263DF0BB232C7F41007635A4 /* Debug */, 444 | 263DF0BC232C7F41007635A4 /* Release */, 445 | ); 446 | defaultConfigurationIsVisible = 0; 447 | defaultConfigurationName = Release; 448 | }; 449 | 263DF0BD232C7F41007635A4 /* Build configuration list for PBXNativeTarget "DataFlow" */ = { 450 | isa = XCConfigurationList; 451 | buildConfigurations = ( 452 | 263DF0BE232C7F41007635A4 /* Debug */, 453 | 263DF0BF232C7F41007635A4 /* Release */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | /* End XCConfigurationList section */ 459 | }; 460 | rootObject = 263DF0A1232C7F3E007635A4 /* Project object */; 461 | } 462 | -------------------------------------------------------------------------------- /DataFlow.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DataFlow.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DataFlow.xcodeproj/project.xcworkspace/xcuserdata/sarah.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /DataFlow.xcodeproj/xcuserdata/sarah.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /DataFlow.xcodeproj/xcuserdata/sarah.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DataFlow.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DataFlow/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 14/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /DataFlow/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /DataFlow/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DataFlow/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DataFlow/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 14/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | @State private var tabSelection = 0 13 | 14 | var body: some View { 15 | NavigationView { 16 | List { 17 | NavigationLink(destination: Property()) { 18 | ListContents(title: "Property", imageNumber: 1) 19 | } 20 | 21 | NavigationLink(destination: UsingState()) { 22 | ListContents(title: "@State", imageNumber: 2) 23 | } 24 | 25 | NavigationLink(destination: Numbers()) { 26 | ListContents(title: "@State & @Binding 1", imageNumber: 3) 27 | } 28 | 29 | NavigationLink(destination: PizzaView()) { 30 | ListContents(title: "@State & @Binding 2", imageNumber: 4) 31 | } 32 | 33 | NavigationLink(destination: ColorSetView()) { 34 | ListContents(title: "ObservableObject 1", imageNumber: 5) 35 | } 36 | 37 | NavigationLink(destination: PersonListView()) { 38 | ListContents(title: "ObservableObject 2", imageNumber: 6) 39 | } 40 | 41 | NavigationLink(destination: NestingViews().environmentObject(UserSettings())) { 42 | ListContents(title: "@EnvironmentObject", imageNumber: 7) 43 | } 44 | } 45 | .navigationBarTitle("Examples") 46 | } 47 | } 48 | } 49 | 50 | struct ContentView_Previews: PreviewProvider { 51 | static var previews: some View { 52 | ContentView() 53 | } 54 | } 55 | 56 | struct ListContents: View { 57 | let title: String 58 | let imageNumber: Int 59 | 60 | var body: some View { 61 | HStack { 62 | Image(systemName: "\(imageNumber).square") 63 | .padding() 64 | .font(.largeTitle) 65 | Text(title) 66 | .font(.headline) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /DataFlow/Environment/ChildView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChildView.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 15/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ChildView: View { 12 | // Because this view does not use the data, there is no need to pass 13 | // it down to it, but its child can still access that data. 14 | 15 | var body: some View { 16 | ZStack { 17 | Color.green 18 | 19 | VStack { 20 | Text("This view has no data.") 21 | Text("But it has a child that does.") 22 | 23 | GrandChildView().padding() 24 | } 25 | .foregroundColor(.white) 26 | .padding() 27 | } 28 | } 29 | } 30 | 31 | struct ChildView_Previews: PreviewProvider { 32 | static var previews: some View { 33 | // Preview must have access to the required environment object 34 | // because this view previews its child which uses it 35 | ChildView() 36 | .environmentObject(UserSettings()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DataFlow/Environment/CustomModifiers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomModifiers.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 15/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | // A custom view modifier so all the buttons can look the same 12 | // without having to enter this amount of detail every time 13 | 14 | struct GrayButtonStyle: ViewModifier { 15 | func body(content: Content) -> some View { 16 | return content 17 | .frame(width: 150, height: 40) 18 | .padding() 19 | .font(.title) 20 | .background(Color.gray) 21 | .foregroundColor(.white) 22 | .cornerRadius(20) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DataFlow/Environment/GrandChildView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GrandChildView.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 15/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct GrandChildView: View { 12 | // This grandchild view now gets access to the environment object 13 | // even though its parent does not. 14 | 15 | @EnvironmentObject var userSettings: UserSettings 16 | 17 | var body: some View { 18 | let imageName = userSettings.isLoggedIn 19 | ? "person.crop.square" 20 | : "questionmark.square" 21 | 22 | let buttonText = userSettings.isLoggedIn 23 | ? "Log Out" 24 | : "Log In" 25 | 26 | // because there is more than one statement in the body method 27 | // the return keyword is needed to return the View 28 | 29 | return ZStack { 30 | Color.blue 31 | 32 | VStack { 33 | Image(systemName: imageName).padding() 34 | 35 | // Toggling the environment object value changes 36 | // all the views that use it 37 | Button(action: { userSettings.isLoggedIn.toggle() }) { 38 | Text(buttonText).modifier(GrayButtonStyle()) 39 | } 40 | } 41 | .padding() 42 | } 43 | .foregroundColor(.white) 44 | .font(.system(size: 100)) 45 | } 46 | } 47 | 48 | struct GrandChildView_Previews: PreviewProvider { 49 | static var previews: some View { 50 | GrandChildView() 51 | .environmentObject(UserSettings()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /DataFlow/Environment/NestingViews.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NestingViews.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 15/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct NestingViews: View { 12 | // Get access to global environment object 13 | @EnvironmentObject var userSettings: UserSettings 14 | 15 | // for demonstration purposes, the nested views have 16 | // different coloured backgrounds 17 | 18 | var body: some View { 19 | ZStack { 20 | Color.yellow.edgesIgnoringSafeArea(.all) 21 | 22 | VStack { 23 | Text(userSettings.isLoggedIn 24 | ? "User Logged In" 25 | : "User Logged Out") 26 | .padding() 27 | .font(.title) 28 | 29 | // Button toggles value in environment object 30 | Button(action: { userSettings.isLoggedIn.toggle() }) { 31 | Text(userSettings.isLoggedIn ? "Log Out" : "Log In") 32 | .modifier(GrayButtonStyle()) 33 | } 34 | .padding() 35 | 36 | Spacer() 37 | 38 | // display first nested view 39 | ChildView() 40 | } 41 | } 42 | } 43 | } 44 | 45 | struct NestingViews_Previews: PreviewProvider { 46 | static var previews: some View { 47 | // Preview must have access to the required environment object 48 | NestingViews() 49 | .environmentObject(UserSettings()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /DataFlow/Environment/UserSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserSettings.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 15/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // An EnvironmentObject class is just like any ObservableObject class 12 | 13 | class UserSettings: ObservableObject { 14 | @Published var isLoggedIn: Bool = false 15 | } 16 | -------------------------------------------------------------------------------- /DataFlow/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /DataFlow/Observable 1/ColorChooser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorChooser.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 17/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ColorChooser: View { 12 | @ObservedObject var colorSet: ColorSet 13 | 14 | var body: some View { 15 | ZStack { 16 | Color(red: 0.95, green: 0.95, blue: 0.95) 17 | .edgesIgnoringSafeArea(.all) 18 | 19 | VStack { 20 | // The 2 Chooser subviews also get passed the ObservedObject 21 | ForeColorChooser(colorSet: colorSet) 22 | 23 | Divider() 24 | 25 | BackColorChooser(colorSet: colorSet) 26 | 27 | } 28 | } 29 | } 30 | } 31 | 32 | struct ColorChooser_Previews: PreviewProvider { 33 | static var previews: some View { 34 | ColorChooser(colorSet: ColorSet()) 35 | } 36 | } 37 | 38 | struct ForeColorChooser: View { 39 | @ObservedObject var colorSet: ColorSet 40 | 41 | var body: some View { 42 | Group { 43 | HStack { 44 | Text("Foreground Red:").frame(width: 150) 45 | Slider(value: $colorSet.foregroundRed, in: 0.0 ... 1.0) 46 | }.padding() 47 | 48 | HStack { 49 | Text("Foreground Green:").frame(width: 150) 50 | Slider(value: $colorSet.foregroundGreen, in: 0.0 ... 1.0) 51 | }.padding() 52 | 53 | HStack { 54 | Text("Foreground Blue:").frame(width: 150) 55 | Slider(value: $colorSet.foregroundBlue, in: 0.0 ... 1.0) 56 | }.padding() 57 | 58 | RoundedRectangle(cornerRadius: 20) 59 | .fill(colorSet.foregroundColor) 60 | .frame(height: 60) 61 | .padding() 62 | } 63 | } 64 | } 65 | 66 | struct BackColorChooser: View { 67 | @ObservedObject var colorSet: ColorSet 68 | 69 | var body: some View { 70 | Group { 71 | HStack { 72 | Text("Background Red:").frame(width: 150) 73 | Slider(value: $colorSet.backgroundRed, in: 0.0 ... 1.0) 74 | }.padding() 75 | 76 | HStack { 77 | Text("Background Green:").frame(width: 150) 78 | Slider(value: $colorSet.backgroundGreen, in: 0.0 ... 1.0) 79 | }.padding() 80 | 81 | HStack { 82 | Text("Background Blue:").frame(width: 150) 83 | Slider(value: $colorSet.backgroundBlue, in: 0.0 ... 1.0) 84 | }.padding() 85 | 86 | RoundedRectangle(cornerRadius: 20) 87 | .fill(colorSet.backgroundColor) 88 | .frame(height: 60) 89 | .padding() 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /DataFlow/Observable 1/ColorSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorSet.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 17/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | class ColorSet: ObservableObject { 12 | 13 | // ObservableObject 14 | // The 6 color components are marked as @Published so any changes 15 | // get published to the views that are observing 16 | 17 | @Published var foregroundRed = 0.0 18 | @Published var foregroundGreen = 0.0 19 | @Published var foregroundBlue = 0.0 20 | 21 | @Published var backgroundRed = 1.0 22 | @Published var backgroundGreen = 1.0 23 | @Published var backgroundBlue = 1.0 24 | 25 | // Computed variables to create the RGB colors from the components 26 | 27 | var foregroundColor: Color { 28 | return Color(red: foregroundRed, green: foregroundGreen, blue: foregroundBlue) 29 | } 30 | 31 | var backgroundColor: Color { 32 | return Color(red: backgroundRed, green: backgroundGreen, blue: backgroundBlue) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DataFlow/Observable 1/ColorSetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorSetDemo.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 17/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ColorSetView: View { 12 | // Using an ObservedObject for reference-based data (classes) 13 | @StateObject private var colorSet = ColorSet() 14 | 15 | // @State property to control when chooser is displayed 16 | @State private var showChooser = false 17 | 18 | var body: some View { 19 | ZStack { 20 | colorSet.backgroundColor 21 | .edgesIgnoringSafeArea(.all) 22 | 23 | Image(systemName: "ant.fill") 24 | .foregroundColor(colorSet.foregroundColor) 25 | .font(.system(size: 200)) 26 | 27 | VStack { 28 | Spacer() 29 | 30 | Button(action: { showChooser = true }) { 31 | Text("Change Colors") 32 | .frame(width: 170, height: 50) 33 | .background(Color.blue) 34 | .foregroundColor(.white) 35 | .cornerRadius(20) 36 | } 37 | } 38 | } 39 | .sheet(isPresented: $showChooser) { 40 | // present the sheet, passing the ObservedObject 41 | // notice that this does not use $ as the ColorChooser 42 | // will get a reference to the ColorSet object 43 | ColorChooser(colorSet: colorSet) 44 | 45 | // changes to this object get passed back automatically 46 | // and used to update this view 47 | } 48 | } 49 | } 50 | 51 | struct ColorSetDemo_Previews: PreviewProvider { 52 | static var previews: some View { 53 | ColorSetView() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /DataFlow/Observable 2/PersonDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersonDetailView.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 14/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct PersonDetailView: View { 12 | // Data passed from parent list view 13 | @Binding var person: PersonViewModel 14 | 15 | // SwiftUI form with data fields 16 | // note the autocapitalization and keyboard modifiers 17 | 18 | var body: some View { 19 | VStack { 20 | Form { 21 | Section(header: Text("First Name")) { 22 | TextField("Enter first name", text: $person.first) 23 | } 24 | 25 | Section(header: Text("Last Name")) { 26 | TextField("Enter last name", text: $person.last) 27 | } 28 | 29 | Section(header: Text("Phone Number")) { 30 | TextField("Enter phone number", text: $person.phone) 31 | .keyboardType(.phonePad) 32 | } 33 | 34 | Section(header: Text("Address")) { 35 | TextField("Address", text: $person.address) 36 | TextField("City", text: $person.city) 37 | TextField("State", text: $person.state) 38 | TextField("Zip", text: $person.zip) 39 | } 40 | } 41 | .autocapitalization(.words) 42 | 43 | Text("Registered on:") 44 | .font(.headline) 45 | .padding(6) 46 | Text("\(person.registered, formatter: dateFormatter)") 47 | } 48 | } 49 | 50 | // Formatter for registration date 51 | var dateFormatter: DateFormatter { 52 | let df = DateFormatter() 53 | df.timeStyle = .short 54 | df.dateStyle = .long 55 | return df 56 | } 57 | } 58 | 59 | // Previewing requires using .constant to convert the data to a binding 60 | // Sample data is generated in an extension in Preview Content 61 | 62 | struct PersonDetailView_Previews: PreviewProvider { 63 | static var previews: some View { 64 | let person = PersonViewModel.samplePerson() 65 | return PersonDetailView(person: .constant(person)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /DataFlow/Observable 2/PersonListModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersonsModel.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 14/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class PersonListModel: ObservableObject { 12 | // Main list view model 13 | // ObservableObject so that updates are detected 14 | 15 | @Published var ids: [UUID] = [] 16 | @Published var persons: [UUID : PersonViewModel] = [:] 17 | 18 | func fetchData() { 19 | // avoid too many calls to the API 20 | if ids.count > 0 { return } 21 | 22 | let address = "https://next.json-generator.com/api/json/get/VyQroKB8P?indent=2" 23 | guard let url = URL(string: address) else { 24 | fatalError("Bad data URL!") 25 | } 26 | 27 | URLSession.shared.dataTask(with: url) { (data, response, error) in 28 | guard let data = data, error == nil else { 29 | print("Error fetching data") 30 | return 31 | } 32 | 33 | do { 34 | let jsonDecoder = JSONDecoder() 35 | jsonDecoder.dateDecodingStrategy = .iso8601 36 | let dataArray = try jsonDecoder.decode([PersonModel].self, from: data) 37 | DispatchQueue.main.async { 38 | let personViewModels = dataArray.map { PersonViewModel(with: $0) }.sorted() { 39 | $0.last + $0.first < $1.last + $1.first 40 | } 41 | self.ids = personViewModels.map { $0.id } 42 | self.persons = Dictionary( 43 | uniqueKeysWithValues: personViewModels.map { ($0.id, $0) } 44 | ) 45 | } 46 | } catch { 47 | print(error) 48 | } 49 | }.resume() 50 | } 51 | 52 | func refreshData() { 53 | ids = [] 54 | persons = [:] 55 | fetchData() 56 | } 57 | } 58 | 59 | class PersonViewModel: Identifiable, ObservableObject { 60 | 61 | // Main model for use as ObservableObject 62 | // Derived from JSON via basic model 63 | 64 | // Even though this is not observed directly, 65 | // it must be an ObservableObject for the data flow to work 66 | 67 | var id = UUID() 68 | var first: String = "" 69 | var last: String = "" 70 | var phone: String = "" 71 | var address: String = "" 72 | var city: String = "" 73 | var state: String = "" 74 | var zip: String = "" 75 | var registered: Date = Date() 76 | 77 | init(with person: PersonModel) { 78 | self.id = person.id 79 | self.first = person.first 80 | self.last = person.last 81 | self.phone = person.phone 82 | self.address = person.address 83 | self.city = person.city 84 | self.state = person.state 85 | self.zip = person.zip 86 | self.registered = person.registered 87 | } 88 | 89 | init() { } 90 | 91 | } 92 | 93 | struct PersonModel: Codable { 94 | // Basic model for decoding from JSON 95 | 96 | let id: UUID 97 | let first: String 98 | let last: String 99 | let phone: String 100 | let address: String 101 | let city: String 102 | let state: String 103 | let zip: String 104 | let registered: Date 105 | 106 | init(from decoder: Decoder) throws { 107 | let values = try decoder.container(keyedBy: CodingKeys.self) 108 | id = try values.decode(UUID.self, forKey: .id) 109 | first = try values.decode(String.self, forKey: .first) 110 | last = try values.decode(String.self, forKey: .last) 111 | phone = try values.decode(String.self, forKey: .phone) 112 | registered = try values.decode(Date.self, forKey: .registered) 113 | 114 | // split up address into separate lines for easier editing 115 | let addressData = try values.decode(String.self, forKey: .address) 116 | let addressComponents = addressData.components(separatedBy: ", ") 117 | address = addressComponents[0] 118 | city = addressComponents[1] 119 | state = addressComponents[2] 120 | zip = addressComponents[3] 121 | } 122 | } 123 | 124 | // Extension to force un-wrap a Dictionary value which is normally an optional. 125 | // This is so it can be used to create a Binding. 126 | extension Dictionary where Key == UUID, Value == PersonViewModel { 127 | subscript(unchecked key: Key) -> Value { 128 | get { 129 | guard let result = self[key] else { 130 | fatalError("This person does not exist.") 131 | } 132 | return result 133 | } 134 | set { 135 | self[key] = newValue 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /DataFlow/Observable 2/PersonListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersonList.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 14/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct PersonListView: View { 12 | // Using an ObservedObject for reference-based data (classes) 13 | @StateObject var personList = PersonListModel() 14 | 15 | var body: some View { 16 | List { 17 | // To make the navigation link edits return to here, 18 | // the data sent must be a direct reference to an element 19 | // of the ObservedObject, not the closure parameter. 20 | 21 | // Thanks to Stewart Lynch (@StewartLynch) for suggesting using a function to 22 | // get a binding to the person so it coud be passed to the detail view. 23 | 24 | // And now thanks to Vadim Shpakovski (@vadimshpakovski) for another option 25 | // which does not rely on creating a binding to every person, but uses 26 | // onReceive to react to changes to the person and trigger an update of personList. 27 | // This will be faster for longer lists and feels more like how ObservedObject is meant to be used. 28 | // Note that PersonDetailView has changed from using @Binding to @ObservedObject. 29 | 30 | // And a further update after receiving some more feedback which suggested 31 | // that using @Binding was better as it means that the entire ForEach doid not have to be 32 | // re-calcaulted afetr every change. But this uses a Dictionary to avoid Stewart's function. 33 | 34 | ForEach(personList.ids, id: \.self) { id in 35 | NavigationLink( 36 | destination: PersonDetailView(person: $personList.persons[unchecked: id]) 37 | ) { 38 | Text("\(personList.persons[unchecked: id].first)") + 39 | Text(" \(personList.persons[unchecked: id].last)") 40 | } 41 | } 42 | .onDelete { indexSet in 43 | // add this modifier to allow deleting from the list 44 | personList.ids.remove(atOffsets: indexSet) 45 | } 46 | .onMove { indices, newOffset in 47 | // add this modifier to allow moving in the list 48 | personList.ids.move(fromOffsets: indices, toOffset: newOffset) 49 | } 50 | } 51 | 52 | // This runs when the view appears to load the initial data 53 | .onAppear(perform: { personList.fetchData() }) 54 | 55 | // set up the navigation bar details 56 | // EditButton() is a standard View 57 | .navigationBarTitle("People") 58 | .navigationBarItems(trailing: 59 | HStack { 60 | Button(action: { personList.refreshData() }) { 61 | Image(systemName: "arrow.clockwise") 62 | } 63 | Spacer().frame(width: 30) 64 | EditButton() 65 | } 66 | ) 67 | } 68 | 69 | } 70 | 71 | // To preview this with navigation, it must be embedded in a NavigationView 72 | // but the main ContentView provides the main NavigationView 73 | // so this view will only get its own when in Proview mode 74 | 75 | struct PersonList_Previews: PreviewProvider { 76 | static var previews: some View { 77 | NavigationView { 78 | PersonListView() 79 | } 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /DataFlow/Preview Content/PersonVMTestData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersonVMTestData.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 22/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension PersonViewModel { 12 | // Sample data for use in Previews only 13 | 14 | static func samplePerson() -> PersonViewModel { 15 | let json = """ 16 | { 17 | "id": "07534800-9c07-4857-b931-d6541ff0df08", 18 | "first": "Beasley", 19 | "last": "Burnett", 20 | "phone": "+1 (957) 453-3538", 21 | "address": "740 Jodie Court, Manchester, Vermont, 8934", 22 | "registered": "2018-06-08T01:08:31+00:00" 23 | } 24 | """ 25 | 26 | let jsonDecoder = JSONDecoder() 27 | jsonDecoder.dateDecodingStrategy = .iso8601 28 | 29 | // Uisng force un-wrapping for sample data only, not for production 30 | let person = try! jsonDecoder.decode(PersonModel.self, from: json.data(using: .utf8)!) 31 | return PersonViewModel(with: person) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DataFlow/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DataFlow/Property.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Property.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 16/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct Property: View { 12 | // Property 13 | let greeting = "Hello from SwiftUI!" 14 | 15 | var body: some View { 16 | // Using property directly 17 | Text(greeting) 18 | .font(.title) 19 | } 20 | } 21 | 22 | struct Property_Previews: PreviewProvider { 23 | static var previews: some View { 24 | Property() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DataFlow/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 14/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /DataFlow/State & Binding 1/Numbers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Numbers.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 16/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct Numbers: View { 12 | @State private var stepperValue = 0 13 | 14 | var body: some View { 15 | VStack { 16 | Text("Parent view value = \(stepperValue)") 17 | .font(.title) 18 | 19 | NumberChooser(stepperValue: $stepperValue) 20 | } 21 | } 22 | } 23 | 24 | struct Numbers_Previews: PreviewProvider { 25 | static var previews: some View { 26 | Numbers() 27 | } 28 | } 29 | 30 | struct NumberChooser: View { 31 | // Using state from parent with 2-way binding 32 | @Binding var stepperValue: Int 33 | 34 | var body: some View { 35 | ZStack { 36 | RoundedRectangle(cornerRadius: 15) 37 | .fill(Color.init(red: 0.95, green: 0.95, blue: 0.95)) 38 | .frame(height: 300) 39 | 40 | VStack { 41 | // Using bound state from parent with 2-way binding 42 | Stepper(value: $stepperValue, in: 0...20) { 43 | Text("Value in child = \(stepperValue)") 44 | } 45 | .padding(50) 46 | 47 | // Using bound state from parent as property 48 | // this view cannot change the value 49 | NumberBlock(stepperValue: stepperValue) 50 | } 51 | } 52 | .padding() 53 | } 54 | } 55 | 56 | struct NumberBlock: View { 57 | // As this view never changes the value, there is no need to bind it 58 | var stepperValue: Int 59 | 60 | var body: some View { 61 | Image(systemName: "\(stepperValue).square") 62 | .font(.system(size: 100)) 63 | .foregroundColor(.blue) 64 | .padding(.bottom, 20) 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /DataFlow/State & Binding 2/Pizza.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pizza.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 14/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // Pizza model based on enums for each property 12 | // All CaseIterable so they can be looped through to create the UI 13 | 14 | struct Pizza { 15 | var name: PizzaName = .margherita 16 | var size: PizzaSize = .medium 17 | var crust: PizzaCrust = .standard 18 | 19 | var pizzaSelection: String { 20 | return "You have selected a \(size.rawValue) \(name.rawValue.capitalized) pizza with a \(crust.rawValue) crust." 21 | } 22 | } 23 | 24 | enum PizzaName: String, CaseIterable { 25 | case margherita, hawaiian, pepperoni, vegetarian, seafood, mushroom 26 | } 27 | 28 | enum PizzaSize: String, CaseIterable { 29 | case small, medium, large 30 | } 31 | 32 | enum PizzaCrust: String, CaseIterable { 33 | case thin, standard, deeppan = "deep pan" 34 | } 35 | -------------------------------------------------------------------------------- /DataFlow/State & Binding 2/PizzaView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PizzaView.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 14/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct PizzaView: View { 12 | // Using @State for a struct 13 | @State private var pizza = Pizza() 14 | 15 | var body: some View { 16 | VStack { 17 | Form { 18 | // Using 2-way binding but each component 19 | // only needs 1 property from the struct 20 | PizzaNamePicker(selectedPizzaName: $pizza.name) 21 | PizzaSizePicker(selectedPizzaSize: $pizza.size) 22 | PizzaCrustPicker(selectedPizzaCrust: $pizza.crust) 23 | } 24 | 25 | // Text representation to prove that the 26 | // subviews are modifying the parent struct 27 | Text(pizza.pizzaSelection) 28 | .padding() 29 | .multilineTextAlignment(.center) 30 | } 31 | .navigationBarTitle("Choose Your Pizza") 32 | } 33 | } 34 | 35 | struct PizzaView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | PizzaView() 38 | } 39 | } 40 | 41 | struct PizzaNamePicker: View { 42 | @Binding var selectedPizzaName: PizzaName 43 | 44 | var body: some View { 45 | Section(header: Text("Select your pizza:").font(.headline)) { 46 | List(PizzaName.allCases, id: \.self) { pizzaName in 47 | Button(action: { selectedPizzaName = pizzaName }) { 48 | PizzaNamePickerRow(selectedPizzaName: selectedPizzaName, 49 | pizzaName: pizzaName) 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | struct PizzaNamePickerRow: View { 57 | // sub-subview for the pizza name row 58 | // still using 2-way binding for the selected pizza name 59 | // and gets a property for the name to display 60 | 61 | let selectedPizzaName: PizzaName 62 | let pizzaName: PizzaName 63 | 64 | var body: some View { 65 | HStack { 66 | Text(pizzaName.rawValue.capitalized) 67 | Spacer() 68 | if pizzaName == selectedPizzaName { 69 | Image(systemName: "checkmark") 70 | } 71 | } 72 | .foregroundColor(.primary) 73 | } 74 | } 75 | 76 | struct PizzaSizePicker: View { 77 | @Binding var selectedPizzaSize: PizzaSize 78 | 79 | var body: some View { 80 | Section(header: Text("Select your size:").font(.headline)) { 81 | Picker(selection: $selectedPizzaSize, label: Text("Pizza Size")) { 82 | ForEach(PizzaSize.allCases, id: \.self) { pizzaSize in 83 | Text(pizzaSize.rawValue.capitalized).tag(pizzaSize) 84 | } 85 | } 86 | .pickerStyle(SegmentedPickerStyle()) 87 | } 88 | } 89 | } 90 | 91 | struct PizzaCrustPicker: View { 92 | @Binding var selectedPizzaCrust: PizzaCrust 93 | 94 | var body: some View { 95 | Section(header: Text("Select your crust:").font(.headline)) { 96 | Picker(selection: $selectedPizzaCrust, label: Text("Pizza Crust")) { 97 | ForEach(PizzaCrust.allCases, id: \.self) { pizzaCrust in 98 | Text(pizzaCrust.rawValue.capitalized).tag(pizzaCrust) 99 | } 100 | } 101 | .pickerStyle(SegmentedPickerStyle()) 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /DataFlow/UsingState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UsingState.swift 3 | // DataFlow 4 | // 5 | // Created by Sarah Reichelt on 16/09/2019. 6 | // Copyright © 2019 TrozWare. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct UsingState: View { 12 | @State private var toggleValue = true 13 | 14 | var body: some View { 15 | // Using state with 2-way binding 16 | Toggle(isOn: $toggleValue) { 17 | Text("Toggle is \(toggleValue ? "ON" : "OFF")") 18 | } 19 | .padding(50) 20 | } 21 | } 22 | 23 | struct UsingState_Previews: PreviewProvider { 24 | static var previews: some View { 25 | UsingState() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI Data Flow 2 | 3 | An Xcode project for use with https://troz.net/post/2019/swiftui-data-flow/ 4 | 5 | Demonstrating multiple ways of passing data around a SwiftUI app. 6 | 7 | ![ContentView](assets/ContentView.png) -------------------------------------------------------------------------------- /assets/ContentView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trozware/swiftui-data-flow/d04854f1f3311c03cec5472de110304b65b89888/assets/ContentView.png --------------------------------------------------------------------------------