├── BlogIdeaList-SwiftUI.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── andrewcbancroft.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── BlogIdeaList-SwiftUI ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── ContentView.swift ├── Core Data │ ├── BlogIdea.swift │ └── BlogIdeaList_SwiftUI.xcdatamodeld │ │ ├── .xccurrentversion │ │ └── BlogIdeaList_SwiftUI.xcdatamodel │ │ └── contents ├── EditView.swift ├── Info.plist ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json └── SceneDelegate.swift └── README.md /BlogIdeaList-SwiftUI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CE44E4DB22F303FF00BE2EDD /* EditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE44E4DA22F303FF00BE2EDD /* EditView.swift */; }; 11 | CE676F3D22F0B74F006141D1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE676F3C22F0B74F006141D1 /* AppDelegate.swift */; }; 12 | CE676F3F22F0B74F006141D1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE676F3E22F0B74F006141D1 /* SceneDelegate.swift */; }; 13 | CE676F4222F0B74F006141D1 /* BlogIdeaList_SwiftUI.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CE676F4022F0B74F006141D1 /* BlogIdeaList_SwiftUI.xcdatamodeld */; }; 14 | CE676F4422F0B74F006141D1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE676F4322F0B74F006141D1 /* ContentView.swift */; }; 15 | CE676F4622F0B74F006141D1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE676F4522F0B74F006141D1 /* Assets.xcassets */; }; 16 | CE676F4922F0B74F006141D1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE676F4822F0B74F006141D1 /* Preview Assets.xcassets */; }; 17 | CE676F4C22F0B74F006141D1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE676F4A22F0B74F006141D1 /* LaunchScreen.storyboard */; }; 18 | CE676F5422F0B7F1006141D1 /* BlogIdea.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE676F5322F0B7F1006141D1 /* BlogIdea.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | CE44E4DA22F303FF00BE2EDD /* EditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditView.swift; sourceTree = ""; }; 23 | CE676F3922F0B74F006141D1 /* BlogIdeaList-SwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "BlogIdeaList-SwiftUI.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | CE676F3C22F0B74F006141D1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | CE676F3E22F0B74F006141D1 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 26 | CE676F4122F0B74F006141D1 /* BlogIdeaList_SwiftUI.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = BlogIdeaList_SwiftUI.xcdatamodel; sourceTree = ""; }; 27 | CE676F4322F0B74F006141D1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 28 | CE676F4522F0B74F006141D1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | CE676F4822F0B74F006141D1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 30 | CE676F4B22F0B74F006141D1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | CE676F4D22F0B74F006141D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | CE676F5322F0B7F1006141D1 /* BlogIdea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogIdea.swift; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | CE676F3622F0B74F006141D1 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | CE44E4B722F1AD0000BE2EDD /* Core Data */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | CE676F4022F0B74F006141D1 /* BlogIdeaList_SwiftUI.xcdatamodeld */, 50 | CE676F5322F0B7F1006141D1 /* BlogIdea.swift */, 51 | ); 52 | path = "Core Data"; 53 | sourceTree = ""; 54 | }; 55 | CE676F3022F0B74F006141D1 = { 56 | isa = PBXGroup; 57 | children = ( 58 | CE676F3B22F0B74F006141D1 /* BlogIdeaList-SwiftUI */, 59 | CE676F3A22F0B74F006141D1 /* Products */, 60 | ); 61 | sourceTree = ""; 62 | }; 63 | CE676F3A22F0B74F006141D1 /* Products */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | CE676F3922F0B74F006141D1 /* BlogIdeaList-SwiftUI.app */, 67 | ); 68 | name = Products; 69 | sourceTree = ""; 70 | }; 71 | CE676F3B22F0B74F006141D1 /* BlogIdeaList-SwiftUI */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | CE44E4B722F1AD0000BE2EDD /* Core Data */, 75 | CE676F3C22F0B74F006141D1 /* AppDelegate.swift */, 76 | CE676F3E22F0B74F006141D1 /* SceneDelegate.swift */, 77 | CE676F4322F0B74F006141D1 /* ContentView.swift */, 78 | CE44E4DA22F303FF00BE2EDD /* EditView.swift */, 79 | CE676F4522F0B74F006141D1 /* Assets.xcassets */, 80 | CE676F4A22F0B74F006141D1 /* LaunchScreen.storyboard */, 81 | CE676F4D22F0B74F006141D1 /* Info.plist */, 82 | CE676F4722F0B74F006141D1 /* Preview Content */, 83 | ); 84 | path = "BlogIdeaList-SwiftUI"; 85 | sourceTree = ""; 86 | }; 87 | CE676F4722F0B74F006141D1 /* Preview Content */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | CE676F4822F0B74F006141D1 /* Preview Assets.xcassets */, 91 | ); 92 | path = "Preview Content"; 93 | sourceTree = ""; 94 | }; 95 | /* End PBXGroup section */ 96 | 97 | /* Begin PBXNativeTarget section */ 98 | CE676F3822F0B74F006141D1 /* BlogIdeaList-SwiftUI */ = { 99 | isa = PBXNativeTarget; 100 | buildConfigurationList = CE676F5022F0B74F006141D1 /* Build configuration list for PBXNativeTarget "BlogIdeaList-SwiftUI" */; 101 | buildPhases = ( 102 | CE676F3522F0B74F006141D1 /* Sources */, 103 | CE676F3622F0B74F006141D1 /* Frameworks */, 104 | CE676F3722F0B74F006141D1 /* Resources */, 105 | ); 106 | buildRules = ( 107 | ); 108 | dependencies = ( 109 | ); 110 | name = "BlogIdeaList-SwiftUI"; 111 | productName = "BlogIdeaList-SwiftUI"; 112 | productReference = CE676F3922F0B74F006141D1 /* BlogIdeaList-SwiftUI.app */; 113 | productType = "com.apple.product-type.application"; 114 | }; 115 | /* End PBXNativeTarget section */ 116 | 117 | /* Begin PBXProject section */ 118 | CE676F3122F0B74F006141D1 /* Project object */ = { 119 | isa = PBXProject; 120 | attributes = { 121 | LastSwiftUpdateCheck = 1100; 122 | LastUpgradeCheck = 1100; 123 | ORGANIZATIONNAME = "Andrew Bancroft"; 124 | TargetAttributes = { 125 | CE676F3822F0B74F006141D1 = { 126 | CreatedOnToolsVersion = 11.0; 127 | }; 128 | }; 129 | }; 130 | buildConfigurationList = CE676F3422F0B74F006141D1 /* Build configuration list for PBXProject "BlogIdeaList-SwiftUI" */; 131 | compatibilityVersion = "Xcode 9.3"; 132 | developmentRegion = en; 133 | hasScannedForEncodings = 0; 134 | knownRegions = ( 135 | en, 136 | Base, 137 | ); 138 | mainGroup = CE676F3022F0B74F006141D1; 139 | productRefGroup = CE676F3A22F0B74F006141D1 /* Products */; 140 | projectDirPath = ""; 141 | projectRoot = ""; 142 | targets = ( 143 | CE676F3822F0B74F006141D1 /* BlogIdeaList-SwiftUI */, 144 | ); 145 | }; 146 | /* End PBXProject section */ 147 | 148 | /* Begin PBXResourcesBuildPhase section */ 149 | CE676F3722F0B74F006141D1 /* Resources */ = { 150 | isa = PBXResourcesBuildPhase; 151 | buildActionMask = 2147483647; 152 | files = ( 153 | CE676F4C22F0B74F006141D1 /* LaunchScreen.storyboard in Resources */, 154 | CE676F4922F0B74F006141D1 /* Preview Assets.xcassets in Resources */, 155 | CE676F4622F0B74F006141D1 /* Assets.xcassets in Resources */, 156 | ); 157 | runOnlyForDeploymentPostprocessing = 0; 158 | }; 159 | /* End PBXResourcesBuildPhase section */ 160 | 161 | /* Begin PBXSourcesBuildPhase section */ 162 | CE676F3522F0B74F006141D1 /* Sources */ = { 163 | isa = PBXSourcesBuildPhase; 164 | buildActionMask = 2147483647; 165 | files = ( 166 | CE676F5422F0B7F1006141D1 /* BlogIdea.swift in Sources */, 167 | CE676F3D22F0B74F006141D1 /* AppDelegate.swift in Sources */, 168 | CE44E4DB22F303FF00BE2EDD /* EditView.swift in Sources */, 169 | CE676F4222F0B74F006141D1 /* BlogIdeaList_SwiftUI.xcdatamodeld in Sources */, 170 | CE676F4422F0B74F006141D1 /* ContentView.swift in Sources */, 171 | CE676F3F22F0B74F006141D1 /* SceneDelegate.swift in Sources */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | /* End PBXSourcesBuildPhase section */ 176 | 177 | /* Begin PBXVariantGroup section */ 178 | CE676F4A22F0B74F006141D1 /* LaunchScreen.storyboard */ = { 179 | isa = PBXVariantGroup; 180 | children = ( 181 | CE676F4B22F0B74F006141D1 /* Base */, 182 | ); 183 | name = LaunchScreen.storyboard; 184 | sourceTree = ""; 185 | }; 186 | /* End PBXVariantGroup section */ 187 | 188 | /* Begin XCBuildConfiguration section */ 189 | CE676F4E22F0B74F006141D1 /* Debug */ = { 190 | isa = XCBuildConfiguration; 191 | buildSettings = { 192 | ALWAYS_SEARCH_USER_PATHS = NO; 193 | CLANG_ANALYZER_NONNULL = YES; 194 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 195 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 196 | CLANG_CXX_LIBRARY = "libc++"; 197 | CLANG_ENABLE_MODULES = YES; 198 | CLANG_ENABLE_OBJC_ARC = YES; 199 | CLANG_ENABLE_OBJC_WEAK = YES; 200 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 201 | CLANG_WARN_BOOL_CONVERSION = YES; 202 | CLANG_WARN_COMMA = YES; 203 | CLANG_WARN_CONSTANT_CONVERSION = YES; 204 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 205 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 206 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 207 | CLANG_WARN_EMPTY_BODY = YES; 208 | CLANG_WARN_ENUM_CONVERSION = YES; 209 | CLANG_WARN_INFINITE_RECURSION = YES; 210 | CLANG_WARN_INT_CONVERSION = YES; 211 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 212 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 213 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 214 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 215 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 216 | CLANG_WARN_STRICT_PROTOTYPES = YES; 217 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 218 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 219 | CLANG_WARN_UNREACHABLE_CODE = YES; 220 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 221 | COPY_PHASE_STRIP = NO; 222 | DEBUG_INFORMATION_FORMAT = dwarf; 223 | ENABLE_STRICT_OBJC_MSGSEND = YES; 224 | ENABLE_TESTABILITY = YES; 225 | GCC_C_LANGUAGE_STANDARD = gnu11; 226 | GCC_DYNAMIC_NO_PIC = NO; 227 | GCC_NO_COMMON_BLOCKS = YES; 228 | GCC_OPTIMIZATION_LEVEL = 0; 229 | GCC_PREPROCESSOR_DEFINITIONS = ( 230 | "DEBUG=1", 231 | "$(inherited)", 232 | ); 233 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 234 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 235 | GCC_WARN_UNDECLARED_SELECTOR = YES; 236 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 237 | GCC_WARN_UNUSED_FUNCTION = YES; 238 | GCC_WARN_UNUSED_VARIABLE = YES; 239 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 240 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 241 | MTL_FAST_MATH = YES; 242 | ONLY_ACTIVE_ARCH = YES; 243 | SDKROOT = iphoneos; 244 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 245 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 246 | }; 247 | name = Debug; 248 | }; 249 | CE676F4F22F0B74F006141D1 /* Release */ = { 250 | isa = XCBuildConfiguration; 251 | buildSettings = { 252 | ALWAYS_SEARCH_USER_PATHS = NO; 253 | CLANG_ANALYZER_NONNULL = YES; 254 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 255 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 256 | CLANG_CXX_LIBRARY = "libc++"; 257 | CLANG_ENABLE_MODULES = YES; 258 | CLANG_ENABLE_OBJC_ARC = YES; 259 | CLANG_ENABLE_OBJC_WEAK = YES; 260 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 261 | CLANG_WARN_BOOL_CONVERSION = YES; 262 | CLANG_WARN_COMMA = YES; 263 | CLANG_WARN_CONSTANT_CONVERSION = YES; 264 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 267 | CLANG_WARN_EMPTY_BODY = YES; 268 | CLANG_WARN_ENUM_CONVERSION = YES; 269 | CLANG_WARN_INFINITE_RECURSION = YES; 270 | CLANG_WARN_INT_CONVERSION = YES; 271 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 273 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 275 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 276 | CLANG_WARN_STRICT_PROTOTYPES = YES; 277 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 278 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 279 | CLANG_WARN_UNREACHABLE_CODE = YES; 280 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 281 | COPY_PHASE_STRIP = NO; 282 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 283 | ENABLE_NS_ASSERTIONS = NO; 284 | ENABLE_STRICT_OBJC_MSGSEND = YES; 285 | GCC_C_LANGUAGE_STANDARD = gnu11; 286 | GCC_NO_COMMON_BLOCKS = YES; 287 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 288 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 289 | GCC_WARN_UNDECLARED_SELECTOR = YES; 290 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 291 | GCC_WARN_UNUSED_FUNCTION = YES; 292 | GCC_WARN_UNUSED_VARIABLE = YES; 293 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 294 | MTL_ENABLE_DEBUG_INFO = NO; 295 | MTL_FAST_MATH = YES; 296 | SDKROOT = iphoneos; 297 | SWIFT_COMPILATION_MODE = wholemodule; 298 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 299 | VALIDATE_PRODUCT = YES; 300 | }; 301 | name = Release; 302 | }; 303 | CE676F5122F0B74F006141D1 /* Debug */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | CODE_SIGN_STYLE = Automatic; 308 | DEVELOPMENT_ASSET_PATHS = "BlogIdeaList-SwiftUI/Preview\\ Content"; 309 | DEVELOPMENT_TEAM = 3534V3UJNS; 310 | ENABLE_PREVIEWS = YES; 311 | INFOPLIST_FILE = "BlogIdeaList-SwiftUI/Info.plist"; 312 | LD_RUNPATH_SEARCH_PATHS = ( 313 | "$(inherited)", 314 | "@executable_path/Frameworks", 315 | ); 316 | PRODUCT_BUNDLE_IDENTIFIER = "com.andrewcbancroft.BlogIdeaList-SwiftUI"; 317 | PRODUCT_NAME = "$(TARGET_NAME)"; 318 | SWIFT_VERSION = 5.0; 319 | TARGETED_DEVICE_FAMILY = "1,2"; 320 | }; 321 | name = Debug; 322 | }; 323 | CE676F5222F0B74F006141D1 /* Release */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 327 | CODE_SIGN_STYLE = Automatic; 328 | DEVELOPMENT_ASSET_PATHS = "BlogIdeaList-SwiftUI/Preview\\ Content"; 329 | DEVELOPMENT_TEAM = 3534V3UJNS; 330 | ENABLE_PREVIEWS = YES; 331 | INFOPLIST_FILE = "BlogIdeaList-SwiftUI/Info.plist"; 332 | LD_RUNPATH_SEARCH_PATHS = ( 333 | "$(inherited)", 334 | "@executable_path/Frameworks", 335 | ); 336 | PRODUCT_BUNDLE_IDENTIFIER = "com.andrewcbancroft.BlogIdeaList-SwiftUI"; 337 | PRODUCT_NAME = "$(TARGET_NAME)"; 338 | SWIFT_VERSION = 5.0; 339 | TARGETED_DEVICE_FAMILY = "1,2"; 340 | }; 341 | name = Release; 342 | }; 343 | /* End XCBuildConfiguration section */ 344 | 345 | /* Begin XCConfigurationList section */ 346 | CE676F3422F0B74F006141D1 /* Build configuration list for PBXProject "BlogIdeaList-SwiftUI" */ = { 347 | isa = XCConfigurationList; 348 | buildConfigurations = ( 349 | CE676F4E22F0B74F006141D1 /* Debug */, 350 | CE676F4F22F0B74F006141D1 /* Release */, 351 | ); 352 | defaultConfigurationIsVisible = 0; 353 | defaultConfigurationName = Release; 354 | }; 355 | CE676F5022F0B74F006141D1 /* Build configuration list for PBXNativeTarget "BlogIdeaList-SwiftUI" */ = { 356 | isa = XCConfigurationList; 357 | buildConfigurations = ( 358 | CE676F5122F0B74F006141D1 /* Debug */, 359 | CE676F5222F0B74F006141D1 /* Release */, 360 | ); 361 | defaultConfigurationIsVisible = 0; 362 | defaultConfigurationName = Release; 363 | }; 364 | /* End XCConfigurationList section */ 365 | 366 | /* Begin XCVersionGroup section */ 367 | CE676F4022F0B74F006141D1 /* BlogIdeaList_SwiftUI.xcdatamodeld */ = { 368 | isa = XCVersionGroup; 369 | children = ( 370 | CE676F4122F0B74F006141D1 /* BlogIdeaList_SwiftUI.xcdatamodel */, 371 | ); 372 | currentVersion = CE676F4122F0B74F006141D1 /* BlogIdeaList_SwiftUI.xcdatamodel */; 373 | path = BlogIdeaList_SwiftUI.xcdatamodeld; 374 | sourceTree = ""; 375 | versionGroupType = wrapper.xcdatamodel; 376 | }; 377 | /* End XCVersionGroup section */ 378 | }; 379 | rootObject = CE676F3122F0B74F006141D1 /* Project object */; 380 | } 381 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI.xcodeproj/xcuserdata/andrewcbancroft.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | BlogIdeaList-SwiftUI.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // BlogIdeaList-SwiftUI 4 | // 5 | // Created by Andrew Bancroft on 7/30/19. 6 | // Copyright © 2019 Andrew Bancroft. All rights reserved. 7 | // 8 | // ❇️ Alerts you to Core Data pieces 9 | // ℹ️ Alerts you to general info about what my brain was thinking when I wrote the code 10 | // 11 | 12 | import UIKit 13 | import CoreData 14 | 15 | @UIApplicationMain 16 | class AppDelegate: UIResponder, UIApplicationDelegate { 17 | 18 | 19 | 20 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 21 | // Override point for customization after application launch. 22 | return true 23 | } 24 | 25 | // MARK: UISceneSession Lifecycle 26 | 27 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 28 | // Called when a new scene session is being created. 29 | // Use this method to select a configuration to create the new scene with. 30 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 31 | } 32 | 33 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 34 | // Called when the user discards a scene session. 35 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | 39 | // MARK: - Core Data stack 40 | 41 | // ❇️ This is the out-of-the-box Core Data stack initialization code 42 | lazy var persistentContainer: NSPersistentContainer = { 43 | /* 44 | The persistent container for the application. This implementation 45 | creates and returns a container, having loaded the store for the 46 | application to it. This property is optional since there are legitimate 47 | error conditions that could cause the creation of the store to fail. 48 | */ 49 | let container = NSPersistentContainer(name: "BlogIdeaList_SwiftUI") 50 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 51 | if let error = error as NSError? { 52 | // Replace this implementation with code to handle the error appropriately. 53 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 54 | 55 | /* 56 | Typical reasons for an error here include: 57 | * The parent directory does not exist, cannot be created, or disallows writing. 58 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 59 | * The device is out of space. 60 | * The store could not be migrated to the current model version. 61 | Check the error message to determine what the actual problem was. 62 | */ 63 | fatalError("Unresolved error \(error), \(error.userInfo)") 64 | } 65 | }) 66 | return container 67 | }() 68 | 69 | // MARK: - Core Data Saving support 70 | 71 | func saveContext () { 72 | let context = persistentContainer.viewContext 73 | if context.hasChanges { 74 | do { 75 | try context.save() 76 | } catch { 77 | // Replace this implementation with code to handle the error appropriately. 78 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 79 | let nserror = error as NSError 80 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 81 | } 82 | } 83 | } 84 | 85 | } 86 | 87 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/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 | } -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // BlogIdeaList-SwiftUI 4 | // 5 | // Created by Andrew Bancroft on 7/30/19. 6 | // Copyright © 2019 Andrew Bancroft. All rights reserved. 7 | // 8 | // ❇️ Alerts you to Core Data pieces 9 | // ℹ️ Alerts you to general info about what my brain was thinking when I wrote the code 10 | // 11 | 12 | import SwiftUI 13 | import CoreData 14 | 15 | struct ContentView: View { 16 | // ❇️ Core Data property wrappers 17 | @Environment(\.managedObjectContext) var managedObjectContext 18 | 19 | // ❇️ The BlogIdea class has an `allIdeasFetchRequest` static function that can be used here 20 | @FetchRequest(fetchRequest: BlogIdea.allIdeasFetchRequest()) var blogIdeas: FetchedResults 21 | 22 | // ℹ️ Temporary in-memory storage for adding new blog ideas 23 | @State private var newIdeaTitle = "" 24 | @State private var newIdeaDescription = "" 25 | 26 | // ℹ️ Two sections: Add Blog Idea at the top, followed by a listing of the ideas in the persistent store 27 | var body: some View { 28 | NavigationView { 29 | List { 30 | Section(header: Text("Add Blog Idea")) { 31 | VStack { 32 | VStack { 33 | TextField("Idea title", text: self.$newIdeaTitle) 34 | .textFieldStyle(RoundedBorderTextFieldStyle()) 35 | TextField("Idea description", text: self.$newIdeaDescription) 36 | .textFieldStyle(RoundedBorderTextFieldStyle()) 37 | } 38 | 39 | VStack { 40 | Button(action: ({ 41 | // ❇️ Initializes new BlogIdea and saves using the @Environment's managedObjectContext 42 | let idea = BlogIdea(context: self.managedObjectContext) 43 | idea.ideaTitle = self.newIdeaTitle 44 | idea.ideaDescription = self.newIdeaDescription 45 | 46 | do { 47 | try self.managedObjectContext.save() 48 | } catch { 49 | print(error) 50 | } 51 | 52 | // ℹ️ Reset the temporary in-memory storage variables for the next new blog idea! 53 | self.newIdeaTitle = "" 54 | self.newIdeaDescription = "" 55 | })) { 56 | HStack { 57 | Image(systemName: "plus.circle.fill") 58 | .foregroundColor(.green) 59 | .imageScale(.large) 60 | Text("Add Idea") 61 | } 62 | } 63 | .padding() 64 | } 65 | } 66 | } 67 | .font(.headline) 68 | 69 | 70 | 71 | Section(header: Text("Blog Ideas")) { 72 | ForEach(self.blogIdeas) { blogIdea in 73 | NavigationLink(destination: EditView(blogIdea: blogIdea)) { 74 | VStack(alignment: .leading) { 75 | Text(blogIdea.ideaTitle ?? "") 76 | .font(.headline) 77 | Text(blogIdea.ideaDescription ?? "") 78 | .font(.subheadline) 79 | } 80 | } 81 | } 82 | .onDelete { (indexSet) in // Delete gets triggered by swiping left on a row 83 | // ❇️ Gets the BlogIdea instance out of the blogIdeas array 84 | // ❇️ and deletes it using the @Environment's managedObjectContext 85 | let blogIdeaToDelete = self.blogIdeas[indexSet.first!] 86 | self.managedObjectContext.delete(blogIdeaToDelete) 87 | 88 | do { 89 | try self.managedObjectContext.save() 90 | } catch { 91 | print(error) 92 | } 93 | } 94 | } 95 | .font(.headline) 96 | } 97 | .listStyle(GroupedListStyle()) 98 | .navigationBarTitle(Text("Blog Idea List")) 99 | .navigationBarItems(trailing: EditButton()) 100 | } 101 | } 102 | } 103 | 104 | #if DEBUG 105 | struct ContentView_Previews: PreviewProvider { 106 | static var previews: some View { 107 | 108 | let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 109 | 110 | let blogIdea = BlogIdea.init(context: context) 111 | blogIdea.ideaTitle = "Idea 1" 112 | blogIdea.ideaDescription = "The first idea." 113 | 114 | return ContentView() 115 | .environment(\.managedObjectContext, (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext) 116 | } 117 | } 118 | #endif 119 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/Core Data/BlogIdea.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlogIdea.swift 3 | // BlogIdeaList-SwiftUI 4 | // 5 | // Created by Andrew Bancroft on 7/30/19. 6 | // Copyright © 2019 Andrew Bancroft. All rights reserved. 7 | // 8 | // ❇️ Alerts you to Core Data pieces 9 | // ℹ️ Alerts you to general info about what my brain was thinking when I wrote the code 10 | // 11 | 12 | import Foundation 13 | import CoreData 14 | 15 | // ❇️ BlogIdea code generation is turned OFF in the xcdatamodeld file 16 | public class BlogIdea: NSManagedObject, Identifiable { 17 | @NSManaged public var ideaTitle: String? 18 | @NSManaged public var ideaDescription: String? 19 | } 20 | 21 | extension BlogIdea { 22 | // ❇️ The @FetchRequest property wrapper in the ContentView will call this function 23 | static func allIdeasFetchRequest() -> NSFetchRequest { 24 | let request: NSFetchRequest = BlogIdea.fetchRequest() as! NSFetchRequest 25 | 26 | // ❇️ The @FetchRequest property wrapper in the ContentView requires a sort descriptor 27 | request.sortDescriptors = [NSSortDescriptor(key: "ideaTitle", ascending: true)] 28 | 29 | return request 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/Core Data/BlogIdeaList_SwiftUI.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | BlogIdeaList_SwiftUI.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/Core Data/BlogIdeaList_SwiftUI.xcdatamodeld/BlogIdeaList_SwiftUI.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/EditView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // BlogIdeaList-SwiftUI 4 | // 5 | // Created by Andrew Bancroft on 7/30/19. 6 | // Copyright © 2019 Andrew Bancroft. All rights reserved. 7 | // 8 | // ❇️ Alerts you to Core Data pieces 9 | // ℹ️ Alerts you to general info about what my brain was thinking when I wrote the code 10 | // 11 | 12 | import SwiftUI 13 | import CoreData 14 | 15 | struct EditView: View { 16 | // ❇️ Core Data property wrappers 17 | @Environment(\.managedObjectContext) var managedObjectContext 18 | 19 | // ℹ️ This is used to "go back" when 'Save' is tapped 20 | @Environment(\.presentationMode) var presentationMode 21 | 22 | var blogIdea: BlogIdea 23 | 24 | // ℹ️ Temporary in-memory storage for updating the title and description values of a Blog Idea 25 | @State var updatedIdeaTitle: String = "" 26 | @State var updatedIdeaDescription: String = "" 27 | 28 | var body: some View { 29 | VStack { 30 | VStack { 31 | TextField("Idea title", text: $updatedIdeaTitle) 32 | .textFieldStyle(RoundedBorderTextFieldStyle()) 33 | .onAppear { 34 | // ℹ️ Set the text field's initial value when it appears 35 | self.updatedIdeaTitle = self.blogIdea.ideaTitle ?? "" 36 | } 37 | 38 | TextField("Idea description", text: $updatedIdeaDescription) 39 | .textFieldStyle(RoundedBorderTextFieldStyle()) 40 | .onAppear { 41 | // ℹ️ Set the text field's initial value when it appears 42 | self.updatedIdeaDescription = self.blogIdea.ideaDescription ?? "" 43 | } 44 | } 45 | 46 | VStack { 47 | Button(action: ({ 48 | // ❇️ Set the blog idea's new values from the TextField's Binding and save 49 | self.blogIdea.ideaTitle = self.updatedIdeaTitle 50 | self.blogIdea.ideaDescription = self.updatedIdeaDescription 51 | 52 | do { 53 | try self.managedObjectContext.save() 54 | } catch { 55 | print(error) 56 | } 57 | 58 | self.presentationMode.wrappedValue.dismiss() 59 | })) { 60 | Text("Save") 61 | } 62 | .padding() 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/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 | 1 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 | -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BlogIdeaList-SwiftUI/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // BlogIdeaList-SwiftUI 4 | // 5 | // Created by Andrew Bancroft on 7/30/19. 6 | // Copyright © 2019 Andrew Bancroft. All rights reserved. 7 | // 8 | // ❇️ Alerts you to Core Data pieces 9 | // ℹ️ Alerts you to general info about what my brain was thinking when I wrote the code 10 | // 11 | 12 | import UIKit 13 | import SwiftUI 14 | 15 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 16 | 17 | var window: UIWindow? 18 | 19 | 20 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 21 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 22 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 23 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 24 | 25 | // Use a UIHostingController as window root view controller 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | 29 | // ❇️ Get the managedObjectContext from the persistent container 30 | let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 31 | 32 | // ❇️ Pass it to the ContentView through the managedObjectContext @Environment variable 33 | let contentView = ContentView() 34 | .environment(\.managedObjectContext, managedObjectContext) 35 | 36 | window.rootViewController = UIHostingController(rootView: contentView) 37 | 38 | self.window = window 39 | window.makeKeyAndVisible() 40 | } 41 | } 42 | 43 | func sceneDidDisconnect(_ scene: UIScene) { 44 | // Called as the scene is being released by the system. 45 | // This occurs shortly after the scene enters the background, or when its session is discarded. 46 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 47 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 48 | } 49 | 50 | func sceneDidBecomeActive(_ scene: UIScene) { 51 | // Called when the scene has moved from an inactive state to an active state. 52 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 53 | } 54 | 55 | func sceneWillResignActive(_ scene: UIScene) { 56 | // Called when the scene will move from an active state to an inactive state. 57 | // This may occur due to temporary interruptions (ex. an incoming phone call). 58 | } 59 | 60 | func sceneWillEnterForeground(_ scene: UIScene) { 61 | // Called as the scene transitions from the background to the foreground. 62 | // Use this method to undo the changes made on entering the background. 63 | } 64 | 65 | func sceneDidEnterBackground(_ scene: UIScene) { 66 | // Called as the scene transitions from the foreground to the background. 67 | // Use this method to save data, release shared resources, and store enough scene-specific state information 68 | // to restore the scene back to its current state. 69 | 70 | // Save changes in the application's managed object context when the application transitions to the background. 71 | (UIApplication.shared.delegate as? AppDelegate)?.saveContext() 72 | } 73 | 74 | 75 | } 76 | 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Using Core Data With SwiftUI - An Introduction 2 | 3 | This sample code project is associated with the following guides at [andrewcbancroft.com](https://www.andrewcbancroft.com): 4 | 5 | * [Using Core Data With SwiftUI - An Introduction 6 | ](https://www.andrewcbancroft.com/blog/ios-development/data-persistence/using-core-data-with-swiftui-introduction/) 7 | * [How to Pass NSManagedObjectContext to a SwiftUI View](https://www.andrewcbancroft.com/blog/ios-development/data-persistence/passing-nsmanagedobjectcontext-to-swiftui-view/) 8 | * [How to Use @FetchRequest in SwiftUI](https://www.andrewcbancroft.com/blog/ios-development/data-persistence/how-to-use-fetchrequest-swiftui/) 9 | 10 | ## Overview 11 | 12 | How does Apple intend for us to use Core Data with SwiftUI? 13 | 14 | This project demonstrates how to stitch things together. 15 | 16 | It's a single-view app that can 17 | 18 | * Persist a list of `BlogIdea`s to a Core Data persistent store 19 | * Use the new `@FetchRequest` property wrapper to fetch `BlogIdea`s 20 | * Use the `@Environment`'s `managedObjectContext` to create, update, and delete `BlogIdea`s 21 | 22 | Within the code, 23 | 24 | ❇️ Alerts you to Core Data pieces 25 | 26 | ℹ️ Alerts you to general info about what my brain was thinking when I wrote the code 27 | --------------------------------------------------------------------------------