├── README.md ├── iTour.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── twostraws.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── twostraws.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── iTour ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── ContentView.swift ├── Destination.swift ├── DestinationListingView.swift ├── EditDestinationView.swift ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── Sight.swift └── iTourApp.swift /README.md: -------------------------------------------------------------------------------- 1 | # iTour SwiftData tutorial 2 | 3 | This is the source code to accompany my 8-part tutorial introduction to SwiftData. You can read the tutorial – or watch its videos, if you prefer – here: . 4 | 5 | Questions? Comments? [Tweet me at @twostraws](https://twitter.com/twostraws). 6 | -------------------------------------------------------------------------------- /iTour.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5141A3DA2AC88CF6007CE882 /* DestinationListingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141A3D92AC88CF6007CE882 /* DestinationListingView.swift */; }; 11 | 5141A3DC2AC891F8007CE882 /* Sight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141A3DB2AC891F8007CE882 /* Sight.swift */; }; 12 | 514A1D2F2AC87E14004CBD3B /* iTourApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A1D2E2AC87E14004CBD3B /* iTourApp.swift */; }; 13 | 514A1D312AC87E14004CBD3B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A1D302AC87E14004CBD3B /* ContentView.swift */; }; 14 | 514A1D332AC87E15004CBD3B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 514A1D322AC87E15004CBD3B /* Assets.xcassets */; }; 15 | 514A1D362AC87E15004CBD3B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 514A1D352AC87E15004CBD3B /* Preview Assets.xcassets */; }; 16 | 514A1D3D2AC87E73004CBD3B /* Destination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A1D3C2AC87E73004CBD3B /* Destination.swift */; }; 17 | 514A1D3F2AC888F0004CBD3B /* EditDestinationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A1D3E2AC888F0004CBD3B /* EditDestinationView.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 5141A3D92AC88CF6007CE882 /* DestinationListingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestinationListingView.swift; sourceTree = ""; }; 22 | 5141A3DB2AC891F8007CE882 /* Sight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sight.swift; sourceTree = ""; }; 23 | 514A1D2B2AC87E14004CBD3B /* iTour.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iTour.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 514A1D2E2AC87E14004CBD3B /* iTourApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iTourApp.swift; sourceTree = ""; }; 25 | 514A1D302AC87E14004CBD3B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 26 | 514A1D322AC87E15004CBD3B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | 514A1D352AC87E15004CBD3B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 28 | 514A1D3C2AC87E73004CBD3B /* Destination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Destination.swift; sourceTree = ""; }; 29 | 514A1D3E2AC888F0004CBD3B /* EditDestinationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditDestinationView.swift; sourceTree = ""; }; 30 | /* End PBXFileReference section */ 31 | 32 | /* Begin PBXFrameworksBuildPhase section */ 33 | 514A1D282AC87E14004CBD3B /* Frameworks */ = { 34 | isa = PBXFrameworksBuildPhase; 35 | buildActionMask = 2147483647; 36 | files = ( 37 | ); 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXFrameworksBuildPhase section */ 41 | 42 | /* Begin PBXGroup section */ 43 | 514A1D222AC87E14004CBD3B = { 44 | isa = PBXGroup; 45 | children = ( 46 | 514A1D2D2AC87E14004CBD3B /* iTour */, 47 | 514A1D2C2AC87E14004CBD3B /* Products */, 48 | ); 49 | sourceTree = ""; 50 | }; 51 | 514A1D2C2AC87E14004CBD3B /* Products */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | 514A1D2B2AC87E14004CBD3B /* iTour.app */, 55 | ); 56 | name = Products; 57 | sourceTree = ""; 58 | }; 59 | 514A1D2D2AC87E14004CBD3B /* iTour */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 514A1D2E2AC87E14004CBD3B /* iTourApp.swift */, 63 | 514A1D302AC87E14004CBD3B /* ContentView.swift */, 64 | 5141A3D92AC88CF6007CE882 /* DestinationListingView.swift */, 65 | 514A1D3E2AC888F0004CBD3B /* EditDestinationView.swift */, 66 | 514A1D3C2AC87E73004CBD3B /* Destination.swift */, 67 | 5141A3DB2AC891F8007CE882 /* Sight.swift */, 68 | 514A1D322AC87E15004CBD3B /* Assets.xcassets */, 69 | 514A1D342AC87E15004CBD3B /* Preview Content */, 70 | ); 71 | path = iTour; 72 | sourceTree = ""; 73 | }; 74 | 514A1D342AC87E15004CBD3B /* Preview Content */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 514A1D352AC87E15004CBD3B /* Preview Assets.xcassets */, 78 | ); 79 | path = "Preview Content"; 80 | sourceTree = ""; 81 | }; 82 | /* End PBXGroup section */ 83 | 84 | /* Begin PBXNativeTarget section */ 85 | 514A1D2A2AC87E14004CBD3B /* iTour */ = { 86 | isa = PBXNativeTarget; 87 | buildConfigurationList = 514A1D392AC87E15004CBD3B /* Build configuration list for PBXNativeTarget "iTour" */; 88 | buildPhases = ( 89 | 514A1D272AC87E14004CBD3B /* Sources */, 90 | 514A1D282AC87E14004CBD3B /* Frameworks */, 91 | 514A1D292AC87E14004CBD3B /* Resources */, 92 | ); 93 | buildRules = ( 94 | ); 95 | dependencies = ( 96 | ); 97 | name = iTour; 98 | productName = iTour; 99 | productReference = 514A1D2B2AC87E14004CBD3B /* iTour.app */; 100 | productType = "com.apple.product-type.application"; 101 | }; 102 | /* End PBXNativeTarget section */ 103 | 104 | /* Begin PBXProject section */ 105 | 514A1D232AC87E14004CBD3B /* Project object */ = { 106 | isa = PBXProject; 107 | attributes = { 108 | BuildIndependentTargetsInParallel = 1; 109 | LastSwiftUpdateCheck = 1500; 110 | LastUpgradeCheck = 1500; 111 | TargetAttributes = { 112 | 514A1D2A2AC87E14004CBD3B = { 113 | CreatedOnToolsVersion = 15.0; 114 | }; 115 | }; 116 | }; 117 | buildConfigurationList = 514A1D262AC87E14004CBD3B /* Build configuration list for PBXProject "iTour" */; 118 | compatibilityVersion = "Xcode 14.0"; 119 | developmentRegion = en; 120 | hasScannedForEncodings = 0; 121 | knownRegions = ( 122 | en, 123 | Base, 124 | ); 125 | mainGroup = 514A1D222AC87E14004CBD3B; 126 | productRefGroup = 514A1D2C2AC87E14004CBD3B /* Products */; 127 | projectDirPath = ""; 128 | projectRoot = ""; 129 | targets = ( 130 | 514A1D2A2AC87E14004CBD3B /* iTour */, 131 | ); 132 | }; 133 | /* End PBXProject section */ 134 | 135 | /* Begin PBXResourcesBuildPhase section */ 136 | 514A1D292AC87E14004CBD3B /* Resources */ = { 137 | isa = PBXResourcesBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | 514A1D362AC87E15004CBD3B /* Preview Assets.xcassets in Resources */, 141 | 514A1D332AC87E15004CBD3B /* Assets.xcassets in Resources */, 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | /* End PBXResourcesBuildPhase section */ 146 | 147 | /* Begin PBXSourcesBuildPhase section */ 148 | 514A1D272AC87E14004CBD3B /* Sources */ = { 149 | isa = PBXSourcesBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | 514A1D312AC87E14004CBD3B /* ContentView.swift in Sources */, 153 | 514A1D2F2AC87E14004CBD3B /* iTourApp.swift in Sources */, 154 | 514A1D3D2AC87E73004CBD3B /* Destination.swift in Sources */, 155 | 5141A3DC2AC891F8007CE882 /* Sight.swift in Sources */, 156 | 514A1D3F2AC888F0004CBD3B /* EditDestinationView.swift in Sources */, 157 | 5141A3DA2AC88CF6007CE882 /* DestinationListingView.swift in Sources */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXSourcesBuildPhase section */ 162 | 163 | /* Begin XCBuildConfiguration section */ 164 | 514A1D372AC87E15004CBD3B /* Debug */ = { 165 | isa = XCBuildConfiguration; 166 | buildSettings = { 167 | ALWAYS_SEARCH_USER_PATHS = NO; 168 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 169 | CLANG_ANALYZER_NONNULL = YES; 170 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 172 | CLANG_ENABLE_MODULES = YES; 173 | CLANG_ENABLE_OBJC_ARC = YES; 174 | CLANG_ENABLE_OBJC_WEAK = YES; 175 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 176 | CLANG_WARN_BOOL_CONVERSION = YES; 177 | CLANG_WARN_COMMA = YES; 178 | CLANG_WARN_CONSTANT_CONVERSION = YES; 179 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 181 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 182 | CLANG_WARN_EMPTY_BODY = YES; 183 | CLANG_WARN_ENUM_CONVERSION = YES; 184 | CLANG_WARN_INFINITE_RECURSION = YES; 185 | CLANG_WARN_INT_CONVERSION = YES; 186 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 187 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 188 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 189 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 190 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 191 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 192 | CLANG_WARN_STRICT_PROTOTYPES = YES; 193 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 194 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 195 | CLANG_WARN_UNREACHABLE_CODE = YES; 196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 197 | COPY_PHASE_STRIP = NO; 198 | DEBUG_INFORMATION_FORMAT = dwarf; 199 | ENABLE_STRICT_OBJC_MSGSEND = YES; 200 | ENABLE_TESTABILITY = YES; 201 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 202 | GCC_C_LANGUAGE_STANDARD = gnu17; 203 | GCC_DYNAMIC_NO_PIC = NO; 204 | GCC_NO_COMMON_BLOCKS = YES; 205 | GCC_OPTIMIZATION_LEVEL = 0; 206 | GCC_PREPROCESSOR_DEFINITIONS = ( 207 | "DEBUG=1", 208 | "$(inherited)", 209 | ); 210 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 211 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 212 | GCC_WARN_UNDECLARED_SELECTOR = YES; 213 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 214 | GCC_WARN_UNUSED_FUNCTION = YES; 215 | GCC_WARN_UNUSED_VARIABLE = YES; 216 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 217 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 218 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 219 | MTL_FAST_MATH = YES; 220 | ONLY_ACTIVE_ARCH = YES; 221 | SDKROOT = iphoneos; 222 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 223 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 224 | }; 225 | name = Debug; 226 | }; 227 | 514A1D382AC87E15004CBD3B /* Release */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | ALWAYS_SEARCH_USER_PATHS = NO; 231 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 232 | CLANG_ANALYZER_NONNULL = YES; 233 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 234 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 235 | CLANG_ENABLE_MODULES = YES; 236 | CLANG_ENABLE_OBJC_ARC = YES; 237 | CLANG_ENABLE_OBJC_WEAK = YES; 238 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 239 | CLANG_WARN_BOOL_CONVERSION = YES; 240 | CLANG_WARN_COMMA = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 243 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 244 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 245 | CLANG_WARN_EMPTY_BODY = YES; 246 | CLANG_WARN_ENUM_CONVERSION = YES; 247 | CLANG_WARN_INFINITE_RECURSION = YES; 248 | CLANG_WARN_INT_CONVERSION = YES; 249 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 251 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 253 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 254 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 255 | CLANG_WARN_STRICT_PROTOTYPES = YES; 256 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 257 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 258 | CLANG_WARN_UNREACHABLE_CODE = YES; 259 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 260 | COPY_PHASE_STRIP = NO; 261 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 262 | ENABLE_NS_ASSERTIONS = NO; 263 | ENABLE_STRICT_OBJC_MSGSEND = YES; 264 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 265 | GCC_C_LANGUAGE_STANDARD = gnu17; 266 | GCC_NO_COMMON_BLOCKS = YES; 267 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 268 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 269 | GCC_WARN_UNDECLARED_SELECTOR = YES; 270 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 271 | GCC_WARN_UNUSED_FUNCTION = YES; 272 | GCC_WARN_UNUSED_VARIABLE = YES; 273 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 274 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 275 | MTL_ENABLE_DEBUG_INFO = NO; 276 | MTL_FAST_MATH = YES; 277 | SDKROOT = iphoneos; 278 | SWIFT_COMPILATION_MODE = wholemodule; 279 | VALIDATE_PRODUCT = YES; 280 | }; 281 | name = Release; 282 | }; 283 | 514A1D3A2AC87E15004CBD3B /* Debug */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 287 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 288 | CODE_SIGN_STYLE = Automatic; 289 | CURRENT_PROJECT_VERSION = 1; 290 | DEVELOPMENT_ASSET_PATHS = "\"iTour/Preview Content\""; 291 | DEVELOPMENT_TEAM = B5C26XE59E; 292 | ENABLE_PREVIEWS = YES; 293 | GENERATE_INFOPLIST_FILE = YES; 294 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 295 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 296 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 297 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 298 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 299 | LD_RUNPATH_SEARCH_PATHS = ( 300 | "$(inherited)", 301 | "@executable_path/Frameworks", 302 | ); 303 | MARKETING_VERSION = 1.0; 304 | PRODUCT_BUNDLE_IDENTIFIER = com.hackingwithswift.iTour; 305 | PRODUCT_NAME = "$(TARGET_NAME)"; 306 | SWIFT_EMIT_LOC_STRINGS = YES; 307 | SWIFT_VERSION = 5.0; 308 | TARGETED_DEVICE_FAMILY = "1,2"; 309 | }; 310 | name = Debug; 311 | }; 312 | 514A1D3B2AC87E15004CBD3B /* Release */ = { 313 | isa = XCBuildConfiguration; 314 | buildSettings = { 315 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 316 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 317 | CODE_SIGN_STYLE = Automatic; 318 | CURRENT_PROJECT_VERSION = 1; 319 | DEVELOPMENT_ASSET_PATHS = "\"iTour/Preview Content\""; 320 | DEVELOPMENT_TEAM = B5C26XE59E; 321 | ENABLE_PREVIEWS = YES; 322 | GENERATE_INFOPLIST_FILE = YES; 323 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 324 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 325 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 326 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 327 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 328 | LD_RUNPATH_SEARCH_PATHS = ( 329 | "$(inherited)", 330 | "@executable_path/Frameworks", 331 | ); 332 | MARKETING_VERSION = 1.0; 333 | PRODUCT_BUNDLE_IDENTIFIER = com.hackingwithswift.iTour; 334 | PRODUCT_NAME = "$(TARGET_NAME)"; 335 | SWIFT_EMIT_LOC_STRINGS = YES; 336 | SWIFT_VERSION = 5.0; 337 | TARGETED_DEVICE_FAMILY = "1,2"; 338 | }; 339 | name = Release; 340 | }; 341 | /* End XCBuildConfiguration section */ 342 | 343 | /* Begin XCConfigurationList section */ 344 | 514A1D262AC87E14004CBD3B /* Build configuration list for PBXProject "iTour" */ = { 345 | isa = XCConfigurationList; 346 | buildConfigurations = ( 347 | 514A1D372AC87E15004CBD3B /* Debug */, 348 | 514A1D382AC87E15004CBD3B /* Release */, 349 | ); 350 | defaultConfigurationIsVisible = 0; 351 | defaultConfigurationName = Release; 352 | }; 353 | 514A1D392AC87E15004CBD3B /* Build configuration list for PBXNativeTarget "iTour" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | 514A1D3A2AC87E15004CBD3B /* Debug */, 357 | 514A1D3B2AC87E15004CBD3B /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | /* End XCConfigurationList section */ 363 | }; 364 | rootObject = 514A1D232AC87E14004CBD3B /* Project object */; 365 | } 366 | -------------------------------------------------------------------------------- /iTour.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iTour.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iTour.xcodeproj/project.xcworkspace/xcuserdata/twostraws.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twostraws/iTour/0f730d26ce62dbc956ef3b51727139c3a6ef16ae/iTour.xcodeproj/project.xcworkspace/xcuserdata/twostraws.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /iTour.xcodeproj/xcuserdata/twostraws.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | iTour.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /iTour/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /iTour/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /iTour/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iTour/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // iTour 4 | // 5 | // Created by Paul Hudson on 30/09/2023. 6 | // 7 | 8 | import SwiftData 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | @Environment(\.modelContext) var modelContext 13 | 14 | @State private var path = [Destination]() 15 | @State private var sortOrder = SortDescriptor(\Destination.name) 16 | @State private var searchText = "" 17 | 18 | var body: some View { 19 | NavigationStack(path: $path) { 20 | DestinationListingView(sort: sortOrder, searchString: searchText) 21 | .navigationTitle("iTour") 22 | .navigationDestination(for: Destination.self, destination: EditDestinationView.init) 23 | .searchable(text: $searchText) 24 | .toolbar { 25 | Button("Add Destination", systemImage: "plus", action: addDestination) 26 | 27 | Menu("Sort", systemImage: "arrow.up.arrow.down") { 28 | Picker("Sort", selection: $sortOrder) { 29 | Text("Name") 30 | .tag(SortDescriptor(\Destination.name)) 31 | 32 | Text("Priority") 33 | .tag(SortDescriptor(\Destination.priority, order: .reverse)) 34 | 35 | Text("Date") 36 | .tag(SortDescriptor(\Destination.date)) 37 | } 38 | .pickerStyle(.inline) 39 | } 40 | } 41 | } 42 | } 43 | 44 | func addDestination() { 45 | let destination = Destination() 46 | modelContext.insert(destination) 47 | path = [destination] 48 | } 49 | } 50 | 51 | #Preview { 52 | ContentView() 53 | } 54 | -------------------------------------------------------------------------------- /iTour/Destination.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Destination.swift 3 | // iTour 4 | // 5 | // Created by Paul Hudson on 30/09/2023. 6 | // 7 | 8 | import Foundation 9 | import SwiftData 10 | 11 | @Model 12 | class Destination { 13 | var name: String 14 | var details: String 15 | var date: Date 16 | var priority: Int 17 | @Relationship(deleteRule: .cascade) var sights = [Sight]() 18 | 19 | init(name: String = "", details: String = "", date: Date = .now, priority: Int = 2) { 20 | self.name = name 21 | self.details = details 22 | self.date = date 23 | self.priority = priority 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /iTour/DestinationListingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DestinationListingView.swift 3 | // iTour 4 | // 5 | // Created by Paul Hudson on 30/09/2023. 6 | // 7 | 8 | import SwiftData 9 | import SwiftUI 10 | 11 | struct DestinationListingView: View { 12 | @Environment(\.modelContext) var modelContext 13 | @Query(sort: [SortDescriptor(\Destination.priority, order: .reverse), SortDescriptor(\Destination.name)]) var destinations: [Destination] 14 | 15 | var body: some View { 16 | List { 17 | ForEach(destinations) { destination in 18 | NavigationLink(value: destination) { 19 | VStack(alignment: .leading) { 20 | Text(destination.name) 21 | .font(.headline) 22 | 23 | Text(destination.date.formatted(date: .long, time: .shortened)) 24 | } 25 | } 26 | } 27 | .onDelete(perform: deleteDestinations) 28 | } 29 | } 30 | 31 | init(sort: SortDescriptor, searchString: String) { 32 | _destinations = Query(filter: #Predicate { 33 | if searchString.isEmpty { 34 | return true 35 | } else { 36 | return $0.name.localizedStandardContains(searchString) 37 | } 38 | }, sort: [sort]) 39 | } 40 | 41 | func deleteDestinations(_ indexSet: IndexSet) { 42 | for index in indexSet { 43 | let destination = destinations[index] 44 | modelContext.delete(destination) 45 | } 46 | } 47 | } 48 | 49 | #Preview { 50 | DestinationListingView(sort: SortDescriptor(\Destination.name), searchString: "") 51 | } 52 | -------------------------------------------------------------------------------- /iTour/EditDestinationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditDestinationView.swift 3 | // iTour 4 | // 5 | // Created by Paul Hudson on 30/09/2023. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftData 10 | 11 | struct EditDestinationView: View { 12 | @Bindable var destination: Destination 13 | @State private var newSightName = "" 14 | 15 | var body: some View { 16 | Form { 17 | TextField("Name", text: $destination.name) 18 | TextField("Details", text: $destination.details, axis: .vertical) 19 | DatePicker("Date", selection: $destination.date) 20 | 21 | Section("Priority") { 22 | Picker("Priority", selection: $destination.priority) { 23 | Text("Meh").tag(1) 24 | Text("Maybe").tag(2) 25 | Text("Must").tag(3) 26 | } 27 | .pickerStyle(.segmented) 28 | } 29 | 30 | Section("Sights") { 31 | ForEach(destination.sights) { sight in 32 | Text(sight.name) 33 | } 34 | 35 | HStack { 36 | TextField("Add a new sight in \(destination.name)", text: $newSightName) 37 | 38 | Button("Add", action: addSight) 39 | } 40 | } 41 | } 42 | .navigationTitle("Edit Destination") 43 | .navigationBarTitleDisplayMode(.inline) 44 | } 45 | 46 | func addSight() { 47 | guard newSightName.isEmpty == false else { return } 48 | 49 | withAnimation { 50 | let sight = Sight(name: newSightName) 51 | destination.sights.append(sight) 52 | newSightName = "" 53 | } 54 | } 55 | } 56 | 57 | #Preview { 58 | do { 59 | let config = ModelConfiguration(isStoredInMemoryOnly: true) 60 | let container = try ModelContainer(for: Destination.self, configurations: config) 61 | let example = Destination(name: "Example Destination", details: "Example details go here and will automatically expand vertically as they are edited.") 62 | return EditDestinationView(destination: example) 63 | .modelContainer(container) 64 | } catch { 65 | fatalError("Failed to create model container.") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /iTour/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iTour/Sight.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sight.swift 3 | // iTour 4 | // 5 | // Created by Paul Hudson on 30/09/2023. 6 | // 7 | 8 | import Foundation 9 | import SwiftData 10 | 11 | @Model 12 | class Sight { 13 | var name: String 14 | 15 | init(name: String) { 16 | self.name = name 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /iTour/iTourApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iTourApp.swift 3 | // iTour 4 | // 5 | // Created by Paul Hudson on 30/09/2023. 6 | // 7 | 8 | import SwiftData 9 | import SwiftUI 10 | 11 | @main 12 | struct iTourApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | ContentView() 16 | } 17 | .modelContainer(for: Destination.self) 18 | } 19 | } 20 | --------------------------------------------------------------------------------