├── Catalyst Photo Grid
├── Assets.xcassets
│ ├── Contents.json
│ ├── Images
│ │ ├── Contents.json
│ │ ├── 7.imageset
│ │ │ ├── kiwi.jpg
│ │ │ └── Contents.json
│ │ ├── 17.imageset
│ │ │ ├── water.jpg
│ │ │ └── Contents.json
│ │ ├── 2.imageset
│ │ │ ├── banana.jpg
│ │ │ └── Contents.json
│ │ ├── 4.imageset
│ │ │ ├── carrot.jpg
│ │ │ └── Contents.json
│ │ ├── 8.imageset
│ │ │ ├── lemon.jpg
│ │ │ └── Contents.json
│ │ ├── 9.imageset
│ │ │ ├── mango.jpg
│ │ │ └── Contents.json
│ │ ├── 1.imageset
│ │ │ ├── avocado.jpg
│ │ │ └── Contents.json
│ │ ├── 10.imageset
│ │ │ ├── orange.jpg
│ │ │ └── Contents.json
│ │ ├── 11.imageset
│ │ │ ├── papaya.jpg
│ │ │ └── Contents.json
│ │ ├── 15.imageset
│ │ │ ├── spinach.jpg
│ │ │ └── Contents.json
│ │ ├── 5.imageset
│ │ │ ├── chocolate.jpg
│ │ │ └── Contents.json
│ │ ├── 6.imageset
│ │ │ ├── coconut.jpg
│ │ │ └── Contents.json
│ │ ├── 0.imageset
│ │ │ ├── almondmilk.jpg
│ │ │ └── Contents.json
│ │ ├── 13.imageset
│ │ │ ├── pineapple.jpg
│ │ │ └── Contents.json
│ │ ├── 16.imageset
│ │ │ ├── strawberry.jpg
│ │ │ └── Contents.json
│ │ ├── 18.imageset
│ │ │ ├── watermelon.jpg
│ │ │ └── Contents.json
│ │ ├── 3.imageset
│ │ │ ├── blueberries.jpg
│ │ │ └── Contents.json
│ │ ├── 12.imageset
│ │ │ ├── peanutbutter.jpg
│ │ │ └── Contents.json
│ │ └── 14.imageset
│ │ │ ├── raspberries.jpg
│ │ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ ├── 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
│ │ ├── icon-1024.png
│ │ └── Contents.json
├── Localizable.strings
├── Source
│ ├── Application
│ │ ├── CGAAppDelegate.swift
│ │ ├── CGAAppDelegate+MenuBuilder.swift
│ │ ├── CGASceneDelegate.swift
│ │ └── CGASceneDelegate+NSToolbar.swift
│ ├── Number+Scaling.swift
│ └── Main
│ │ ├── CGAMainViewController.swift
│ │ └── Grid View
│ │ ├── CGAGridViewCell.swift
│ │ └── CGAGridViewController.swift
├── Info.plist
└── Base.lproj
│ └── LaunchScreen.storyboard
├── Catalyst Photo Grid.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ └── Catalyst Photo Grid.xcscheme
└── project.pbxproj
└── README.md
/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/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/7.imageset/kiwi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/7.imageset/kiwi.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/17.imageset/water.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/17.imageset/water.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/2.imageset/banana.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/2.imageset/banana.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/4.imageset/carrot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/4.imageset/carrot.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/8.imageset/lemon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/8.imageset/lemon.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/9.imageset/mango.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/9.imageset/mango.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon20.png
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/1.imageset/avocado.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/1.imageset/avocado.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/10.imageset/orange.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/10.imageset/orange.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/11.imageset/papaya.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/11.imageset/papaya.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/15.imageset/spinach.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/15.imageset/spinach.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/5.imageset/chocolate.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/5.imageset/chocolate.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/6.imageset/coconut.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/6.imageset/coconut.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/AppIcon.appiconset/icon-1024.png
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/0.imageset/almondmilk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/0.imageset/almondmilk.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/13.imageset/pineapple.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/13.imageset/pineapple.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/16.imageset/strawberry.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/16.imageset/strawberry.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/18.imageset/watermelon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/18.imageset/watermelon.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/3.imageset/blueberries.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/3.imageset/blueberries.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/12.imageset/peanutbutter.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/12.imageset/peanutbutter.jpg
--------------------------------------------------------------------------------
/Catalyst Photo Grid/Assets.xcassets/Images/14.imageset/raspberries.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steventroughtonsmith/CatalystPhotoGrid/HEAD/Catalyst Photo Grid/Assets.xcassets/Images/14.imageset/raspberries.jpg
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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/Number+Scaling.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGFloat+Scaling.swift
3 | // Voxcaster
4 | //
5 | // Created by Steven Troughton-Smith on 29/07/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | public let supportsMacIdiom = !(UIDevice.current.userInterfaceIdiom == .pad)
11 |
12 | @inlinable func UIFloat(_ value: CGFloat) -> 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 | 
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/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/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/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/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.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/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/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..