├── Catalyst Photo Grid.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── Catalyst Photo Grid.xcscheme ├── Catalyst Photo Grid ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-1024.png │ │ ├── icon-120.png │ │ ├── icon-152.png │ │ ├── icon-167.png │ │ ├── icon-180.png │ │ ├── icon-29.png │ │ ├── icon-40.png │ │ ├── icon-58.png │ │ ├── icon-60.png │ │ ├── icon-76.png │ │ ├── icon-80.png │ │ ├── icon-87.png │ │ └── icon20.png │ ├── Contents.json │ └── Images │ │ ├── 0.imageset │ │ ├── Contents.json │ │ └── almondmilk.jpg │ │ ├── 1.imageset │ │ ├── Contents.json │ │ └── avocado.jpg │ │ ├── 10.imageset │ │ ├── Contents.json │ │ └── orange.jpg │ │ ├── 11.imageset │ │ ├── Contents.json │ │ └── papaya.jpg │ │ ├── 12.imageset │ │ ├── Contents.json │ │ └── peanutbutter.jpg │ │ ├── 13.imageset │ │ ├── Contents.json │ │ └── pineapple.jpg │ │ ├── 14.imageset │ │ ├── Contents.json │ │ └── raspberries.jpg │ │ ├── 15.imageset │ │ ├── Contents.json │ │ └── spinach.jpg │ │ ├── 16.imageset │ │ ├── Contents.json │ │ └── strawberry.jpg │ │ ├── 17.imageset │ │ ├── Contents.json │ │ └── water.jpg │ │ ├── 18.imageset │ │ ├── Contents.json │ │ └── watermelon.jpg │ │ ├── 2.imageset │ │ ├── Contents.json │ │ └── banana.jpg │ │ ├── 3.imageset │ │ ├── Contents.json │ │ └── blueberries.jpg │ │ ├── 4.imageset │ │ ├── Contents.json │ │ └── carrot.jpg │ │ ├── 5.imageset │ │ ├── Contents.json │ │ └── chocolate.jpg │ │ ├── 6.imageset │ │ ├── Contents.json │ │ └── coconut.jpg │ │ ├── 7.imageset │ │ ├── Contents.json │ │ └── kiwi.jpg │ │ ├── 8.imageset │ │ ├── Contents.json │ │ └── lemon.jpg │ │ ├── 9.imageset │ │ ├── Contents.json │ │ └── mango.jpg │ │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── Localizable.strings └── Source │ ├── Application │ ├── CGAAppDelegate+MenuBuilder.swift │ ├── CGAAppDelegate.swift │ ├── CGASceneDelegate+NSToolbar.swift │ └── CGASceneDelegate.swift │ ├── Main │ ├── CGAMainViewController.swift │ └── Grid View │ │ ├── CGAGridViewCell.swift │ │ └── CGAGridViewController.swift │ └── Number+Scaling.swift └── README.md /Catalyst Photo Grid.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B07B4C31270F68B700A7F560 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B07B4C30270F68B700A7F560 /* Assets.xcassets */; }; 11 | B07B4C35270F68B700A7F560 /* CGAAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07B4C34270F68B700A7F560 /* CGAAppDelegate.swift */; }; 12 | B07B4C37270F68B700A7F560 /* CGASceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07B4C36270F68B700A7F560 /* CGASceneDelegate.swift */; }; 13 | B07B4C39270F68B700A7F560 /* CGASceneDelegate+NSToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07B4C38270F68B700A7F560 /* CGASceneDelegate+NSToolbar.swift */; }; 14 | B07B4C3C270F68B700A7F560 /* CGAMainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07B4C3B270F68B700A7F560 /* CGAMainViewController.swift */; }; 15 | B07B4C3F270F68B700A7F560 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B07B4C3D270F68B700A7F560 /* LaunchScreen.storyboard */; }; 16 | B07B4C47270F68F900A7F560 /* Number+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07B4C46270F68F900A7F560 /* Number+Scaling.swift */; }; 17 | B07B4C4B270F6A6500A7F560 /* CGAGridViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07B4C4A270F6A6500A7F560 /* CGAGridViewController.swift */; }; 18 | B07B4C54270F6B7400A7F560 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B07B4C53270F6B7400A7F560 /* Localizable.strings */; }; 19 | B07B4C56270F6E7700A7F560 /* CGAGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07B4C55270F6E7700A7F560 /* CGAGridViewCell.swift */; }; 20 | B0DC7E7C270F757800E14BBD /* CGAAppDelegate+MenuBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0DC7E7B270F757800E14BBD /* CGAAppDelegate+MenuBuilder.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | B07B4C2D270F68B700A7F560 /* Catalyst Photo Grid.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Catalyst Photo Grid.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | B07B4C30270F68B700A7F560 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | B07B4C34270F68B700A7F560 /* CGAAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGAAppDelegate.swift; sourceTree = ""; }; 27 | B07B4C36270F68B700A7F560 /* CGASceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGASceneDelegate.swift; sourceTree = ""; }; 28 | B07B4C38270F68B700A7F560 /* CGASceneDelegate+NSToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGASceneDelegate+NSToolbar.swift"; sourceTree = ""; }; 29 | B07B4C3B270F68B700A7F560 /* CGAMainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGAMainViewController.swift; sourceTree = ""; }; 30 | B07B4C3E270F68B700A7F560 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | B07B4C40270F68B700A7F560 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | B07B4C46270F68F900A7F560 /* Number+Scaling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Number+Scaling.swift"; sourceTree = ""; }; 33 | B07B4C4A270F6A6500A7F560 /* CGAGridViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGAGridViewController.swift; sourceTree = ""; }; 34 | B07B4C53270F6B7400A7F560 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; 35 | B07B4C55270F6E7700A7F560 /* CGAGridViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGAGridViewCell.swift; sourceTree = ""; }; 36 | B0DC7E7B270F757800E14BBD /* CGAAppDelegate+MenuBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGAAppDelegate+MenuBuilder.swift"; sourceTree = ""; }; 37 | B0DC7E7E270F76B800E14BBD /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | B07B4C2A270F68B700A7F560 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | B07B4C24270F68B700A7F560 = { 52 | isa = PBXGroup; 53 | children = ( 54 | B0DC7E7E270F76B800E14BBD /* README.md */, 55 | B07B4C2F270F68B700A7F560 /* Catalyst Photo Grid */, 56 | B07B4C2E270F68B700A7F560 /* Products */, 57 | ); 58 | sourceTree = ""; 59 | }; 60 | B07B4C2E270F68B700A7F560 /* Products */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | B07B4C2D270F68B700A7F560 /* Catalyst Photo Grid.app */, 64 | ); 65 | name = Products; 66 | sourceTree = ""; 67 | }; 68 | B07B4C2F270F68B700A7F560 /* Catalyst Photo Grid */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | B07B4C30270F68B700A7F560 /* Assets.xcassets */, 72 | B07B4C3D270F68B700A7F560 /* LaunchScreen.storyboard */, 73 | B07B4C40270F68B700A7F560 /* Info.plist */, 74 | B07B4C53270F6B7400A7F560 /* Localizable.strings */, 75 | B07B4C32270F68B700A7F560 /* Source */, 76 | ); 77 | path = "Catalyst Photo Grid"; 78 | sourceTree = ""; 79 | }; 80 | B07B4C32270F68B700A7F560 /* Source */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | B07B4C46270F68F900A7F560 /* Number+Scaling.swift */, 84 | B07B4C33270F68B700A7F560 /* Application */, 85 | B07B4C3A270F68B700A7F560 /* Main */, 86 | ); 87 | path = Source; 88 | sourceTree = ""; 89 | }; 90 | B07B4C33270F68B700A7F560 /* Application */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | B07B4C34270F68B700A7F560 /* CGAAppDelegate.swift */, 94 | B0DC7E7B270F757800E14BBD /* CGAAppDelegate+MenuBuilder.swift */, 95 | B07B4C36270F68B700A7F560 /* CGASceneDelegate.swift */, 96 | B07B4C38270F68B700A7F560 /* CGASceneDelegate+NSToolbar.swift */, 97 | ); 98 | path = Application; 99 | sourceTree = ""; 100 | }; 101 | B07B4C3A270F68B700A7F560 /* Main */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | B07B4C3B270F68B700A7F560 /* CGAMainViewController.swift */, 105 | B0DC7E7A270F727000E14BBD /* Grid View */, 106 | ); 107 | path = Main; 108 | sourceTree = ""; 109 | }; 110 | B0DC7E7A270F727000E14BBD /* Grid View */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | B07B4C4A270F6A6500A7F560 /* CGAGridViewController.swift */, 114 | B07B4C55270F6E7700A7F560 /* CGAGridViewCell.swift */, 115 | ); 116 | path = "Grid View"; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | B07B4C2C270F68B700A7F560 /* Catalyst Photo Grid */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = B07B4C43270F68B700A7F560 /* Build configuration list for PBXNativeTarget "Catalyst Photo Grid" */; 125 | buildPhases = ( 126 | B07B4C29270F68B700A7F560 /* Sources */, 127 | B07B4C2A270F68B700A7F560 /* Frameworks */, 128 | B07B4C2B270F68B700A7F560 /* Resources */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = "Catalyst Photo Grid"; 135 | productName = "Catalyst Grid App"; 136 | productReference = B07B4C2D270F68B700A7F560 /* Catalyst Photo Grid.app */; 137 | productType = "com.apple.product-type.application"; 138 | }; 139 | /* End PBXNativeTarget section */ 140 | 141 | /* Begin PBXProject section */ 142 | B07B4C25270F68B700A7F560 /* Project object */ = { 143 | isa = PBXProject; 144 | attributes = { 145 | BuildIndependentTargetsInParallel = 1; 146 | CLASSPREFIX = CGA; 147 | LastSwiftUpdateCheck = 1300; 148 | LastUpgradeCheck = 1300; 149 | TargetAttributes = { 150 | B07B4C2C270F68B700A7F560 = { 151 | CreatedOnToolsVersion = 13.0; 152 | }; 153 | }; 154 | }; 155 | buildConfigurationList = B07B4C28270F68B700A7F560 /* Build configuration list for PBXProject "Catalyst Photo Grid" */; 156 | compatibilityVersion = "Xcode 13.0"; 157 | developmentRegion = en; 158 | hasScannedForEncodings = 0; 159 | knownRegions = ( 160 | en, 161 | Base, 162 | ); 163 | mainGroup = B07B4C24270F68B700A7F560; 164 | productRefGroup = B07B4C2E270F68B700A7F560 /* Products */; 165 | projectDirPath = ""; 166 | projectRoot = ""; 167 | targets = ( 168 | B07B4C2C270F68B700A7F560 /* Catalyst Photo Grid */, 169 | ); 170 | }; 171 | /* End PBXProject section */ 172 | 173 | /* Begin PBXResourcesBuildPhase section */ 174 | B07B4C2B270F68B700A7F560 /* Resources */ = { 175 | isa = PBXResourcesBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | B07B4C31270F68B700A7F560 /* Assets.xcassets in Resources */, 179 | B07B4C54270F6B7400A7F560 /* Localizable.strings in Resources */, 180 | B07B4C3F270F68B700A7F560 /* LaunchScreen.storyboard in Resources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXResourcesBuildPhase section */ 185 | 186 | /* Begin PBXSourcesBuildPhase section */ 187 | B07B4C29270F68B700A7F560 /* Sources */ = { 188 | isa = PBXSourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | B07B4C35270F68B700A7F560 /* CGAAppDelegate.swift in Sources */, 192 | B07B4C3C270F68B700A7F560 /* CGAMainViewController.swift in Sources */, 193 | B07B4C37270F68B700A7F560 /* CGASceneDelegate.swift in Sources */, 194 | B0DC7E7C270F757800E14BBD /* CGAAppDelegate+MenuBuilder.swift in Sources */, 195 | B07B4C4B270F6A6500A7F560 /* CGAGridViewController.swift in Sources */, 196 | B07B4C47270F68F900A7F560 /* Number+Scaling.swift in Sources */, 197 | B07B4C56270F6E7700A7F560 /* CGAGridViewCell.swift in Sources */, 198 | B07B4C39270F68B700A7F560 /* CGASceneDelegate+NSToolbar.swift in Sources */, 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | /* End PBXSourcesBuildPhase section */ 203 | 204 | /* Begin PBXVariantGroup section */ 205 | B07B4C3D270F68B700A7F560 /* LaunchScreen.storyboard */ = { 206 | isa = PBXVariantGroup; 207 | children = ( 208 | B07B4C3E270F68B700A7F560 /* Base */, 209 | ); 210 | name = LaunchScreen.storyboard; 211 | sourceTree = ""; 212 | }; 213 | /* End PBXVariantGroup section */ 214 | 215 | /* Begin XCBuildConfiguration section */ 216 | B07B4C41270F68B700A7F560 /* Debug */ = { 217 | isa = XCBuildConfiguration; 218 | buildSettings = { 219 | ALWAYS_SEARCH_USER_PATHS = NO; 220 | CLANG_ANALYZER_NONNULL = YES; 221 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 222 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 223 | CLANG_CXX_LIBRARY = "libc++"; 224 | CLANG_ENABLE_MODULES = YES; 225 | CLANG_ENABLE_OBJC_ARC = YES; 226 | CLANG_ENABLE_OBJC_WEAK = YES; 227 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 228 | CLANG_WARN_BOOL_CONVERSION = YES; 229 | CLANG_WARN_COMMA = YES; 230 | CLANG_WARN_CONSTANT_CONVERSION = YES; 231 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 234 | CLANG_WARN_EMPTY_BODY = YES; 235 | CLANG_WARN_ENUM_CONVERSION = YES; 236 | CLANG_WARN_INFINITE_RECURSION = YES; 237 | CLANG_WARN_INT_CONVERSION = YES; 238 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 239 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 240 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 241 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 242 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 243 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 244 | CLANG_WARN_STRICT_PROTOTYPES = YES; 245 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 246 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 250 | COPY_PHASE_STRIP = NO; 251 | DEBUG_INFORMATION_FORMAT = dwarf; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | ENABLE_TESTABILITY = YES; 254 | GCC_C_LANGUAGE_STANDARD = gnu11; 255 | GCC_DYNAMIC_NO_PIC = NO; 256 | GCC_NO_COMMON_BLOCKS = YES; 257 | GCC_OPTIMIZATION_LEVEL = 0; 258 | GCC_PREPROCESSOR_DEFINITIONS = ( 259 | "DEBUG=1", 260 | "$(inherited)", 261 | ); 262 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 263 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 264 | GCC_WARN_UNDECLARED_SELECTOR = YES; 265 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 266 | GCC_WARN_UNUSED_FUNCTION = YES; 267 | GCC_WARN_UNUSED_VARIABLE = YES; 268 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 269 | MTL_FAST_MATH = YES; 270 | ONLY_ACTIVE_ARCH = YES; 271 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 272 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 273 | SWIFT_VERSION = 5.0; 274 | }; 275 | name = Debug; 276 | }; 277 | B07B4C42270F68B700A7F560 /* Release */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ALWAYS_SEARCH_USER_PATHS = NO; 281 | CLANG_ANALYZER_NONNULL = YES; 282 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 283 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 284 | CLANG_CXX_LIBRARY = "libc++"; 285 | CLANG_ENABLE_MODULES = YES; 286 | CLANG_ENABLE_OBJC_ARC = YES; 287 | CLANG_ENABLE_OBJC_WEAK = YES; 288 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 289 | CLANG_WARN_BOOL_CONVERSION = YES; 290 | CLANG_WARN_COMMA = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 293 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 294 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 295 | CLANG_WARN_EMPTY_BODY = YES; 296 | CLANG_WARN_ENUM_CONVERSION = YES; 297 | CLANG_WARN_INFINITE_RECURSION = YES; 298 | CLANG_WARN_INT_CONVERSION = YES; 299 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 300 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 301 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 302 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 303 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 304 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 305 | CLANG_WARN_STRICT_PROTOTYPES = YES; 306 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 307 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 308 | CLANG_WARN_UNREACHABLE_CODE = YES; 309 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 310 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 311 | COPY_PHASE_STRIP = NO; 312 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 313 | ENABLE_NS_ASSERTIONS = NO; 314 | ENABLE_STRICT_OBJC_MSGSEND = YES; 315 | GCC_C_LANGUAGE_STANDARD = gnu11; 316 | GCC_NO_COMMON_BLOCKS = YES; 317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 319 | GCC_WARN_UNDECLARED_SELECTOR = YES; 320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 321 | GCC_WARN_UNUSED_FUNCTION = YES; 322 | GCC_WARN_UNUSED_VARIABLE = YES; 323 | MTL_ENABLE_DEBUG_INFO = NO; 324 | MTL_FAST_MATH = YES; 325 | SWIFT_COMPILATION_MODE = wholemodule; 326 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 327 | SWIFT_VERSION = 5.0; 328 | }; 329 | name = Release; 330 | }; 331 | B07B4C44270F68B700A7F560 /* Debug */ = { 332 | isa = XCBuildConfiguration; 333 | buildSettings = { 334 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 335 | CLANG_STATIC_ANALYZER_MODE = deep; 336 | CODE_SIGN_IDENTITY = "iPhone Developer"; 337 | CODE_SIGN_STYLE = Automatic; 338 | CURRENT_PROJECT_VERSION = 1; 339 | DEVELOPMENT_TEAM = 2ZDN69KUUV; 340 | GCC_TREAT_WARNINGS_AS_ERRORS = deep; 341 | GCC_WARN_PEDANTIC = deep; 342 | GENERATE_INFOPLIST_FILE = YES; 343 | INFOPLIST_FILE = "$(PRODUCT_NAME)/Info.plist"; 344 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 345 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; 346 | LD_RUNPATH_SEARCH_PATHS = ( 347 | "$(inherited)", 348 | "@executable_path/Frameworks", 349 | ); 350 | MARKETING_VERSION = 1.0; 351 | PRODUCT_BUNDLE_IDENTIFIER = "com.highcaffeinecontent.Catalyst-Photo-Grid"; 352 | PRODUCT_NAME = "Catalyst Photo Grid"; 353 | RUN_CLANG_STATIC_ANALYZER = YES; 354 | SDKROOT = iphoneos; 355 | SUPPORTS_MACCATALYST = YES; 356 | SWIFT_EMIT_LOC_STRINGS = YES; 357 | SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; 358 | SWIFT_VERSION = 5.0; 359 | TARGETED_DEVICE_FAMILY = "1,2,6"; 360 | }; 361 | name = Debug; 362 | }; 363 | B07B4C45270F68B700A7F560 /* Release */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 367 | CLANG_STATIC_ANALYZER_MODE = deep; 368 | CODE_SIGN_IDENTITY = "iPhone Developer"; 369 | CODE_SIGN_STYLE = Automatic; 370 | CURRENT_PROJECT_VERSION = 1; 371 | DEVELOPMENT_TEAM = 2ZDN69KUUV; 372 | GCC_TREAT_WARNINGS_AS_ERRORS = deep; 373 | GCC_WARN_PEDANTIC = deep; 374 | GENERATE_INFOPLIST_FILE = YES; 375 | INFOPLIST_FILE = "$(PRODUCT_NAME)/Info.plist"; 376 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 377 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; 378 | LD_RUNPATH_SEARCH_PATHS = ( 379 | "$(inherited)", 380 | "@executable_path/Frameworks", 381 | ); 382 | MARKETING_VERSION = 1.0; 383 | PRODUCT_BUNDLE_IDENTIFIER = "com.highcaffeinecontent.Catalyst-Photo-Grid"; 384 | PRODUCT_NAME = "Catalyst Photo Grid"; 385 | RUN_CLANG_STATIC_ANALYZER = YES; 386 | SDKROOT = iphoneos; 387 | SUPPORTS_MACCATALYST = YES; 388 | SWIFT_EMIT_LOC_STRINGS = YES; 389 | SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; 390 | SWIFT_VERSION = 5.0; 391 | TARGETED_DEVICE_FAMILY = "1,2,6"; 392 | VALIDATE_PRODUCT = YES; 393 | }; 394 | name = Release; 395 | }; 396 | /* End XCBuildConfiguration section */ 397 | 398 | /* Begin XCConfigurationList section */ 399 | B07B4C28270F68B700A7F560 /* Build configuration list for PBXProject "Catalyst Photo Grid" */ = { 400 | isa = XCConfigurationList; 401 | buildConfigurations = ( 402 | B07B4C41270F68B700A7F560 /* Debug */, 403 | B07B4C42270F68B700A7F560 /* Release */, 404 | ); 405 | defaultConfigurationIsVisible = 0; 406 | defaultConfigurationName = Release; 407 | }; 408 | B07B4C43270F68B700A7F560 /* Build configuration list for PBXNativeTarget "Catalyst Photo Grid" */ = { 409 | isa = XCConfigurationList; 410 | buildConfigurations = ( 411 | B07B4C44270F68B700A7F560 /* Debug */, 412 | B07B4C45270F68B700A7F560 /* Release */, 413 | ); 414 | defaultConfigurationIsVisible = 0; 415 | defaultConfigurationName = Release; 416 | }; 417 | /* End XCConfigurationList section */ 418 | }; 419 | rootObject = B07B4C25270F68B700A7F560 /* Project object */; 420 | } 421 | -------------------------------------------------------------------------------- /Catalyst Photo Grid.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Catalyst Photo Grid.xcodeproj/xcshareddata/xcschemes/Catalyst Photo Grid.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "iphone", 6 | "filename": "icon-40.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size": "20x20", 11 | "idiom": "iphone", 12 | "filename": "icon-60.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size": "29x29", 17 | "idiom": "iphone", 18 | "filename": "icon-58.png", 19 | "scale": "2x" 20 | }, 21 | { 22 | "size": "29x29", 23 | "idiom": "iphone", 24 | "filename": "icon-87.png", 25 | "scale": "3x" 26 | }, 27 | { 28 | "size": "40x40", 29 | "idiom": "iphone", 30 | "filename": "icon-80.png", 31 | "scale": "2x" 32 | }, 33 | { 34 | "size": "40x40", 35 | "idiom": "iphone", 36 | "filename": "icon-120.png", 37 | "scale": "3x" 38 | }, 39 | { 40 | "size": "60x60", 41 | "idiom": "iphone", 42 | "filename": "icon-120.png", 43 | "scale": "2x" 44 | }, 45 | { 46 | "size": "60x60", 47 | "idiom": "iphone", 48 | "filename": "icon-180.png", 49 | "scale": "3x" 50 | }, 51 | { 52 | "size": "20x20", 53 | "idiom": "ipad", 54 | "filename": "icon20.png", 55 | "scale": "1x" 56 | }, 57 | { 58 | "size": "20x20", 59 | "idiom": "ipad", 60 | "filename": "icon-40.png", 61 | "scale": "2x" 62 | }, 63 | { 64 | "size": "29x29", 65 | "idiom": "ipad", 66 | "filename": "icon-29.png", 67 | "scale": "1x" 68 | }, 69 | { 70 | "size": "29x29", 71 | "idiom": "ipad", 72 | "filename": "icon-58.png", 73 | "scale": "2x" 74 | }, 75 | { 76 | "size": "40x40", 77 | "idiom": "ipad", 78 | "filename": "icon-40.png", 79 | "scale": "1x" 80 | }, 81 | { 82 | "size": "40x40", 83 | "idiom": "ipad", 84 | "filename": "icon-80.png", 85 | "scale": "2x" 86 | }, 87 | { 88 | "size": "76x76", 89 | "idiom": "ipad", 90 | "filename": "icon-76.png", 91 | "scale": "1x" 92 | }, 93 | { 94 | "size": "76x76", 95 | "idiom": "ipad", 96 | "filename": "icon-152.png", 97 | "scale": "2x" 98 | }, 99 | { 100 | "size": "83.5x83.5", 101 | "idiom": "ipad", 102 | "filename": "icon-167.png", 103 | "scale": "2x" 104 | }, 105 | { 106 | "size": "1024x1024", 107 | "idiom": "ios-marketing", 108 | "filename": "icon-1024.png", 109 | "scale": "1x" 110 | } 111 | ], 112 | "info": { 113 | "version": 1, 114 | "author": "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-120.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-152.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-167.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-180.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-58.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-60.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-80.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-87.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon20.png -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "almondmilk.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/0.imageset/almondmilk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/0.imageset/almondmilk.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "avocado.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/1.imageset/avocado.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/1.imageset/avocado.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/10.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "orange.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/10.imageset/orange.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/10.imageset/orange.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/11.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "papaya.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/11.imageset/papaya.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/11.imageset/papaya.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/12.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "peanutbutter.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/12.imageset/peanutbutter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/12.imageset/peanutbutter.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/13.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pineapple.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/13.imageset/pineapple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/13.imageset/pineapple.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/14.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "raspberries.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/14.imageset/raspberries.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/14.imageset/raspberries.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/15.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "spinach.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/15.imageset/spinach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/15.imageset/spinach.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/16.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "strawberry.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/16.imageset/strawberry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/16.imageset/strawberry.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/17.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "water.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/17.imageset/water.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/17.imageset/water.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/18.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "watermelon.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/18.imageset/watermelon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/18.imageset/watermelon.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "banana.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/2.imageset/banana.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/2.imageset/banana.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blueberries.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/3.imageset/blueberries.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/3.imageset/blueberries.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "carrot.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/4.imageset/carrot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/4.imageset/carrot.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "chocolate.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/5.imageset/chocolate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/5.imageset/chocolate.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "coconut.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/6.imageset/coconut.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/6.imageset/coconut.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "kiwi.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/7.imageset/kiwi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/7.imageset/kiwi.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "lemon.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/8.imageset/lemon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/8.imageset/lemon.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "mango.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/9.imageset/mango.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/3b84cd7f2b183e37def71f7d73f0380010f685cb/Catalyst Photo Grid/Assets.xcassets/Images/9.imageset/mango.jpg -------------------------------------------------------------------------------- /Catalyst Photo Grid/Assets.xcassets/Images/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ITSAppUsesNonExemptEncryption 6 | 7 | LSRequiresIPhoneOS 8 | 9 | UIApplicationSceneManifest 10 | 11 | UIApplicationSupportsMultipleScenes 12 | 13 | UISceneConfigurations 14 | 15 | UIWindowSceneSessionRoleApplication 16 | 17 | 18 | UISceneConfigurationName 19 | Default Configuration 20 | UISceneDelegateClassName 21 | $(PRODUCT_MODULE_NAME).CGASceneDelegate 22 | 23 | 24 | 25 | 26 | UIApplicationSupportsIndirectInputEvents 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIRequiredDeviceCapabilities 31 | 32 | armv7 33 | 34 | UISupportedInterfaceOrientations 35 | 36 | UIInterfaceOrientationPortrait 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Catalyst Grid App 4 | 5 | Created by Steven Troughton-Smith on 07/10/2021. 6 | 7 | */ 8 | 9 | "SWITCH_ASPECT" = "Show thumbnails as square or in full aspect ratio"; 10 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Source/Application/CGAAppDelegate+MenuBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGAAppDelegate+NSToolbar.swift 3 | // Catalyst Grid App 4 | // 5 | // Created by Steven Troughton-Smith on 07/10/2021. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension CGAAppDelegate { 12 | 13 | override func buildMenu(with builder: UIMenuBuilder) { 14 | if builder.system == UIMenuSystem.context { 15 | return 16 | } 17 | 18 | super.buildMenu(with: builder) 19 | 20 | builder.remove(menu: .format) 21 | builder.remove(menu: .toolbar) 22 | builder.remove(menu: .newScene) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Source/Application/CGAAppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGAAppDelegate.swift 3 | // Catalyst Grid App 4 | // 5 | // Created by Steven Troughton-Smith on 07/10/2021. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class CGAAppDelegate: UIResponder, UIApplicationDelegate { 13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 14 | return true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Source/Application/CGASceneDelegate+NSToolbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGASceneDelegate+NSToolbar.swift 3 | // Catalyst Grid App 4 | // 5 | // Created by Steven Troughton-Smith on 07/10/2021. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | #if targetEnvironment(macCatalyst) 12 | import AppKit 13 | 14 | extension NSToolbarItem.Identifier { 15 | static let switchAspect = NSToolbarItem.Identifier("com.example.switchAspect") 16 | } 17 | 18 | extension CGASceneDelegate: NSToolbarDelegate { 19 | 20 | func generateDynamicToolbarItems() { 21 | 22 | let barItem = UIBarButtonItem(image: UIImage(systemName: "rectangle.arrowtriangle.2.inward"), style: .plain, target: nil, action: NSSelectorFromString("toggleAspect:")) 23 | /* 24 | NSToolbarItemGroup does not auto-enable/disable buttons based on the responder chain, so we need an NSToolbarItem here instead 25 | */ 26 | 27 | let item = NSToolbarItem(itemIdentifier: .switchAspect, barButtonItem: barItem) 28 | 29 | item.label = NSLocalizedString("SWITCH_ASPECT", comment: "") 30 | item.toolTip = NSLocalizedString("SWITCH_ASPECT", comment: "") 31 | item.isBordered = true 32 | item.isNavigational = true 33 | 34 | displayModeToolbarItem = item 35 | 36 | watchForDisplayModeChanges() 37 | } 38 | 39 | func toolbarItems() -> [NSToolbarItem.Identifier] { 40 | return [.switchAspect] 41 | } 42 | 43 | func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 44 | return toolbarItems() 45 | } 46 | 47 | func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 48 | return toolbarItems() 49 | } 50 | 51 | func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { 52 | if itemIdentifier == .switchAspect { 53 | 54 | return displayModeToolbarItem 55 | } 56 | 57 | return NSToolbarItem(itemIdentifier: itemIdentifier) 58 | } 59 | 60 | func watchForDisplayModeChanges() { 61 | NotificationCenter.default.addObserver(forName: .displayModeChanged, object: nil, queue: nil) { notification in 62 | 63 | guard let displayModeToolbarItem = self.displayModeToolbarItem else { return } 64 | 65 | if let displayMode = notification.object as? String { 66 | if displayMode == "0" { 67 | displayModeToolbarItem.image = UIImage(systemName:"rectangle.arrowtriangle.2.inward") 68 | } 69 | else { 70 | displayModeToolbarItem.image = UIImage(systemName:"rectangle.arrowtriangle.2.outward") 71 | } 72 | } 73 | 74 | } 75 | } 76 | } 77 | #endif 78 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Source/Application/CGASceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGASceneDelegate.swift 3 | // Catalyst Grid App 4 | // 5 | // Created by Steven Troughton-Smith on 07/10/2021. 6 | // 7 | // 8 | 9 | import UIKit 10 | #if targetEnvironment(macCatalyst) 11 | import AppKit 12 | #endif 13 | 14 | extension NSNotification.Name { 15 | static let displayModeChanged = NSNotification.Name("DisplayModeChanged") 16 | } 17 | 18 | class CGASceneDelegate: UIResponder, UIWindowSceneDelegate { 19 | var window: UIWindow? 20 | 21 | #if targetEnvironment(macCatalyst) 22 | var displayModeToolbarItem:NSToolbarItem? 23 | #endif 24 | 25 | func buildPrimaryUI() { 26 | 27 | guard let window = window, let windowScene = window.windowScene else { return } 28 | 29 | let mainVC = CGAMainViewController() 30 | mainVC.sceneDelegate = self 31 | 32 | window.rootViewController = mainVC 33 | 34 | #if targetEnvironment(macCatalyst) 35 | 36 | mainVC.navigationController?.isNavigationBarHidden = true 37 | 38 | generateDynamicToolbarItems() 39 | 40 | let toolbar = NSToolbar(identifier: NSToolbar.Identifier("CGASceneDelegate.Toolbar")) 41 | toolbar.delegate = self 42 | toolbar.displayMode = .iconOnly 43 | toolbar.allowsUserCustomization = false 44 | 45 | 46 | windowScene.titlebar?.toolbar = toolbar 47 | windowScene.titlebar?.toolbarStyle = .unified 48 | windowScene.titlebar?.titleVisibility = .hidden 49 | #endif 50 | } 51 | 52 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 53 | guard let windowScene = scene as? UIWindowScene else { 54 | fatalError("Expected scene of type UIWindowScene but got an unexpected type") 55 | } 56 | window = UIWindow(windowScene: windowScene) 57 | 58 | if let window = window { 59 | 60 | buildPrimaryUI() 61 | window.makeKeyAndVisible() 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Source/Main/CGAMainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGAMainViewController.swift 3 | // Catalyst Grid App 4 | // 5 | // Created by Steven Troughton-Smith on 07/10/2021. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | final class CGAMainViewController: UIViewController, UINavigationControllerDelegate { 12 | 13 | let gridVC = CGAGridViewController() 14 | var gridNC:UINavigationController! 15 | 16 | var sceneDelegate:CGASceneDelegate? 17 | 18 | init() { 19 | super.init(nibName: nil, bundle: nil) 20 | 21 | gridNC = UINavigationController(rootViewController: gridVC) 22 | 23 | gridNC.delegate = self 24 | 25 | #if targetEnvironment(macCatalyst) 26 | gridNC.isNavigationBarHidden = true 27 | #endif 28 | 29 | addChild(gridNC) 30 | 31 | view.addSubview(gridNC.view) 32 | } 33 | 34 | required init?(coder aDecoder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | // MARK: Responder Chain - 39 | 40 | override var canBecomeFirstResponder: Bool { 41 | return true 42 | } 43 | 44 | override func responds(to aSelector: Selector!) -> Bool { 45 | 46 | /* 47 | This is the magic that determines whether the back button is enabled/disabled 48 | */ 49 | 50 | if aSelector == #selector(goBack(_:)) { 51 | return (gridNC.viewControllers.count > 1) ? true : false 52 | } 53 | 54 | return super.responds(to: aSelector) 55 | } 56 | 57 | @objc func goBack(_ sender:Any) { 58 | gridNC.popViewController(animated: true) 59 | } 60 | 61 | // MARK: Layout - 62 | 63 | override func viewDidLayoutSubviews() { 64 | super.viewDidLayoutSubviews() 65 | 66 | var inspectorWidth = UIFloat(260) 67 | 68 | /* Hide the inspector sidebar if the view gets too small */ 69 | if view.bounds.width < (inspectorWidth*2) { 70 | inspectorWidth = 0 71 | } 72 | 73 | let contentFrame = view.bounds 74 | 75 | gridNC.view.frame = contentFrame 76 | } 77 | 78 | // MARK: Navigation (macOS) - 79 | 80 | /* On macOS, change the window title to match the navigation controller's current item */ 81 | func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { 82 | #if targetEnvironment(macCatalyst) 83 | guard let sceneDelegate = sceneDelegate else { return } 84 | 85 | sceneDelegate.window?.windowScene?.title = viewController.title 86 | 87 | #endif 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Source/Main/Grid View/CGAGridViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGAGridViewCell.swift 3 | // Catalyst Grid App 4 | // 5 | // Created by Steven Troughton-Smith on 07/10/2021. 6 | // 7 | 8 | import UIKit 9 | import CoreGraphics 10 | 11 | class CGAGridViewCell: UICollectionViewCell { 12 | 13 | /* 14 | Keyboard focus & cell selection are two different things, so conceptually it is 15 | important to remember that they may not necessarily be linked. In this example, 16 | we're using two different 'selection' styles to underscore that. 17 | 18 | As of macOS 12.1, multi-select (drag-select) handles focus state incorrectly, 19 | so as soon as that behavior is improved this distinction may not be necessary. 20 | */ 21 | 22 | let focusRingView = UIView() 23 | let selectionRingView = UIView() 24 | 25 | let imageView = UIImageView() 26 | 27 | var displayMode:CGAGridViewController.DisplayMode = .square { 28 | didSet { 29 | layoutSubviews() 30 | } 31 | } 32 | 33 | @objc var showFocusRing:Bool = false { 34 | didSet { 35 | if showFocusRing { 36 | focusRingView.alpha = 1 37 | } 38 | else { 39 | focusRingView.alpha = 0 40 | } 41 | } 42 | } 43 | 44 | @objc var showSelectionRing:Bool = false { 45 | didSet { 46 | if showSelectionRing { 47 | selectionRingView.alpha = 1 48 | } 49 | else { 50 | selectionRingView.alpha = 0 51 | } 52 | } 53 | } 54 | 55 | override var isSelected: Bool { 56 | didSet { 57 | showSelectionRing = isSelected 58 | } 59 | } 60 | 61 | // MARK: - 62 | 63 | override init(frame: CGRect) { 64 | super.init(frame: frame) 65 | 66 | imageView.layer.cornerCurve = .continuous 67 | imageView.layer.cornerRadius = UIFloat(8) 68 | imageView.layer.masksToBounds = true 69 | imageView.layer.borderWidth = UIFloat(0.5) 70 | imageView.layer.borderColor = UIColor.separator.cgColor 71 | 72 | imageView.contentMode = .scaleAspectFill 73 | 74 | contentView.addSubview(imageView) 75 | 76 | /* Selection Ring */ 77 | 78 | selectionRingView.alpha = 0 79 | 80 | selectionRingView.layer.cornerRadius = UIFloat(8) 81 | selectionRingView.layer.borderWidth = UIFloat(4) 82 | selectionRingView.layer.borderColor = UIColor.white.withAlphaComponent(0.7).cgColor 83 | 84 | imageView.addSubview(selectionRingView) 85 | 86 | /* Focus Ring */ 87 | 88 | focusRingView.alpha = 0 89 | 90 | focusRingView.layer.cornerRadius = UIFloat(8) 91 | focusRingView.layer.borderWidth = UIFloat(4) 92 | focusRingView.layer.borderColor = UIColor.systemBlue.cgColor 93 | 94 | imageView.addSubview(focusRingView) 95 | 96 | if #available(macCatalyst 15.0, iOS 15.0, *) { 97 | focusEffect = nil 98 | } 99 | } 100 | 101 | required init?(coder: NSCoder) { 102 | fatalError("init(coder:) has not been implemented") 103 | } 104 | 105 | // MARK: Layout - 106 | 107 | override func layoutSubviews() { 108 | super.layoutSubviews() // Always call super 109 | 110 | let contentBounds = contentView.bounds 111 | 112 | if displayMode == .square { 113 | imageView.layer.cornerRadius = 0 114 | focusRingView.layer.cornerRadius = 0 115 | selectionRingView.layer.cornerRadius = 0 116 | 117 | imageView.frame = contentBounds 118 | focusRingView.frame = imageView.bounds 119 | selectionRingView.frame = imageView.bounds 120 | } 121 | else { 122 | imageView.layer.cornerRadius = UIFloat(8) 123 | focusRingView.layer.cornerRadius = UIFloat(8) 124 | selectionRingView.layer.cornerRadius = UIFloat(8) 125 | 126 | /* 127 | Here, the aspect ratio is hard-coded for this demo. But in a real app dealing with 128 | photos you might have this ratio stored or calculated in your data model 129 | */ 130 | let insetBounds = contentBounds.insetBy(dx:UIFloat(8), dy:UIFloat(8)) 131 | let aspectRatio = 3.0 / 4.0 132 | 133 | let desiredWidth = insetBounds.height * aspectRatio 134 | let desiredHeight = insetBounds.height 135 | 136 | imageView.frame = CGRect(x: (contentBounds.width-desiredWidth)/2, y: (contentBounds.height-desiredHeight)/2, width: desiredWidth, height: desiredHeight) 137 | focusRingView.frame = imageView.bounds 138 | selectionRingView.frame = imageView.bounds 139 | } 140 | } 141 | 142 | // MARK: Trait Changes - 143 | 144 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 145 | super.traitCollectionDidChange(previousTraitCollection) 146 | 147 | /* CALayer color properties don't automatically update when Light/Dark mode changes */ 148 | imageView.layer.borderColor = UIColor.separator.cgColor 149 | focusRingView.layer.borderColor = UIColor.systemBlue.cgColor 150 | selectionRingView.layer.borderColor = UIColor.white.withAlphaComponent(0.7).cgColor 151 | } 152 | 153 | // MARK: - Focus Support 154 | 155 | override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) { 156 | 157 | super.didUpdateFocus(in: context, with: coordinator) 158 | 159 | // customize the border of the cell instead of putting a focus ring on top of it. 160 | if context.nextFocusedItem === self { 161 | showFocusRing = true 162 | } 163 | else if context.previouslyFocusedItem === self { 164 | showFocusRing = false 165 | } 166 | } 167 | 168 | // MARK: - Selection Area 169 | 170 | override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 171 | /* 172 | Change the hit region of the cell to use the image view's frame. 173 | Clicking in the empty space between images will deselect the cell 174 | */ 175 | return imageView.frame.contains(point) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Catalyst Photo Grid/Source/Main/Grid View/CGAGridViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGAGridViewController.swift 3 | // Catalyst Grid App 4 | // 5 | // Created by Steven Troughton-Smith on 07/10/2021. 6 | // 7 | 8 | import UIKit 9 | import Foundation 10 | 11 | 12 | class CGAGridViewController: UICollectionViewController { 13 | 14 | let reuseIdentifier = "Cell" 15 | let padding = UIFloat(4) 16 | 17 | enum DisplayMode:Int { 18 | case square 19 | case aspect 20 | } 21 | 22 | enum HIGridSection { 23 | case main 24 | } 25 | 26 | var displayMode = DisplayMode.square { 27 | didSet { 28 | NotificationCenter.default.post(name: .displayModeChanged, object: String(self.displayMode.rawValue)) 29 | } 30 | } 31 | 32 | struct HIGridItem: Hashable { 33 | var title: String = "" 34 | var image: String = "" 35 | 36 | func hash(into hasher: inout Hasher) { 37 | hasher.combine(identifier) 38 | } 39 | static func == (lhs: HIGridItem, rhs: HIGridItem) -> Bool { 40 | return lhs.identifier == rhs.identifier 41 | } 42 | private let identifier = UUID() 43 | } 44 | 45 | var dataSource: UICollectionViewDiffableDataSource! = nil 46 | 47 | init() { 48 | let layout = UICollectionViewFlowLayout() 49 | 50 | super.init(collectionViewLayout: layout) 51 | 52 | guard let collectionView = collectionView else { return } 53 | 54 | title = "Photos" 55 | 56 | collectionView.selectionFollowsFocus = true 57 | collectionView.allowsMultipleSelection = true 58 | 59 | collectionView.backgroundColor = .systemBackground 60 | collectionView.contentInset = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding) 61 | collectionView.register(CGAGridViewCell.self, forCellWithReuseIdentifier: reuseIdentifier) 62 | 63 | configureDataSource() 64 | } 65 | 66 | required init?(coder: NSCoder) { 67 | fatalError("init(coder:) has not been implemented") 68 | } 69 | 70 | // MARK: Data Source - 71 | 72 | func configureDataSource() { 73 | 74 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { 75 | (collectionView: UICollectionView, indexPath: IndexPath, item: HIGridItem) -> UICollectionViewCell? in 76 | 77 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.reuseIdentifier, for: indexPath) 78 | 79 | if let cell = cell as? CGAGridViewCell { 80 | /* 81 | Customize cell here 82 | */ 83 | 84 | cell.imageView.image = UIImage(named:item.image) 85 | cell.displayMode = self.displayMode 86 | } 87 | 88 | return cell 89 | } 90 | 91 | collectionView.dataSource = dataSource 92 | 93 | refresh() 94 | } 95 | 96 | 97 | func initialSnapshot() -> NSDiffableDataSourceSectionSnapshot { 98 | var snapshot = NSDiffableDataSourceSectionSnapshot() 99 | 100 | var items:[HIGridItem] = [] 101 | 102 | /* Just some dummy items to populate the grid */ 103 | for i in 0 ..< 19 { 104 | 105 | var item = HIGridItem() 106 | item.image = "\(i)" 107 | items.append(item) 108 | } 109 | 110 | snapshot.append(items) 111 | 112 | return snapshot 113 | } 114 | 115 | func refresh() { 116 | guard let dataSource = collectionView.dataSource as? UICollectionViewDiffableDataSource else { return } 117 | 118 | dataSource.apply(initialSnapshot(), to: .main, animatingDifferences: false) 119 | } 120 | 121 | // - MARK: Manual Layout Sizing (Fast) 122 | 123 | override func viewDidLayoutSubviews() { 124 | super.viewDidLayoutSubviews() 125 | 126 | var columns = 4 127 | let threshold = UIFloat(200) 128 | 129 | guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return } 130 | 131 | while view.bounds.size.width/CGFloat(columns) > threshold 132 | { 133 | columns += 1 134 | } 135 | 136 | let frame = view.bounds.inset(by: collectionView.contentInset) 137 | 138 | let itemSize = (frame.width - padding*CGFloat(columns-1)) / CGFloat(columns) 139 | 140 | layout.itemSize = CGSize(width: itemSize, height: itemSize) 141 | layout.minimumLineSpacing = padding 142 | layout.minimumInteritemSpacing = 0 143 | } 144 | 145 | override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 146 | if let cell = cell as? CGAGridViewCell { 147 | cell.displayMode = self.displayMode 148 | } 149 | } 150 | 151 | // MARK: - Multiple Selection 152 | 153 | override func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool { 154 | return true 155 | } 156 | 157 | // MARK: - Support for Select All (Cmd + A) 158 | 159 | override func selectAll(_ sender: Any?) { 160 | for section in 0.. CGFloat 13 | { 14 | #if targetEnvironment(macCatalyst) 15 | return (value == 0.5) ? 0.5 : round(value * (supportsMacIdiom ? 0.77 : 1.0)) 16 | #else 17 | return value 18 | #endif 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Catalyst Photo Grid 2 | 3 | Simple Catalyst example (Mac idiom) of a grid-based app populated with photos that can animate its cells between two different layouts (in this case, aspect ratio). It also demonstrates custom keyboard focus presentation, and supports multi-select via dragging. 4 | 5 | The sample images are taken from Apple's [Fruta sample project](https://developer.apple.com/documentation/swiftui/fruta_building_a_feature-rich_app_with_swiftui). 6 | 7 | ### Screenshots 8 | 9 | ![](https://hccdata.s3.amazonaws.com/gh_photogrid.jpg) 10 | --------------------------------------------------------------------------------