├── .gitignore ├── NoteSwiftData.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── NoteSwiftData ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Info.plist ├── NoteSwiftData.entitlements └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── NotesMac ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── NotesMac.entitlements └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── README.MD └── Shared ├── Models ├── Filters.swift ├── Note.swift └── Tag.swift ├── NoteSwiftDataApp.swift └── Views ├── NoteListView.swift └── TagListView.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, 4 | .DS_Store 5 | Objective-C.gitignore & Swift.gitignore 6 | 7 | ## User settings 8 | xcuserdata/ 9 | 10 | ## compatibility with Xcode 8 and earlier (ignoring not required starting 11 | Xcode 9) 12 | *.xcscmblueprint 13 | *.xccheckout 14 | 15 | ## compatibility with Xcode 3 and earlier (ignoring not required starting 16 | Xcode 4) 17 | build/ 18 | DerivedData/ 19 | *.moved-aside 20 | *.pbxuser 21 | !default.pbxuser 22 | *.mode1v3 23 | !default.mode1v3 24 | *.mode2v3 25 | !default.mode2v3 26 | *.perspectivev3 27 | !default.perspectivev3 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | 32 | ## App packaging 33 | *.ipa 34 | *.dSYM.zip 35 | *.dSYM 36 | 37 | ## Playgrounds 38 | timeline.xctimeline 39 | playground.xcworkspace 40 | 41 | # Swift Package Manager 42 | # 43 | # Add this line if you want to avoid checking in source code from Swift 44 | Package Manager dependencies. 45 | # Packages/ 46 | # Package.pins 47 | # Package.resolved 48 | # *.xcodeproj 49 | # 50 | # Xcode automatically generates this directory with a .xcworkspacedata 51 | file and xcuserdata 52 | # hence it is not needed unless you have added a package configuration 53 | file to your project 54 | # .swiftpm 55 | 56 | .build/ 57 | 58 | # CocoaPods 59 | # 60 | # We recommend against adding the Pods directory to your .gitignore. 61 | However 62 | # you should judge for yourself, the pros and cons are mentioned at: 63 | # 64 | https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 65 | # 66 | # Pods/ 67 | # 68 | # Add this line if you want to avoid checking in source code from the 69 | Xcode workspace 70 | # *.xcworkspace 71 | 72 | # Carthage 73 | # 74 | # Add this line if you want to avoid checking in source code from Carthage 75 | dependencies. 76 | # Carthage/Checkouts 77 | 78 | Carthage/Build/ 79 | 80 | # Accio dependency management 81 | Dependencies/ 82 | .accio/ 83 | 84 | # fastlane 85 | # 86 | # It is recommended to not store the screenshots in the git repo. 87 | # Instead, use fastlane to re-generate the screenshots whenever they are 88 | needed. 89 | # For more information about the recommended setup visit: 90 | # 91 | https://docs.fastlane.tools/best-practices/source-control/#source-control 92 | 93 | fastlane/report.xml 94 | fastlane/Preview.html 95 | fastlane/screenshots/**/*.png 96 | fastlane/test_output 97 | 98 | # Code Injection 99 | # 100 | # After new code Injection tools there's a generated folder 101 | /iOSInjectionProject 102 | # https://github.com/johnno1962/injectionforxcode 103 | 104 | iOSInjectionProject/ 105 | 106 | -------------------------------------------------------------------------------- /NoteSwiftData.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8B8E67B92A3ED65A00B602DC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B8E67B82A3ED65A00B602DC /* Assets.xcassets */; }; 11 | 8B8E67BC2A3ED65A00B602DC /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B8E67BB2A3ED65A00B602DC /* Preview Assets.xcassets */; }; 12 | 8B8E67C42A3ED69700B602DC /* NoteListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19D72A30C9E000CE1554 /* NoteListView.swift */; }; 13 | 8B8E67C52A3ED69700B602DC /* Filters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19DB2A30D8B900CE1554 /* Filters.swift */; }; 14 | 8B8E67C62A3ED69700B602DC /* Note.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19D22A30C6F000CE1554 /* Note.swift */; }; 15 | 8B8E67C72A3ED69700B602DC /* NoteSwiftDataApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19C32A30C29800CE1554 /* NoteSwiftDataApp.swift */; }; 16 | 8B8E67C82A3ED69700B602DC /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19D42A30C74200CE1554 /* Tag.swift */; }; 17 | 8B8E67C92A3ED69700B602DC /* TagListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19D92A30CD9B00CE1554 /* TagListView.swift */; }; 18 | 8BED19C42A30C29800CE1554 /* NoteSwiftDataApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19C32A30C29800CE1554 /* NoteSwiftDataApp.swift */; }; 19 | 8BED19C82A30C29800CE1554 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8BED19C72A30C29800CE1554 /* Assets.xcassets */; }; 20 | 8BED19CB2A30C29800CE1554 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8BED19CA2A30C29800CE1554 /* Preview Assets.xcassets */; }; 21 | 8BED19D32A30C6F000CE1554 /* Note.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19D22A30C6F000CE1554 /* Note.swift */; }; 22 | 8BED19D52A30C74200CE1554 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19D42A30C74200CE1554 /* Tag.swift */; }; 23 | 8BED19D82A30C9E000CE1554 /* NoteListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19D72A30C9E000CE1554 /* NoteListView.swift */; }; 24 | 8BED19DA2A30CD9B00CE1554 /* TagListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19D92A30CD9B00CE1554 /* TagListView.swift */; }; 25 | 8BED19DC2A30D8B900CE1554 /* Filters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BED19DB2A30D8B900CE1554 /* Filters.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 8B8E67B22A3ED65900B602DC /* NotesMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NotesMac.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 8B8E67B82A3ED65A00B602DC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | 8B8E67BB2A3ED65A00B602DC /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 32 | 8B8E67BF2A3ED65A00B602DC /* NotesMac.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotesMac.entitlements; sourceTree = ""; }; 33 | 8BD6ED0C2A3CA1F5007F6F2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 34 | 8BD6ED0D2A3CA20D007F6F2F /* NoteSwiftData.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NoteSwiftData.entitlements; sourceTree = ""; }; 35 | 8BED19C02A30C29800CE1554 /* NoteSwiftData.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NoteSwiftData.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 8BED19C32A30C29800CE1554 /* NoteSwiftDataApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteSwiftDataApp.swift; sourceTree = ""; }; 37 | 8BED19C72A30C29800CE1554 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 38 | 8BED19CA2A30C29800CE1554 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 39 | 8BED19D22A30C6F000CE1554 /* Note.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Note.swift; sourceTree = ""; }; 40 | 8BED19D42A30C74200CE1554 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = ""; }; 41 | 8BED19D72A30C9E000CE1554 /* NoteListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteListView.swift; sourceTree = ""; }; 42 | 8BED19D92A30CD9B00CE1554 /* TagListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagListView.swift; sourceTree = ""; }; 43 | 8BED19DB2A30D8B900CE1554 /* Filters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filters.swift; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 8B8E67AF2A3ED65900B602DC /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | 8BED19BD2A30C29800CE1554 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 8B8E67B32A3ED65900B602DC /* NotesMac */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 8B8E67B82A3ED65A00B602DC /* Assets.xcassets */, 68 | 8B8E67BF2A3ED65A00B602DC /* NotesMac.entitlements */, 69 | 8B8E67BA2A3ED65A00B602DC /* Preview Content */, 70 | ); 71 | path = NotesMac; 72 | sourceTree = ""; 73 | }; 74 | 8B8E67BA2A3ED65A00B602DC /* Preview Content */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 8B8E67BB2A3ED65A00B602DC /* Preview Assets.xcassets */, 78 | ); 79 | path = "Preview Content"; 80 | sourceTree = ""; 81 | }; 82 | 8B8E67C32A3ED67600B602DC /* Shared */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 8BED19C32A30C29800CE1554 /* NoteSwiftDataApp.swift */, 86 | 8BED19D62A30C9D300CE1554 /* Views */, 87 | 8BED19D12A30C6E400CE1554 /* Models */, 88 | ); 89 | path = Shared; 90 | sourceTree = ""; 91 | }; 92 | 8BED19B72A30C29800CE1554 = { 93 | isa = PBXGroup; 94 | children = ( 95 | 8B8E67C32A3ED67600B602DC /* Shared */, 96 | 8BED19C22A30C29800CE1554 /* NoteSwiftData */, 97 | 8B8E67B32A3ED65900B602DC /* NotesMac */, 98 | 8BED19C12A30C29800CE1554 /* Products */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | 8BED19C12A30C29800CE1554 /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 8BED19C02A30C29800CE1554 /* NoteSwiftData.app */, 106 | 8B8E67B22A3ED65900B602DC /* NotesMac.app */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | 8BED19C22A30C29800CE1554 /* NoteSwiftData */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 8BD6ED0D2A3CA20D007F6F2F /* NoteSwiftData.entitlements */, 115 | 8BD6ED0C2A3CA1F5007F6F2F /* Info.plist */, 116 | 8BED19C72A30C29800CE1554 /* Assets.xcassets */, 117 | 8BED19C92A30C29800CE1554 /* Preview Content */, 118 | ); 119 | path = NoteSwiftData; 120 | sourceTree = ""; 121 | }; 122 | 8BED19C92A30C29800CE1554 /* Preview Content */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 8BED19CA2A30C29800CE1554 /* Preview Assets.xcassets */, 126 | ); 127 | path = "Preview Content"; 128 | sourceTree = ""; 129 | }; 130 | 8BED19D12A30C6E400CE1554 /* Models */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 8BED19D22A30C6F000CE1554 /* Note.swift */, 134 | 8BED19D42A30C74200CE1554 /* Tag.swift */, 135 | 8BED19DB2A30D8B900CE1554 /* Filters.swift */, 136 | ); 137 | path = Models; 138 | sourceTree = ""; 139 | }; 140 | 8BED19D62A30C9D300CE1554 /* Views */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 8BED19D72A30C9E000CE1554 /* NoteListView.swift */, 144 | 8BED19D92A30CD9B00CE1554 /* TagListView.swift */, 145 | ); 146 | path = Views; 147 | sourceTree = ""; 148 | }; 149 | /* End PBXGroup section */ 150 | 151 | /* Begin PBXNativeTarget section */ 152 | 8B8E67B12A3ED65900B602DC /* NotesMac */ = { 153 | isa = PBXNativeTarget; 154 | buildConfigurationList = 8B8E67C22A3ED65A00B602DC /* Build configuration list for PBXNativeTarget "NotesMac" */; 155 | buildPhases = ( 156 | 8B8E67AE2A3ED65900B602DC /* Sources */, 157 | 8B8E67AF2A3ED65900B602DC /* Frameworks */, 158 | 8B8E67B02A3ED65900B602DC /* Resources */, 159 | ); 160 | buildRules = ( 161 | ); 162 | dependencies = ( 163 | ); 164 | name = NotesMac; 165 | productName = NotesMac; 166 | productReference = 8B8E67B22A3ED65900B602DC /* NotesMac.app */; 167 | productType = "com.apple.product-type.application"; 168 | }; 169 | 8BED19BF2A30C29800CE1554 /* NoteSwiftData */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = 8BED19CE2A30C29800CE1554 /* Build configuration list for PBXNativeTarget "NoteSwiftData" */; 172 | buildPhases = ( 173 | 8BED19BC2A30C29800CE1554 /* Sources */, 174 | 8BED19BD2A30C29800CE1554 /* Frameworks */, 175 | 8BED19BE2A30C29800CE1554 /* Resources */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | ); 181 | name = NoteSwiftData; 182 | productName = NoteSwiftData; 183 | productReference = 8BED19C02A30C29800CE1554 /* NoteSwiftData.app */; 184 | productType = "com.apple.product-type.application"; 185 | }; 186 | /* End PBXNativeTarget section */ 187 | 188 | /* Begin PBXProject section */ 189 | 8BED19B82A30C29800CE1554 /* Project object */ = { 190 | isa = PBXProject; 191 | attributes = { 192 | BuildIndependentTargetsInParallel = 1; 193 | LastSwiftUpdateCheck = 1500; 194 | LastUpgradeCheck = 1500; 195 | TargetAttributes = { 196 | 8B8E67B12A3ED65900B602DC = { 197 | CreatedOnToolsVersion = 15.0; 198 | }; 199 | 8BED19BF2A30C29800CE1554 = { 200 | CreatedOnToolsVersion = 15.0; 201 | }; 202 | }; 203 | }; 204 | buildConfigurationList = 8BED19BB2A30C29800CE1554 /* Build configuration list for PBXProject "NoteSwiftData" */; 205 | compatibilityVersion = "Xcode 14.0"; 206 | developmentRegion = en; 207 | hasScannedForEncodings = 0; 208 | knownRegions = ( 209 | en, 210 | Base, 211 | ); 212 | mainGroup = 8BED19B72A30C29800CE1554; 213 | productRefGroup = 8BED19C12A30C29800CE1554 /* Products */; 214 | projectDirPath = ""; 215 | projectRoot = ""; 216 | targets = ( 217 | 8BED19BF2A30C29800CE1554 /* NoteSwiftData */, 218 | 8B8E67B12A3ED65900B602DC /* NotesMac */, 219 | ); 220 | }; 221 | /* End PBXProject section */ 222 | 223 | /* Begin PBXResourcesBuildPhase section */ 224 | 8B8E67B02A3ED65900B602DC /* Resources */ = { 225 | isa = PBXResourcesBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | 8B8E67BC2A3ED65A00B602DC /* Preview Assets.xcassets in Resources */, 229 | 8B8E67B92A3ED65A00B602DC /* Assets.xcassets in Resources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | 8BED19BE2A30C29800CE1554 /* Resources */ = { 234 | isa = PBXResourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | 8BED19CB2A30C29800CE1554 /* Preview Assets.xcassets in Resources */, 238 | 8BED19C82A30C29800CE1554 /* Assets.xcassets in Resources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXResourcesBuildPhase section */ 243 | 244 | /* Begin PBXSourcesBuildPhase section */ 245 | 8B8E67AE2A3ED65900B602DC /* Sources */ = { 246 | isa = PBXSourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | 8B8E67C42A3ED69700B602DC /* NoteListView.swift in Sources */, 250 | 8B8E67C92A3ED69700B602DC /* TagListView.swift in Sources */, 251 | 8B8E67C52A3ED69700B602DC /* Filters.swift in Sources */, 252 | 8B8E67C82A3ED69700B602DC /* Tag.swift in Sources */, 253 | 8B8E67C62A3ED69700B602DC /* Note.swift in Sources */, 254 | 8B8E67C72A3ED69700B602DC /* NoteSwiftDataApp.swift in Sources */, 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | 8BED19BC2A30C29800CE1554 /* Sources */ = { 259 | isa = PBXSourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | 8BED19DC2A30D8B900CE1554 /* Filters.swift in Sources */, 263 | 8BED19D52A30C74200CE1554 /* Tag.swift in Sources */, 264 | 8BED19DA2A30CD9B00CE1554 /* TagListView.swift in Sources */, 265 | 8BED19D82A30C9E000CE1554 /* NoteListView.swift in Sources */, 266 | 8BED19C42A30C29800CE1554 /* NoteSwiftDataApp.swift in Sources */, 267 | 8BED19D32A30C6F000CE1554 /* Note.swift in Sources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | /* End PBXSourcesBuildPhase section */ 272 | 273 | /* Begin XCBuildConfiguration section */ 274 | 8B8E67C02A3ED65A00B602DC /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 278 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 279 | CODE_SIGN_ENTITLEMENTS = NotesMac/NotesMac.entitlements; 280 | CODE_SIGN_STYLE = Automatic; 281 | COMBINE_HIDPI_IMAGES = YES; 282 | CURRENT_PROJECT_VERSION = 1; 283 | DEVELOPMENT_ASSET_PATHS = "\"NotesMac/Preview Content\""; 284 | DEVELOPMENT_TEAM = 5C2XD9H2JS; 285 | ENABLE_HARDENED_RUNTIME = YES; 286 | ENABLE_PREVIEWS = YES; 287 | GENERATE_INFOPLIST_FILE = YES; 288 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 289 | LD_RUNPATH_SEARCH_PATHS = ( 290 | "$(inherited)", 291 | "@executable_path/../Frameworks", 292 | ); 293 | MACOSX_DEPLOYMENT_TARGET = 14.0; 294 | MARKETING_VERSION = 1.0; 295 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.NotesMac; 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | SDKROOT = macosx; 298 | SWIFT_EMIT_LOC_STRINGS = YES; 299 | SWIFT_VERSION = 5.0; 300 | }; 301 | name = Debug; 302 | }; 303 | 8B8E67C12A3ED65A00B602DC /* Release */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 308 | CODE_SIGN_ENTITLEMENTS = NotesMac/NotesMac.entitlements; 309 | CODE_SIGN_STYLE = Automatic; 310 | COMBINE_HIDPI_IMAGES = YES; 311 | CURRENT_PROJECT_VERSION = 1; 312 | DEVELOPMENT_ASSET_PATHS = "\"NotesMac/Preview Content\""; 313 | DEVELOPMENT_TEAM = 5C2XD9H2JS; 314 | ENABLE_HARDENED_RUNTIME = YES; 315 | ENABLE_PREVIEWS = YES; 316 | GENERATE_INFOPLIST_FILE = YES; 317 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 318 | LD_RUNPATH_SEARCH_PATHS = ( 319 | "$(inherited)", 320 | "@executable_path/../Frameworks", 321 | ); 322 | MACOSX_DEPLOYMENT_TARGET = 14.0; 323 | MARKETING_VERSION = 1.0; 324 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.NotesMac; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | SDKROOT = macosx; 327 | SWIFT_EMIT_LOC_STRINGS = YES; 328 | SWIFT_VERSION = 5.0; 329 | }; 330 | name = Release; 331 | }; 332 | 8BED19CC2A30C29800CE1554 /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 337 | CLANG_ANALYZER_NONNULL = YES; 338 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 340 | CLANG_ENABLE_MODULES = YES; 341 | CLANG_ENABLE_OBJC_ARC = YES; 342 | CLANG_ENABLE_OBJC_WEAK = YES; 343 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 344 | CLANG_WARN_BOOL_CONVERSION = YES; 345 | CLANG_WARN_COMMA = YES; 346 | CLANG_WARN_CONSTANT_CONVERSION = YES; 347 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 348 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 349 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 350 | CLANG_WARN_EMPTY_BODY = YES; 351 | CLANG_WARN_ENUM_CONVERSION = YES; 352 | CLANG_WARN_INFINITE_RECURSION = YES; 353 | CLANG_WARN_INT_CONVERSION = YES; 354 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 355 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 356 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 358 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 359 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 360 | CLANG_WARN_STRICT_PROTOTYPES = YES; 361 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 362 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 363 | CLANG_WARN_UNREACHABLE_CODE = YES; 364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 365 | COPY_PHASE_STRIP = NO; 366 | DEBUG_INFORMATION_FORMAT = dwarf; 367 | ENABLE_STRICT_OBJC_MSGSEND = YES; 368 | ENABLE_TESTABILITY = YES; 369 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 370 | GCC_C_LANGUAGE_STANDARD = gnu17; 371 | GCC_DYNAMIC_NO_PIC = NO; 372 | GCC_NO_COMMON_BLOCKS = YES; 373 | GCC_OPTIMIZATION_LEVEL = 0; 374 | GCC_PREPROCESSOR_DEFINITIONS = ( 375 | "DEBUG=1", 376 | "$(inherited)", 377 | ); 378 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 379 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 380 | GCC_WARN_UNDECLARED_SELECTOR = YES; 381 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 382 | GCC_WARN_UNUSED_FUNCTION = YES; 383 | GCC_WARN_UNUSED_VARIABLE = YES; 384 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 385 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 386 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 387 | MTL_FAST_MATH = YES; 388 | ONLY_ACTIVE_ARCH = YES; 389 | SDKROOT = iphoneos; 390 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 391 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 392 | }; 393 | name = Debug; 394 | }; 395 | 8BED19CD2A30C29800CE1554 /* Release */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ALWAYS_SEARCH_USER_PATHS = NO; 399 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 400 | CLANG_ANALYZER_NONNULL = YES; 401 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 402 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 403 | CLANG_ENABLE_MODULES = YES; 404 | CLANG_ENABLE_OBJC_ARC = YES; 405 | CLANG_ENABLE_OBJC_WEAK = YES; 406 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 407 | CLANG_WARN_BOOL_CONVERSION = YES; 408 | CLANG_WARN_COMMA = YES; 409 | CLANG_WARN_CONSTANT_CONVERSION = YES; 410 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 411 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 412 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 413 | CLANG_WARN_EMPTY_BODY = YES; 414 | CLANG_WARN_ENUM_CONVERSION = YES; 415 | CLANG_WARN_INFINITE_RECURSION = YES; 416 | CLANG_WARN_INT_CONVERSION = YES; 417 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 418 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 419 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 421 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 422 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 423 | CLANG_WARN_STRICT_PROTOTYPES = YES; 424 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 425 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 426 | CLANG_WARN_UNREACHABLE_CODE = YES; 427 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 428 | COPY_PHASE_STRIP = NO; 429 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 430 | ENABLE_NS_ASSERTIONS = NO; 431 | ENABLE_STRICT_OBJC_MSGSEND = YES; 432 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 433 | GCC_C_LANGUAGE_STANDARD = gnu17; 434 | GCC_NO_COMMON_BLOCKS = YES; 435 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 436 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 437 | GCC_WARN_UNDECLARED_SELECTOR = YES; 438 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 439 | GCC_WARN_UNUSED_FUNCTION = YES; 440 | GCC_WARN_UNUSED_VARIABLE = YES; 441 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 442 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 443 | MTL_ENABLE_DEBUG_INFO = NO; 444 | MTL_FAST_MATH = YES; 445 | SDKROOT = iphoneos; 446 | SWIFT_COMPILATION_MODE = wholemodule; 447 | VALIDATE_PRODUCT = YES; 448 | }; 449 | name = Release; 450 | }; 451 | 8BED19CF2A30C29800CE1554 /* Debug */ = { 452 | isa = XCBuildConfiguration; 453 | buildSettings = { 454 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 455 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 456 | CODE_SIGN_ENTITLEMENTS = NoteSwiftData/NoteSwiftData.entitlements; 457 | CODE_SIGN_STYLE = Automatic; 458 | CURRENT_PROJECT_VERSION = 1; 459 | DEVELOPMENT_ASSET_PATHS = "\"NoteSwiftData/Preview Content\""; 460 | DEVELOPMENT_TEAM = 5C2XD9H2JS; 461 | ENABLE_PREVIEWS = YES; 462 | GENERATE_INFOPLIST_FILE = YES; 463 | INFOPLIST_FILE = NoteSwiftData/Info.plist; 464 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 465 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 466 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 467 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 468 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 469 | LD_RUNPATH_SEARCH_PATHS = ( 470 | "$(inherited)", 471 | "@executable_path/Frameworks", 472 | ); 473 | MARKETING_VERSION = 1.0; 474 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.NoteSwiftData; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | SWIFT_EMIT_LOC_STRINGS = YES; 477 | SWIFT_VERSION = 5.0; 478 | TARGETED_DEVICE_FAMILY = "1,2"; 479 | }; 480 | name = Debug; 481 | }; 482 | 8BED19D02A30C29800CE1554 /* Release */ = { 483 | isa = XCBuildConfiguration; 484 | buildSettings = { 485 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 486 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 487 | CODE_SIGN_ENTITLEMENTS = NoteSwiftData/NoteSwiftData.entitlements; 488 | CODE_SIGN_STYLE = Automatic; 489 | CURRENT_PROJECT_VERSION = 1; 490 | DEVELOPMENT_ASSET_PATHS = "\"NoteSwiftData/Preview Content\""; 491 | DEVELOPMENT_TEAM = 5C2XD9H2JS; 492 | ENABLE_PREVIEWS = YES; 493 | GENERATE_INFOPLIST_FILE = YES; 494 | INFOPLIST_FILE = NoteSwiftData/Info.plist; 495 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 496 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 497 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 498 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 499 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 500 | LD_RUNPATH_SEARCH_PATHS = ( 501 | "$(inherited)", 502 | "@executable_path/Frameworks", 503 | ); 504 | MARKETING_VERSION = 1.0; 505 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.NoteSwiftData; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | SWIFT_EMIT_LOC_STRINGS = YES; 508 | SWIFT_VERSION = 5.0; 509 | TARGETED_DEVICE_FAMILY = "1,2"; 510 | }; 511 | name = Release; 512 | }; 513 | /* End XCBuildConfiguration section */ 514 | 515 | /* Begin XCConfigurationList section */ 516 | 8B8E67C22A3ED65A00B602DC /* Build configuration list for PBXNativeTarget "NotesMac" */ = { 517 | isa = XCConfigurationList; 518 | buildConfigurations = ( 519 | 8B8E67C02A3ED65A00B602DC /* Debug */, 520 | 8B8E67C12A3ED65A00B602DC /* Release */, 521 | ); 522 | defaultConfigurationIsVisible = 0; 523 | defaultConfigurationName = Release; 524 | }; 525 | 8BED19BB2A30C29800CE1554 /* Build configuration list for PBXProject "NoteSwiftData" */ = { 526 | isa = XCConfigurationList; 527 | buildConfigurations = ( 528 | 8BED19CC2A30C29800CE1554 /* Debug */, 529 | 8BED19CD2A30C29800CE1554 /* Release */, 530 | ); 531 | defaultConfigurationIsVisible = 0; 532 | defaultConfigurationName = Release; 533 | }; 534 | 8BED19CE2A30C29800CE1554 /* Build configuration list for PBXNativeTarget "NoteSwiftData" */ = { 535 | isa = XCConfigurationList; 536 | buildConfigurations = ( 537 | 8BED19CF2A30C29800CE1554 /* Debug */, 538 | 8BED19D02A30C29800CE1554 /* Release */, 539 | ); 540 | defaultConfigurationIsVisible = 0; 541 | defaultConfigurationName = Release; 542 | }; 543 | /* End XCConfigurationList section */ 544 | }; 545 | rootObject = 8BED19B82A30C29800CE1554 /* Project object */; 546 | } 547 | -------------------------------------------------------------------------------- /NoteSwiftData.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NoteSwiftData.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /NoteSwiftData/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 | -------------------------------------------------------------------------------- /NoteSwiftData/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 | -------------------------------------------------------------------------------- /NoteSwiftData/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /NoteSwiftData/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIBackgroundModes 6 | 7 | remote-notification 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /NoteSwiftData/NoteSwiftData.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.files.user-selected.read-only 6 | 7 | com.apple.security.app-sandbox 8 | 9 | aps-environment 10 | development 11 | com.apple.developer.icloud-container-identifiers 12 | 13 | iCloud.com.alfianlosari.noteapp 14 | 15 | com.apple.developer.icloud-services 16 | 17 | CloudKit 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /NoteSwiftData/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /NotesMac/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 | -------------------------------------------------------------------------------- /NotesMac/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /NotesMac/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /NotesMac/NotesMac.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.com.alfianlosari.noteapp 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | com.apple.security.app-sandbox 16 | 17 | com.apple.security.files.user-selected.read-only 18 | 19 | com.apple.security.network.client 20 | 21 | com.apple.security.network.server 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /NotesMac/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # SwiftData Sample Note App 2 | 3 | This is a demo app where it uses many to many relationship schema between Note and Tag models. It also use Query with custom predicate, sort order, and order by that can be updated by users in runtime. 4 | 5 | CloudKit Sync is also enabled to enable data sync across user devices. Make sure to provide your own CloudKit container by registering for Apple Paid Member Developer account. Make sure to login using your iCloud account on devices to enable syncing. 6 | 7 | ![Alt text](https://i.ibb.co/ckBTJDf/promo.png "image") 8 | 9 | ## Video tutorial 10 | [YouTube](https://www.youtube.com/watch?v=hG6O5pVLc54) 11 | -------------------------------------------------------------------------------- /Shared/Models/Filters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Filters.swift 3 | // NoteSwiftData 4 | // 5 | // Created by Alfian Losari on 07/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | enum NoteSortBy: Identifiable, CaseIterable { 11 | var id: Self { self } 12 | case createdAt 13 | case content 14 | 15 | var text: String { 16 | switch self { 17 | case .createdAt: return "Created at" 18 | case .content: return "Content" 19 | } 20 | } 21 | 22 | } 23 | 24 | 25 | enum OrderBy: Identifiable, CaseIterable { 26 | 27 | var id: Self { self } 28 | case ascending 29 | case descending 30 | 31 | var text: String { 32 | switch self { 33 | case .ascending: 34 | return "Ascending" 35 | case .descending: 36 | return "Descending" 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Shared/Models/Note.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Note.swift 3 | // NoteSwiftData 4 | // 5 | // Created by Alfian Losari on 07/06/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftData 10 | 11 | @Model 12 | class Note { 13 | var id: String? 14 | var content: String = "" 15 | var createdAt: Date = Date() 16 | 17 | @Relationship(inverse: \Tag.notes) var tags: [Tag]? 18 | 19 | init(id: String, content: String, createdAt: Date, tags: [Tag]) { 20 | self.id = id 21 | self.content = content 22 | self.createdAt = createdAt 23 | self.tags = tags 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Shared/Models/Tag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tag.swift 3 | // NoteSwiftData 4 | // 5 | // Created by Alfian Losari on 07/06/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftData 10 | 11 | @Model 12 | class Tag { 13 | var id: String? 14 | var name: String = "" 15 | 16 | @Relationship var notes: [Note]? 17 | @Attribute(.transient) var isChecked = false 18 | 19 | init(id: String, name: String, notes: [Note]) { 20 | self.id = id 21 | self.name = name 22 | self.notes = notes 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Shared/NoteSwiftDataApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoteSwiftDataApp.swift 3 | // NoteSwiftData 4 | // 5 | // Created by Alfian Losari on 07/06/23. 6 | // 7 | 8 | import SwiftData 9 | import SwiftUI 10 | 11 | @main 12 | struct NoteSwiftDataApp: App { 13 | 14 | @State var noteSearchText = "" 15 | @State var noteSortBy = NoteSortBy.createdAt 16 | @State var noteOrderBy = OrderBy.descending 17 | 18 | @State var tagSearchText = "" 19 | @State var tagOrderBy = OrderBy.ascending 20 | 21 | var body: some Scene { 22 | WindowGroup { 23 | TabView { 24 | noteList 25 | tagList 26 | } 27 | #if os(macOS) 28 | .frame(maxWidth: 800, alignment: .center) 29 | #endif 30 | .modelContainer(for: [ 31 | Note.self, 32 | Tag.self 33 | ]) 34 | 35 | } 36 | } 37 | 38 | var noteList: some View { 39 | NavigationStack { 40 | NoteListView(allNotes: noteListQuery) 41 | .searchable(text: $noteSearchText, prompt: "Search") 42 | #if os(macOS) 43 | .textCase(.none) 44 | #else 45 | .textInputAutocapitalization(.never) 46 | #endif 47 | .navigationTitle("Notes") 48 | .toolbar { 49 | ToolbarItemGroup { 50 | Menu { 51 | Picker("Sort by", selection: $noteSortBy) { 52 | ForEach(NoteSortBy.allCases) { 53 | Text($0.text).id($0) 54 | } 55 | } 56 | } label: { 57 | Label(noteSortBy.text, systemImage: "line.horizontal.3.decrease.circle") 58 | } 59 | 60 | Menu { 61 | Picker("Order by", selection: $noteOrderBy) { 62 | ForEach(OrderBy.allCases) { 63 | Text($0.text).id($0) 64 | } 65 | } 66 | } label: { 67 | Label(noteOrderBy.text, systemImage: "arrow.up.arrow.down") 68 | } 69 | } 70 | } 71 | } 72 | .tabItem { Label("Notes", systemImage: "note") } 73 | } 74 | 75 | var noteListQuery: Query { 76 | let sortOrder: SortOrder = noteOrderBy == .ascending ? .forward : .reverse 77 | var predicate: Predicate? 78 | if !noteSearchText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { 79 | predicate = .init(#Predicate { $0.content.contains(noteSearchText) }) 80 | } 81 | if noteSortBy == .content { 82 | return Query(filter: predicate, sort: \.content, order: sortOrder) 83 | } else { 84 | return Query(filter: predicate, sort: \.createdAt, order: sortOrder) 85 | } 86 | } 87 | 88 | var tagList: some View { 89 | NavigationStack { 90 | TagListView(allTags: tagListQuery) 91 | .searchable(text: $tagSearchText, prompt: "Search") 92 | #if os(macOS) 93 | .textCase(.none) 94 | #else 95 | .textInputAutocapitalization(.never) 96 | #endif 97 | .navigationTitle("Tags") 98 | .toolbar { 99 | ToolbarItemGroup { 100 | Menu { 101 | Picker("Order by", selection: $tagOrderBy) { 102 | ForEach(OrderBy.allCases) { 103 | Text($0.text).id($0) 104 | } 105 | } 106 | } label: { 107 | Label(tagOrderBy.text, systemImage: "arrow.up.arrow.down") 108 | } 109 | } 110 | } 111 | } 112 | .tabItem { Label("Tags", systemImage: "tag") } 113 | } 114 | 115 | var tagListQuery: Query { 116 | let sortOrder: SortOrder = tagOrderBy == .ascending ? .forward : .reverse 117 | var predicate: Predicate? 118 | if !tagSearchText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { 119 | predicate = .init(#Predicate { $0.name.contains(tagSearchText) }) 120 | } 121 | return Query(filter: predicate, sort: \.name, order: sortOrder) 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /Shared/Views/NoteListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoteListView.swift 3 | // NoteSwiftData 4 | // 5 | // Created by Alfian Losari on 07/06/23. 6 | // 7 | 8 | import SwiftData 9 | import SwiftUI 10 | 11 | struct NoteListView: View { 12 | 13 | @Environment(\.modelContext) private var context 14 | @Query(sort: \.createdAt, order: .reverse) var allNotes: [Note] 15 | @Query(sort: \.name, order: .forward) var allTags: [Tag] 16 | @State var noteText = "" 17 | 18 | var body: some View { 19 | List { 20 | Section { 21 | DisclosureGroup("Create a note") { 22 | TextField("Enter text", text: $noteText, axis: .vertical) 23 | .lineLimit(2...4) 24 | 25 | DisclosureGroup("Tag With") { 26 | if allTags.isEmpty { 27 | Text("You don't have any tags yet. Please create one from Tags tab") 28 | .foregroundStyle(Color.gray) 29 | } 30 | 31 | ForEach(allTags) { tag in 32 | HStack { 33 | Text(tag.name) 34 | if tag.isChecked { 35 | Spacer() 36 | Image(systemName: "checkmark.circle") 37 | .symbolRenderingMode(.multicolor) 38 | } 39 | } 40 | .frame(maxWidth: .infinity, alignment: .leading) 41 | .contentShape(Rectangle()) 42 | .onTapGesture { 43 | tag.isChecked.toggle() 44 | } 45 | } 46 | } 47 | 48 | Button("Save") { 49 | createNote() 50 | } 51 | .disabled(noteText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) 52 | } 53 | } 54 | 55 | Section { 56 | if allNotes.isEmpty { 57 | ContentUnavailableView("You don't have any notes yet", systemImage: "note") 58 | } else { 59 | ForEach(allNotes) { note in 60 | VStack(alignment: .leading) { 61 | Text(note.content) 62 | if let tags = note.tags, tags.count > 0 { 63 | Text("Tags:" + tags.map { $0.name }.joined(separator: ", ")) 64 | .font(.caption) 65 | } 66 | Text(note.createdAt, style: .time) 67 | .font(.caption) 68 | 69 | } 70 | } 71 | .onDelete { indexSet in 72 | indexSet.forEach { index in 73 | context.delete(allNotes[index]) 74 | } 75 | try? context.save() 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | func createNote() { 83 | var tags = [Tag]() 84 | allTags.forEach { tag in 85 | if tag.isChecked { 86 | tags.append(tag) 87 | tag.isChecked = false 88 | } 89 | } 90 | 91 | let note = Note(id: UUID().uuidString, content: noteText, createdAt: .now, tags: tags) 92 | context.insert(note) 93 | try? context.save() 94 | noteText = "" 95 | } 96 | } 97 | 98 | #Preview { 99 | NoteListView() 100 | } 101 | -------------------------------------------------------------------------------- /Shared/Views/TagListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagListView.swift 3 | // NoteSwiftData 4 | // 5 | // Created by Alfian Losari on 07/06/23. 6 | // 7 | 8 | import SwiftData 9 | import SwiftUI 10 | 11 | struct TagListView: View { 12 | 13 | @Environment(\.modelContext) private var context 14 | @Query(sort: \.name, order: .forward) var allTags: [Tag] 15 | @State var tagText = "" 16 | 17 | var body: some View { 18 | List { 19 | Section { 20 | DisclosureGroup("Create a tag") { 21 | TextField("Enter text", text: $tagText, axis: .vertical) 22 | .lineLimit(2...4) 23 | 24 | Button("Save") { 25 | createTag() 26 | } 27 | .disabled(tagText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) 28 | } 29 | } 30 | 31 | Section { 32 | if allTags.isEmpty { 33 | ContentUnavailableView("You don't have any tags yet", systemImage: "tag") 34 | } else { 35 | ForEach(allTags) { tag in 36 | if let notes = tag.notes, notes.count > 0 { 37 | DisclosureGroup("\(tag.name) (\(notes.count))") { 38 | ForEach(notes) { note in 39 | Text(note.content) 40 | } 41 | .onDelete { indexSet in 42 | indexSet.forEach { index in 43 | context.delete(notes[index]) 44 | } 45 | try? context.save() 46 | } 47 | } 48 | } else { 49 | Text(tag.name) 50 | } 51 | } 52 | .onDelete { indexSet in 53 | indexSet.forEach { index in 54 | context.delete(allTags[index]) 55 | } 56 | try? context.save() 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | func createTag() { 64 | let tag = Tag(id: UUID().uuidString, name: tagText, notes: []) 65 | context.insert(tag) 66 | try? context.save() 67 | tagText = "" 68 | } 69 | } 70 | 71 | #Preview { 72 | TagListView() 73 | } 74 | --------------------------------------------------------------------------------