├── AppKitIntegration
├── AppKitController.swift
└── Info.plist
├── CatalystExample
├── App
│ ├── Controllers
│ │ ├── CATAppDelegate+MenuBuilder.swift
│ │ ├── CATAppDelegate.swift
│ │ └── CATPreferencesController.swift
│ └── Main Window
│ │ ├── CATMainViewController.swift
│ │ ├── CATWindowSceneDelegate+NSToolbar.swift
│ │ └── CATWindowSceneDelegate.swift
├── CatalystExample.entitlements
├── Common.swift
├── Content View
│ └── CATItemViewController.swift
├── Data Model
│ ├── CATElement.swift
│ ├── CATFolder.swift
│ ├── CATItem.swift
│ └── CATSourceFile.swift
├── Middle Column
│ ├── CATItemListViewCell.swift
│ └── CATItemListViewController.swift
├── Number+Scaling.swift
├── Resources
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Credits.rtf
│ ├── Example.json
│ ├── Info.plist
│ └── Localizable.strings
└── Sidebar
│ ├── CATSourceListViewController+Reordering.swift
│ └── CATSourceListViewController.swift
├── README.md
└── catalystapp.xcodeproj
├── project.pbxproj
├── project.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── xcshareddata
└── xcschemes
├── AppKitIntegration.xcscheme
└── CatalystExample.xcscheme
/AppKitIntegration/AppKitController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import AppKit
8 |
9 | extension NSObject {
10 | @objc func hostWindowForSceneIdentifier(_ identifier:String) -> NSWindow? {
11 | return nil
12 | }
13 | }
14 |
15 | class AppKitController : NSObject {
16 | var searchItems:[String:NSSearchToolbarItem] = [:]
17 |
18 | // MARK: -
19 |
20 | @objc public func _marzipan_setupWindow(_ note:Notification) {
21 |
22 | NSLog("_marzipan_setupWindow: \(String(describing: note.userInfo))")
23 |
24 | /*
25 | Here, AppKit has generated the host window for your UIKit window.
26 | Now it is safe to go do any AppKit-y things you'd like to do to it
27 | */
28 | }
29 |
30 | @objc public func closeWindowForSceneIdentifier(_ sceneIdentifier:String) {
31 | guard let appDelegate = NSApp.delegate as? NSObject else { return }
32 |
33 | if appDelegate.responds(to: #selector(hostWindowForSceneIdentifier(_:))) {
34 | guard let hostWindow = appDelegate.hostWindowForSceneIdentifier(sceneIdentifier) else { return }
35 |
36 | hostWindow.performClose(self)
37 | }
38 | }
39 | }
40 |
41 | extension AppKitController {
42 |
43 | @objc public func searchToolbarItem(sceneIdentifier:String, itemIdentifier:NSToolbarItem.Identifier, target:AnyObject, selector:Selector) -> NSToolbarItem {
44 |
45 | if let searchItem = searchItems[sceneIdentifier] {
46 |
47 | return searchItem
48 | }
49 | else {
50 | let searchItem = NSSearchToolbarItem(itemIdentifier: itemIdentifier)
51 | searchItem.target = target
52 | searchItem.action = selector
53 | searchItem.searchField.placeholderString = NSLocalizedString("TOOLBAR_SEARCH_PLACEHOLDER", comment: "")
54 |
55 | searchItems[sceneIdentifier] = searchItem
56 |
57 | return searchItem
58 | }
59 |
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/AppKitIntegration/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/CatalystExample/App/Controllers/CATAppDelegate+MenuBuilder.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 |
9 | extension CATAppDelegate {
10 | override func buildMenu(with builder: UIMenuBuilder) {
11 |
12 | builder.remove(menu: .format)
13 |
14 | do {
15 | let command = UIKeyCommand(input: "N", modifierFlags: [.command, .alternate], action: NSSelectorFromString("newItem:"))
16 |
17 | command.title = NSLocalizedString("MENU_ITEM_NEW_ITEM", comment: "")
18 |
19 | let menu = UIMenu(title: NSLocalizedString("MENU_ITEM", comment: ""), image: nil, identifier: UIMenu.Identifier("MENU_ITEM"), options: [], children: [command])
20 | builder.insertSibling(menu, afterMenu: .edit)
21 | }
22 |
23 | super.buildMenu(with: builder)
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/CatalystExample/App/Controllers/CATAppDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 |
9 | /*
10 | Let Swift know how to talk to the APIs defined in your AppKitIntegration framework,
11 | which you can't link directly because it's built for macOS, not iOS
12 | */
13 |
14 | #if targetEnvironment(macCatalyst)
15 |
16 | extension NSObject {
17 | @objc public func _marzipan_setupWindow(_ sender:Any) {
18 |
19 | }
20 |
21 | @objc public func searchToolbarItem(sceneIdentifier:String, itemIdentifier:NSToolbarItem.Identifier, target:AnyObject, selector:Selector) -> NSToolbarItem {
22 | return NSToolbarItem(itemIdentifier: itemIdentifier)
23 | }
24 |
25 | @objc func CAT_endEditing(_ sender:Any?) {
26 |
27 | }
28 | }
29 | #endif
30 |
31 | @main
32 | class CATAppDelegate: UIResponder, UIApplicationDelegate {
33 |
34 | var window: UIWindow?
35 |
36 | #if targetEnvironment(macCatalyst)
37 |
38 | static var appKitController:NSObject?
39 |
40 | class func fixNSSearchFieldFocusLockup() {
41 | /*
42 |
43 | In macOS 11 and macOS 12, tabbing to and from a search field in the toolbar
44 | will cause an endless loop. FB9724872
45 |
46 | This may be fixed in a future release, but for now, we can swizzle the issue
47 | away for another day.
48 |
49 | */
50 |
51 | let m1 = class_getInstanceMethod(NSClassFromString("NSSearchFieldCell"), NSSelectorFromString("endEditing:"))
52 | let m2 = class_getInstanceMethod(NSClassFromString("NSSearchFieldCell"), NSSelectorFromString("CAT_endEditing:"))
53 |
54 | if let m1 = m1, let m2 = m2 {
55 | method_exchangeImplementations(m1, m2)
56 | }
57 | }
58 |
59 | class func loadAppKitIntegrationFramework() {
60 |
61 | if let frameworksPath = Bundle.main.privateFrameworksPath {
62 | let bundlePath = "\(frameworksPath)/AppKitIntegration.framework"
63 | do {
64 | try Bundle(path: bundlePath)?.loadAndReturnError()
65 |
66 | let bundle = Bundle(path: bundlePath)!
67 | NSLog("[APPKIT BUNDLE] Loaded Successfully")
68 |
69 | if let appKitControllerClass = bundle.classNamed("AppKitIntegration.AppKitController") as? NSObject.Type {
70 | appKitController = appKitControllerClass.init()
71 |
72 | NotificationCenter.default.addObserver(appKitController as Any, selector: #selector(_marzipan_setupWindow(_:)), name: NSNotification.Name("UISBHSDidCreateWindowForSceneNotification"), object: nil)
73 | }
74 | }
75 | catch {
76 | NSLog("[APPKIT BUNDLE] Error loading: \(error)")
77 | }
78 | }
79 | }
80 | #endif
81 |
82 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
83 | // Override point for customization after application launch.
84 |
85 | #if targetEnvironment(macCatalyst)
86 |
87 | CATAppDelegate.loadAppKitIntegrationFramework()
88 | CATAppDelegate.fixNSSearchFieldFocusLockup()
89 |
90 | #endif
91 |
92 | return true
93 | }
94 |
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/CatalystExample/App/Controllers/CATPreferencesController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import Foundation
8 |
9 | class CATPreferencesController {
10 | static let shared = CATPreferencesController()
11 |
12 | static let debugEnabledKey = "CATDebugEnabled"
13 |
14 | var enableDebugFeatures:Bool = UserDefaults.standard.bool(forKey: debugEnabledKey)
15 | }
16 |
--------------------------------------------------------------------------------
/CatalystExample/App/Main Window/CATMainViewController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 |
9 | class CATMainViewController: UIViewController, ObservableObject, UISplitViewControllerDelegate {
10 |
11 | let rootSplitViewController = UISplitViewController(style: .tripleColumn)
12 |
13 | public var listViewController:CATItemListViewController?
14 | public let detailViewController = CATItemViewController()
15 | public let sourceListViewController = CATSourceListViewController()
16 |
17 | let source = CATSourceFile(url: Bundle.main.url(forResource: "Example", withExtension: "json")!)
18 |
19 | init() {
20 |
21 | super.init(nibName: nil, bundle: nil)
22 |
23 | listViewController = CATItemListViewController()
24 |
25 | rootSplitViewController.primaryBackgroundStyle = .sidebar
26 |
27 | rootSplitViewController.preferredPrimaryColumnWidth = UIFloat(260)
28 | rootSplitViewController.minimumPrimaryColumnWidth = UIFloat(200)
29 |
30 | rootSplitViewController.delegate = self
31 | rootSplitViewController.preferredDisplayMode = .twoBesideSecondary
32 | rootSplitViewController.modalPresentationStyle = .overFullScreen
33 |
34 | #if targetEnvironment(macCatalyst)
35 | rootSplitViewController.presentsWithGesture = false
36 | #endif
37 |
38 | buildThreeColumnUI()
39 |
40 | addChild(rootSplitViewController)
41 | view.addSubview(rootSplitViewController.view)
42 |
43 |
44 | listViewController?.documentViewController = self
45 | sourceListViewController.documentViewController = self
46 |
47 | listViewController?.source = source
48 | detailViewController.source = source
49 | sourceListViewController.source = source
50 | sourceListViewController.listViewController = listViewController
51 |
52 | traitCollectionDidChange(traitCollection)
53 | }
54 |
55 |
56 | func buildThreeColumnUI() {
57 | let sidebarNC = UINavigationController(rootViewController: sourceListViewController)
58 |
59 | #if targetEnvironment(macCatalyst)
60 | sidebarNC.isNavigationBarHidden = true
61 | #else
62 | sidebarNC.navigationBar.prefersLargeTitles = true
63 | #endif
64 |
65 | let listNC = UINavigationController(rootViewController: listViewController!)
66 | #if targetEnvironment(macCatalyst)
67 | listNC.isNavigationBarHidden = true
68 | listNC.navigationBar.setBackgroundImage(UIImage(), for: .default)
69 | listNC.navigationBar.shadowImage = UIImage()
70 | #else
71 | listNC.navigationBar.prefersLargeTitles = true
72 | #endif
73 |
74 | let detailViewNC = UINavigationController(rootViewController: detailViewController)
75 | #if targetEnvironment(macCatalyst)
76 | detailViewNC.isNavigationBarHidden = true
77 | #else
78 | detailViewNC.navigationBar.setBackgroundImage(UIImage(), for: .default)
79 | detailViewNC.navigationBar.shadowImage = UIImage()
80 | #endif
81 | detailViewNC.isToolbarHidden = true
82 |
83 | rootSplitViewController.viewControllers = [sidebarNC, listNC, detailViewNC]
84 | }
85 |
86 | required init?(coder: NSCoder) {
87 | fatalError("init(coder:) has not been implemented")
88 | }
89 |
90 | // MARK: - Actions
91 |
92 | @objc func newItem(_ sender:Any) {
93 | let item = CATItem()
94 | source.add(item: item)
95 | }
96 |
97 | @objc func newFolder(_ sender:Any) {
98 | source.newFolder()
99 | }
100 |
101 | // MARK: - Layout
102 |
103 | override func viewDidLayoutSubviews() {
104 | rootSplitViewController.view.frame = view.bounds
105 | }
106 |
107 | // MARK: - Disallow rotation on iPhone
108 |
109 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
110 | return UIDevice.current.userInterfaceIdiom == .phone ? .portrait : .all
111 | }
112 |
113 | // MARK: - Size Class Transition
114 |
115 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
116 | super.traitCollectionDidChange(previousTraitCollection)
117 |
118 | if traitCollection.horizontalSizeClass == .regular {
119 | buildThreeColumnUI()
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/CatalystExample/App/Main Window/CATWindowSceneDelegate+NSToolbar.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 |
9 | #if targetEnvironment(macCatalyst)
10 | extension NSToolbarItem.Identifier {
11 | static let newItem = NSToolbarItem.Identifier("com.highcaffeinecontent.catalystexample.new")
12 | static let newFolder = NSToolbarItem.Identifier("com.highcaffeinecontent.catalystexample.newfolder")
13 | static let search = NSToolbarItem.Identifier("com.highcaffeinecontent.catalystexample.search")
14 | }
15 |
16 | extension CATWindowSceneDelegate : NSToolbarDelegate {
17 |
18 | func toolbarIdentifiers() -> [NSToolbarItem.Identifier] {
19 | return [.flexibleSpace, .newFolder, .primarySidebarTrackingSeparatorItemIdentifier, .flexibleSpace, .supplementarySidebarTrackingSeparatorItemIdentifier, .newItem, .flexibleSpace, .search]
20 | }
21 |
22 | func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
23 | return toolbarIdentifiers()
24 | }
25 |
26 | func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
27 | return toolbarIdentifiers()
28 | }
29 |
30 | func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
31 |
32 | if itemIdentifier == .newItem {
33 | let barItem = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(create(_:)))
34 | let item = NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: barItem)
35 | item.accessibilityLabel = NSLocalizedString("ITEM_NONE_SELECTED_NEW_BUTTON", comment: "")
36 | item.toolTip = NSLocalizedString("ITEM_NONE_SELECTED_NEW_BUTTON", comment: "")
37 |
38 | return item
39 | }
40 | else if itemIdentifier == .newFolder {
41 | let barItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .plain, target: self, action: #selector(createFolder(_:)))
42 | let item = NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: barItem)
43 | item.accessibilityLabel = NSLocalizedString("TOOLBAR_NEW_FOLDER_BUTTON", comment: "")
44 | item.toolTip = NSLocalizedString("TOOLBAR_NEW_FOLDER_BUTTON", comment: "")
45 |
46 | return item
47 | }
48 | else if itemIdentifier == .search {
49 |
50 | if let searchItem = CATAppDelegate.appKitController?.searchToolbarItem(sceneIdentifier:scene?.session.persistentIdentifier ?? UUID().uuidString, itemIdentifier: itemIdentifier, target: self, selector: #selector(search(_:))) {
51 | return searchItem
52 | }
53 | else {
54 | return NSToolbarItem(itemIdentifier: itemIdentifier)
55 | }
56 | }
57 | else {
58 | return NSToolbarItem(itemIdentifier: itemIdentifier)
59 | }
60 | }
61 | }
62 | #endif
63 |
--------------------------------------------------------------------------------
/CatalystExample/App/Main Window/CATWindowSceneDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 | import UniformTypeIdentifiers
9 |
10 | class CATWindowSceneDelegate : NSObject, UISceneDelegate, UIWindowSceneDelegate, UIDocumentPickerDelegate {
11 |
12 | var window: UIWindow?
13 | weak var scene:UIScene?
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | window = UIWindow(windowScene: scene as! UIWindowScene)
17 | self.scene = scene
18 |
19 | if let window = window {
20 | let rootViewController = CATMainViewController()
21 | window.rootViewController = rootViewController
22 |
23 | window.backgroundColor = .systemBackground
24 |
25 | reloadToolbar()
26 |
27 | window.makeKeyAndVisible()
28 |
29 | }
30 | }
31 |
32 | // MARK: - Toolbar Actions
33 |
34 | @objc func create(_ sender:Any) {
35 | if let documentViewController = window?.rootViewController as? CATMainViewController {
36 | documentViewController.newItem(sender)
37 | }
38 | }
39 |
40 | @objc func createFolder(_ sender:Any) {
41 | if let documentViewController = window?.rootViewController as? CATMainViewController {
42 | documentViewController.newFolder(sender)
43 | }
44 | }
45 |
46 | @objc func search(_ sender:Any) {
47 | if let nsSender = sender as? NSObject {
48 | if let string = nsSender.value(forKey: "stringValue") as? NSString {
49 | NSLog("[SEARCH]: \(string)")
50 |
51 | if let documentViewController = window?.rootViewController as? CATMainViewController {
52 | documentViewController.source.searchString = string as String
53 | }
54 | }
55 | }
56 | }
57 |
58 | // MARK: - Mac Toolbar
59 |
60 | func reloadToolbar() {
61 | #if targetEnvironment(macCatalyst)
62 | let toolbar = NSToolbar()
63 | toolbar.delegate = self
64 | toolbar.displayMode = .iconOnly
65 |
66 | window?.windowScene?.titlebar?.toolbar = toolbar
67 | window?.windowScene?.titlebar?.toolbarStyle = .automatic
68 | #endif
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/CatalystExample/CatalystExample.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.com.highcaffeinecontent.Shared
10 |
11 | com.apple.security.files.user-selected.read-write
12 |
13 | com.apple.security.network.client
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CatalystExample/Common.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import Foundation
8 |
9 | func CATPrettyFormatDate(_ date:Date) -> String {
10 | return CATPrettyFormatDate(date, dateStyle: .long, timeStyle: .short)
11 | }
12 |
13 | func CATPrettyFormatDate(_ date:Date, dateStyle:DateFormatter.Style, timeStyle:DateFormatter.Style) -> String {
14 | let formatter = DateFormatter()
15 |
16 | formatter.locale = Locale.current
17 | formatter.dateStyle = dateStyle
18 | formatter.timeStyle = timeStyle
19 |
20 | return formatter.string(from: date)
21 | }
22 |
--------------------------------------------------------------------------------
/CatalystExample/Content View/CATItemViewController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 | import SwiftUI
9 |
10 | struct ContentView: View {
11 | @ObservedObject var item:CATItem = CATItem()
12 |
13 | var parent:CATItemViewController
14 |
15 | var body: some View {
16 | Text("MAIN_VIEW_HELLO_WORLD_LABEL")
17 |
18 | }
19 | }
20 |
21 | class CATItemViewController : UIViewController, ObservableObject {
22 |
23 | var hostingController:UIHostingController?
24 | var documentViewController:CATMainViewController?
25 | var source:CATSourceFile?
26 |
27 | override func viewDidLoad() {
28 | view.backgroundColor = .systemBackground
29 | }
30 |
31 | init() {
32 | super.init(nibName: nil, bundle: nil)
33 |
34 | hostingController = UIHostingController(rootView: ContentView(parent: self))
35 | navigationItem.largeTitleDisplayMode = .never
36 |
37 | if let hostingController = hostingController {
38 | view.addSubview(hostingController.view)
39 | }
40 |
41 | if #available(macCatalyst 15.0, iOS 15.0, *) {
42 | focusGroupIdentifier = "com.example.details"
43 | }
44 | }
45 |
46 | // MARK: -
47 |
48 | @objc required dynamic init?(coder aDecoder: NSCoder) {
49 | fatalError("init(coder:) has not been implemented")
50 | }
51 |
52 | override func viewDidLayoutSubviews() {
53 | if let hostingController = hostingController {
54 | hostingController.view.frame = view.bounds.inset(by: view.safeAreaInsets)
55 | }
56 | }
57 |
58 | // MARK: - Keyboard Focus
59 |
60 | override func shouldUpdateFocus(in context: UIFocusUpdateContext) -> Bool {
61 | return false
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/CatalystExample/Data Model/CATElement.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import Foundation
8 | import MobileCoreServices
9 |
10 | class CATElementWrapper: NSObject, NSItemProviderWriting {
11 | static var writableTypeIdentifiersForItemProvider: [String] { return ["public.string"] }
12 |
13 | var element:CATElement
14 |
15 | init(element:CATElement) {
16 | self.element = element
17 | super.init()
18 | }
19 |
20 | // MARK: -
21 |
22 | func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
23 |
24 | let encoder = JSONEncoder()
25 | encoder.outputFormatting = .prettyPrinted
26 |
27 | let jsonData = try! encoder.encode(element)
28 |
29 | completionHandler(jsonData, nil)
30 |
31 | return nil
32 | }
33 |
34 | }
35 |
36 | class CATElement: Codable, Hashable {
37 | var identifier = UUID().uuidString
38 |
39 | init() {
40 |
41 | }
42 |
43 | // MARK: - Hashable
44 |
45 | static func == (lhs: CATElement, rhs: CATElement) -> Bool {
46 | if (lhs.identifier == rhs.identifier) {
47 | return true
48 | }
49 |
50 | return false
51 | }
52 |
53 | func hash(into hasher: inout Hasher) {
54 | hasher.combine(self)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/CatalystExample/Data Model/CATFolder.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import Foundation
8 |
9 | class CATFolder: CATElement, ObservableObject {
10 |
11 | @Published var title = NSLocalizedString("FOLDER_UNTITLED", comment: "")
12 | var itemIDs = Array()
13 |
14 | override init() {
15 | super.init()
16 | }
17 |
18 | // MARK: - Codable
19 |
20 | required init(from decoder: Decoder) throws {
21 | super.init()
22 |
23 | let container = try decoder.container(keyedBy: CodingKeys.self)
24 |
25 | identifier = (try? container.decode(String.self, forKey: .identifier)) ?? UUID().uuidString
26 | title = (try? container.decode(String.self, forKey: .title)) ?? NSLocalizedString("FOLDER_UNTITLED", comment: "")
27 | itemIDs = (try? container.decode(Array.self, forKey: .itemIDs)) ?? Array()
28 |
29 | }
30 |
31 | override func encode(to encoder: Encoder) throws {
32 |
33 | var container = encoder.container(keyedBy: CodingKeys.self)
34 |
35 | try container.encode(identifier, forKey: .identifier)
36 | try container.encode(title, forKey: .title)
37 | try container.encode(itemIDs, forKey: .itemIDs)
38 | }
39 |
40 | enum CodingKeys: String, CodingKey {
41 | case identifier
42 | case title
43 | case itemIDs
44 | }
45 |
46 | // MARK: - Hashable
47 |
48 | static func == (lhs: CATFolder, rhs: CATFolder) -> Bool {
49 | if (lhs.identifier == rhs.identifier) && (lhs.title == rhs.title) && (rhs.itemIDs == lhs.itemIDs) {
50 | return true
51 | }
52 |
53 | return false
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/CatalystExample/Data Model/CATItem.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import Foundation
8 |
9 | class CATItem: CATElement, ObservableObject {
10 |
11 | var title = NSLocalizedString("ITEM_TITLE_UNTITLED", comment: "")
12 | var body = "Lorem ipsum dolor sit amet"
13 | var created = Date()
14 |
15 | override init() {
16 | super.init()
17 | }
18 |
19 | // MARK: - Codable
20 |
21 | required init(from decoder: Decoder) throws {
22 | super.init()
23 |
24 | let container = try decoder.container(keyedBy: CodingKeys.self)
25 |
26 | identifier = try container.decode(String.self, forKey: .identifier)
27 | title = (try? container.decode(String.self, forKey: .title)) ?? NSLocalizedString("ITEM_TITLE_UNTITLED", comment: "")
28 | body = (try? container.decode(String.self, forKey: .body)) ?? ""
29 |
30 | created = (try? container.decode(Date.self, forKey: .created)) ?? Date()
31 | }
32 |
33 | override func encode(to encoder: Encoder) throws {
34 |
35 | var container = encoder.container(keyedBy: CodingKeys.self)
36 |
37 | try container.encode(identifier, forKey: .identifier)
38 | try container.encode(title, forKey: .title)
39 | try container.encode(body, forKey: .body)
40 |
41 | try container.encode(created, forKey: .created)
42 | }
43 |
44 | enum CodingKeys: String, CodingKey {
45 | case identifier
46 | case title
47 | case body
48 |
49 | case created
50 | }
51 |
52 | // MARK: - Search
53 |
54 | func search(searchString:String) -> Bool {
55 |
56 | let searchString = searchString.lowercased()
57 |
58 | if title.lowercased().contains(searchString) || body.lowercased().contains(searchString) {
59 | return true
60 | }
61 |
62 | return false
63 | }
64 |
65 | // MARK: - Hashable
66 |
67 | static func == (lhs: CATItem, rhs: CATItem) -> Bool {
68 | if (lhs.identifier == rhs.identifier) && (lhs.title == rhs.title) && (rhs.body == lhs.body) && (rhs.created == lhs.created) {
69 | return true
70 | }
71 |
72 | return false
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/CatalystExample/Data Model/CATSourceFile.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 |
9 | extension Notification.Name {
10 | static let documentChanged = NSNotification.Name("CATSourceFile.modified")
11 | }
12 |
13 | class CATDataStore: Codable {
14 |
15 | var items = Array()
16 | var folders = Array()
17 |
18 | init() {
19 |
20 | }
21 |
22 | required init(from decoder: Decoder) throws {
23 | let container = try decoder.container(keyedBy: CodingKeys.self)
24 |
25 | items = (try? container.decode(Array.self, forKey: .items)) ?? Array()
26 | folders = (try? container.decode(Array.self, forKey: .folders)) ?? Array()
27 | }
28 |
29 | func encode(to encoder: Encoder) throws {
30 |
31 | var container = encoder.container(keyedBy: CodingKeys.self)
32 |
33 | try container.encode(items, forKey: .items)
34 | try container.encode(folders, forKey: .folders)
35 | }
36 |
37 | enum CodingKeys: String, CodingKey {
38 | case items
39 | case folders
40 | }
41 | }
42 |
43 | class CATSourceFile {
44 |
45 | var dataStore = CATDataStore()
46 |
47 | var searchString = "" {
48 | didSet {
49 | changed()
50 | }
51 | }
52 |
53 | static let CATFolderIndexAll = -1
54 | static let CATFolderIndexRecents = -2
55 |
56 | // MARK: - Load & Save the data store
57 |
58 | convenience init(url: URL) {
59 | self.init()
60 |
61 | do {
62 | let data = try Data(contentsOf: url)
63 | try load(fromData: data)
64 | }
65 | catch {
66 | NSLog("[LOAD] Error: \(error.localizedDescription)")
67 | }
68 |
69 | }
70 |
71 | func load(fromData data: Data) throws {
72 | let decoder = JSONDecoder()
73 |
74 | do {
75 | let decodedDataStore = try decoder.decode(CATDataStore.self, from: data)
76 | dataStore = decodedDataStore
77 |
78 | print("[ARCHIVE] Opened successfully")
79 |
80 | } catch {
81 | print("[ARCHIVE] Fatal: \(error.localizedDescription)")
82 | }
83 | }
84 |
85 | func save(to url: URL, completionHandler: ((Bool) -> Void)? = nil) {
86 |
87 | let _ = url.startAccessingSecurityScopedResource()
88 |
89 | let encoder = JSONEncoder()
90 | encoder.outputFormatting = .prettyPrinted
91 |
92 | let jsonData = try! encoder.encode(dataStore)
93 |
94 | do {
95 | try jsonData.write(to: url, options: .atomic)
96 |
97 | if let completionHandler = completionHandler {
98 | completionHandler(true)
99 | }
100 | }
101 | catch {
102 | print("[ARCHIVE] Error \(error)")
103 |
104 | if let completionHandler = completionHandler {
105 | completionHandler(false)
106 | }
107 | }
108 | }
109 | }
110 |
111 | extension CATSourceFile {
112 | // MARK: - Managing the data store
113 |
114 | func add(item:CATItem) {
115 | dataStore.items.append(item)
116 |
117 | changed()
118 | }
119 |
120 | func removeItem(withIdentifier:String) {
121 | dataStore.items.removeAll { potentialItem in
122 | return (potentialItem.identifier == withIdentifier)
123 | }
124 |
125 | changed()
126 | }
127 |
128 | func reorderFolder(at:UInt, to:UInt) {
129 |
130 | let target = dataStore.folders[Int(at)]
131 | dataStore.folders.remove(at: Int(at))
132 | dataStore.folders.insert(target, at: Int(to))
133 |
134 | changed()
135 | }
136 |
137 | func newFolder() {
138 | dataStore.folders.append(CATFolder())
139 |
140 | changed()
141 | }
142 |
143 | // MARK: - Change Notifications
144 |
145 | func changed() {
146 | NotificationCenter.default.post(name: .documentChanged, object: nil)
147 | }
148 |
149 | // MARK: - Sorted Accessor
150 |
151 | func sortedItems(tagIndex:Int) -> Array {
152 | var itemsSorted = [CATItem]()
153 |
154 | if tagIndex >= dataStore.folders.count {
155 | return itemsSorted
156 | }
157 |
158 | if tagIndex == CATSourceFile.CATFolderIndexAll {
159 |
160 | for item in dataStore.items {
161 |
162 | if searchString != "" {
163 | if item.search(searchString: searchString) == false {
164 | continue
165 | }
166 | }
167 |
168 | itemsSorted.append(item)
169 | }
170 | }
171 | else if tagIndex == CATSourceFile.CATFolderIndexRecents {
172 |
173 | let oneDay = TimeInterval(60 * 60 * 24)
174 |
175 | for item in dataStore.items {
176 | if Date.timeIntervalSinceReferenceDate - item.created.timeIntervalSinceReferenceDate < (oneDay * 3) {
177 |
178 | if searchString != "" {
179 | if (item.title.lowercased().contains(searchString.lowercased()) == false) && (item.body.lowercased().contains(searchString.lowercased()) == false) {
180 | continue
181 | }
182 | }
183 |
184 | itemsSorted.append(item)
185 | }
186 | }
187 | }
188 | else {
189 |
190 |
191 | let folder = dataStore.folders[tagIndex]
192 |
193 | for item in dataStore.items {
194 | if folder.itemIDs.contains(item.identifier) {
195 |
196 | if searchString != "" {
197 | if (item.title.lowercased().contains(searchString.lowercased()) == false) && (item.body.lowercased().contains(searchString.lowercased()) == false) {
198 | continue
199 | }
200 | }
201 |
202 | itemsSorted.append(item)
203 | }
204 | }
205 | }
206 |
207 | itemsSorted.sort { A, B in
208 | return (A.created.compare(B.created) == .orderedDescending)
209 | }
210 |
211 | return itemsSorted
212 | }
213 | }
214 |
215 |
--------------------------------------------------------------------------------
/CatalystExample/Middle Column/CATItemListViewCell.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 |
9 | class CATItemListViewCell: UICollectionViewCell {
10 |
11 | var inactive = false
12 |
13 | var titleLabel = UILabel()
14 | var bodyLabel = UILabel()
15 |
16 | var modifiedLabel = UILabel()
17 |
18 | var stateAreaWidth = CGFloat.zero
19 |
20 | let focusRingView = UIView()
21 | let selectionRingView = UIView()
22 |
23 | override var isSelected: Bool {
24 | didSet {
25 | if isSelected == true {
26 | selectionRingView.alpha = 1
27 | }
28 | else {
29 | selectionRingView.alpha = 0
30 | }
31 |
32 | recolor()
33 | }
34 | }
35 |
36 | override var isHighlighted: Bool {
37 | didSet {
38 | recolor()
39 | }
40 | }
41 |
42 | var showFocusRing: Bool = false {
43 | didSet {
44 | focusRingView.isHidden = !showFocusRing
45 |
46 | recolor()
47 | }
48 | }
49 |
50 | // MARK: -
51 |
52 |
53 | override func prepareForReuse() {
54 | isSelected = false
55 | isHighlighted = false
56 | showFocusRing = false
57 | }
58 |
59 | func recolor() {
60 |
61 | var showWhiteLabels = false
62 |
63 | if (isSelected && !inactive) || showFocusRing {
64 | showWhiteLabels = true
65 | }
66 |
67 | if (!showFocusRing && isSelected) || isHighlighted {
68 | showWhiteLabels = false
69 | }
70 |
71 | if showWhiteLabels == true {
72 | titleLabel.textColor = .white
73 | bodyLabel.textColor = .white
74 | modifiedLabel.textColor = .white
75 | }
76 | else {
77 | titleLabel.textColor = .label
78 | bodyLabel.textColor = .secondaryLabel
79 | modifiedLabel.textColor = .secondaryLabel
80 | }
81 | }
82 |
83 | override init(frame: CGRect) {
84 | super.init(frame: frame)
85 |
86 | focusRingView.layer.cornerRadius = UIFloat(8)
87 | focusRingView.layer.cornerCurve = .continuous
88 |
89 | selectionRingView.layer.cornerRadius = UIFloat(8)
90 | selectionRingView.layer.cornerCurve = .continuous
91 |
92 | titleLabel.font = UIFont.boldSystemFont(ofSize: UIFloat(18))
93 | bodyLabel.font = UIFont.systemFont(ofSize: UIFloat(16))
94 |
95 | modifiedLabel.font = UIFont.systemFont(ofSize: UIFloat(15))
96 |
97 | modifiedLabel.textAlignment = .right
98 |
99 | stateAreaWidth = ("31/12/2021" as NSString).size(withAttributes: [.font : UIFont.systemFont(ofSize: UIFloat(15))]).width + UIFloat(13)
100 |
101 | recolor()
102 |
103 | selectionRingView.alpha = 0
104 | selectionRingView.backgroundColor = .systemFill
105 | addSubview(selectionRingView)
106 |
107 | focusRingView.isHidden = true
108 | focusRingView.backgroundColor = tintColor
109 | addSubview(focusRingView)
110 |
111 | contentView.addSubview(titleLabel)
112 | contentView.addSubview(bodyLabel)
113 | contentView.addSubview(modifiedLabel)
114 |
115 | if #available(macCatalyst 15.0, iOS 15.0, *) {
116 | focusEffect = nil
117 | }
118 | }
119 |
120 | required init?(coder: NSCoder) {
121 | fatalError("init(coder:) has not been implemented")
122 | }
123 |
124 | // MARK: -
125 |
126 | override func layoutSubviews() {
127 |
128 | let padding = UIFloat(13)
129 | let insetFrame = bounds.insetBy(dx: padding, dy: 0)
130 |
131 | contentView.frame = insetFrame.insetBy(dx: padding, dy: 0)
132 |
133 | selectionRingView.frame = bounds.insetBy(dx: padding, dy: 0)
134 | focusRingView.frame = selectionRingView.frame
135 |
136 | /* */
137 |
138 | let contentRegion = contentView.bounds.divided(atDistance: stateAreaWidth, from: .maxXEdge)
139 | let rightRegion = contentRegion.slice.inset(by: UIEdgeInsets(top: padding, left: 0, bottom: padding, right: 0))
140 | let leftRegion = contentRegion.remainder.inset(by: UIEdgeInsets(top: padding, left: padding, bottom: padding, right: 0))
141 |
142 | let leftLabelRegion = leftRegion.divided(atDistance: leftRegion.height/2, from: .minYEdge)
143 | let rightLabelRegion = rightRegion.divided(atDistance: rightRegion.height/2, from: .minYEdge)
144 |
145 | titleLabel.frame = leftLabelRegion.slice
146 | bodyLabel.frame = leftLabelRegion.remainder
147 |
148 | modifiedLabel.frame = rightLabelRegion.slice
149 | }
150 |
151 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
152 | inactive = (traitCollection.activeAppearance == .inactive)
153 | recolor()
154 | }
155 |
156 | // MARK: - Keyboard Focus
157 |
158 | override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
159 |
160 | super.didUpdateFocus(in: context, with: coordinator)
161 |
162 | if context.nextFocusedItem === self {
163 | showFocusRing = true
164 | }
165 | else if context.previouslyFocusedItem === self {
166 | showFocusRing = false
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/CatalystExample/Middle Column/CATItemListViewController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 |
9 | class CATItemListViewController: UICollectionViewController {
10 |
11 | static let cellIdentifier = "Cell"
12 | var diffableDataSource:UICollectionViewDiffableDataSource?
13 | var selectedIdentifier = UUID().uuidString
14 |
15 | var documentViewController:CATMainViewController? {
16 | didSet {
17 | reload()
18 | }
19 | }
20 |
21 | var source:CATSourceFile? {
22 | didSet {
23 | reload()
24 | }
25 | }
26 |
27 | var currentFolderIndex = CATSourceFile.CATFolderIndexAll {
28 | didSet {
29 |
30 | if currentFolderIndex == CATSourceFile.CATFolderIndexAll {
31 | title = NSLocalizedString("SIDEBAR_ALL_ITEMS", comment: "")
32 | }
33 | else if currentFolderIndex == CATSourceFile.CATFolderIndexRecents {
34 | title = NSLocalizedString("SIDEBAR_RECENTS", comment: "")
35 | }
36 | else {
37 | title = source?.dataStore.folders[currentFolderIndex].title
38 | }
39 |
40 | DispatchQueue.main.async {
41 | self.reload()
42 | }
43 | }
44 | }
45 |
46 | // MARK: -
47 |
48 | init() {
49 |
50 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
51 | heightDimension: .absolute(UIFloat(80)))
52 | let item = NSCollectionLayoutItem(layoutSize: itemSize)
53 |
54 | let group = NSCollectionLayoutGroup.vertical(layoutSize: itemSize,
55 | subitems: [item])
56 |
57 | let section = NSCollectionLayoutSection(group: group)
58 | let layout = UICollectionViewCompositionalLayout(section: section)
59 |
60 | super.init(collectionViewLayout: layout)
61 |
62 | diffableDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { collectionView, indexPath, ctx in
63 |
64 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CATItemListViewController.cellIdentifier, for: indexPath) as! CATItemListViewCell
65 |
66 | if let items = self.source?.sortedItems(tagIndex: self.currentFolderIndex) {
67 | let item = items[indexPath.item]
68 |
69 | cell.titleLabel.text = item.title
70 | cell.bodyLabel.text = item.body
71 | cell.modifiedLabel.text = CATPrettyFormatDate(item.created, dateStyle: .short, timeStyle: .none)
72 | }
73 |
74 | return cell
75 | })
76 |
77 | #if !targetEnvironment(macCatalyst)
78 | collectionView.backgroundColor = .systemGroupedBackground
79 | #endif
80 |
81 | collectionView.backgroundColor = .systemBackground
82 |
83 | collectionView.register(CATItemListViewCell.self, forCellWithReuseIdentifier: CATItemListViewController.cellIdentifier)
84 | collectionView.dataSource = diffableDataSource
85 | collectionView.delegate = self
86 |
87 | if #available(iOS 15.0, macOS 15.0, *) {
88 | collectionView.allowsFocus = true
89 | }
90 |
91 | collectionView.contentInset.top = UIFloat(13)
92 |
93 | NotificationCenter.default.addObserver(forName: .documentChanged, object: nil, queue: nil) { [weak self] _ in
94 | DispatchQueue.main.async {
95 |
96 |
97 | if self!.currentFolderIndex >= 0 {
98 | if let count = self!.source?.dataStore.folders.count {
99 |
100 | if self!.currentFolderIndex < count {
101 | if let tag = self!.source?.dataStore.folders[self!.currentFolderIndex] {
102 | self!.title = tag.title
103 | }
104 | }
105 | else {
106 | self!.title = ""
107 | }
108 | }
109 | }
110 |
111 | self!.reload()
112 | }
113 | }
114 |
115 | NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
116 | NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
117 |
118 | #if !targetEnvironment(macCatalyst)
119 | let searchController = UISearchController()
120 |
121 | searchController.searchResultsUpdater = self
122 | searchController.definesPresentationContext = true
123 | searchController.delegate = self
124 | searchController.obscuresBackgroundDuringPresentation = false
125 |
126 | navigationItem.searchController = searchController
127 | #endif
128 | }
129 |
130 | required init?(coder: NSCoder) {
131 | fatalError("init(coder:) has not been implemented")
132 | }
133 |
134 | override func viewDidLoad() {
135 | super.viewDidLoad()
136 |
137 | self.clearsSelectionOnViewWillAppear = true
138 | }
139 |
140 | override func viewWillAppear(_ animated: Bool) {
141 |
142 | let barItem = UIBarButtonItem(barButtonSystemItem: .compose, target: documentViewController, action: NSSelectorFromString("newItem:"))
143 |
144 | if UIDevice.current.userInterfaceIdiom != .phone {
145 | navigationItem.title = NSLocalizedString("SIDEBAR_ALL_ITEMS", comment: "")
146 | }
147 | navigationItem.rightBarButtonItem = barItem
148 |
149 |
150 | }
151 |
152 | // MARK: - Keyboard Avoidance
153 |
154 | @objc func adjustForKeyboard(notification: Notification) {
155 | guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
156 |
157 | let keyboardScreenEndFrame = keyboardValue.cgRectValue
158 | let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
159 |
160 | if notification.name == UIResponder.keyboardWillHideNotification {
161 | collectionView.contentInset = .zero
162 | } else {
163 | collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
164 | }
165 |
166 | collectionView.scrollIndicatorInsets = collectionView.contentInset
167 | }
168 |
169 | // MARK: - Diffable Data Source
170 |
171 | func reload() {
172 |
173 | var snapshot = NSDiffableDataSourceSnapshot()
174 |
175 | snapshot.appendSections(["items"])
176 |
177 | if let items = source?.sortedItems(tagIndex: currentFolderIndex) {
178 | snapshot.appendItems(items)
179 | }
180 |
181 | guard let diffableDataSource = diffableDataSource else { return }
182 |
183 | diffableDataSource.apply(snapshot, animatingDifferences: false, completion: { [weak self] in
184 | self!.selectItemWithIdentifier(self!.selectedIdentifier)
185 | })
186 | }
187 |
188 | func selectItemWithIdentifier(_ identifier:String) {
189 |
190 | var intendedIndexPath = IndexPath(item: 0, section: 0)
191 | var found = false
192 |
193 | if let items = source?.sortedItems(tagIndex: self.currentFolderIndex) {
194 | for i in 0 ..< items.count {
195 | if items[i].identifier == identifier {
196 | intendedIndexPath.item = i
197 | found = true
198 | break
199 | }
200 | }
201 | }
202 |
203 | if found == true {
204 | guard intendedIndexPath.section < collectionView.numberOfSections else { return }
205 | guard intendedIndexPath.item < collectionView.numberOfItems(inSection: intendedIndexPath.section) else { return }
206 |
207 | collectionView.selectItem(at: intendedIndexPath, animated: false, scrollPosition: [])
208 | }
209 | }
210 |
211 | func pushItemWithIdentifier(_ identifier:String) {
212 |
213 | var intendedIndexPath = IndexPath(item: 0, section: 0)
214 | var found = false
215 |
216 | if let items = source?.sortedItems(tagIndex: self.currentFolderIndex) {
217 | for i in 0 ..< items.count {
218 | if items[i].identifier == identifier {
219 | intendedIndexPath.item = i
220 | found = true
221 | break
222 | }
223 | }
224 | }
225 |
226 | if found == true {
227 | guard intendedIndexPath.section < collectionView.numberOfSections else { return }
228 | guard intendedIndexPath.item < collectionView.numberOfItems(inSection: intendedIndexPath.section) else { return }
229 |
230 | collectionView(collectionView, didSelectItemAt: intendedIndexPath)
231 | }
232 | }
233 |
234 | // MARK: - List View
235 |
236 | override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
237 | if let source = source {
238 | let sortedItems = source.sortedItems(tagIndex: self.currentFolderIndex)
239 | if indexPath.item < sortedItems.count {
240 | let item = sortedItems[indexPath.item]
241 | selectedIdentifier = item.identifier
242 |
243 | guard let detailViewController = documentViewController?.detailViewController else { return }
244 | detailViewController.source = source
245 |
246 | if UIDevice.current.userInterfaceIdiom == .phone || view.window?.traitCollection.horizontalSizeClass == .compact {
247 | navigationController?.pushViewController(detailViewController, animated: true)
248 | }
249 | }
250 | }
251 | }
252 |
253 | override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
254 | cell.selectedBackgroundView = nil
255 | }
256 |
257 | // MARK: - Contextual Menus
258 |
259 | override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
260 |
261 | let config = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { elementArray -> UIMenu? in
262 | return self.contextMenuForIndexPath(indexPath: indexPath)
263 | }
264 |
265 | return config
266 | }
267 |
268 | func contextMenuForIndexPath(indexPath:IndexPath) -> UIMenu {
269 | let symcfg = UIImage.SymbolConfiguration(pointSize: UIFloat(18), weight: .bold)
270 |
271 | var deleteActionsCommand = Array()
272 |
273 | do {
274 | let action = UIAction(title: NSLocalizedString("CONTEXT_DELETE", comment:""), image: UIImage(systemName: "trash.fill", withConfiguration: symcfg), identifier: nil) { (UIAction) in
275 |
276 | if let source = self.source {
277 | if let sortedItems = self.source?.sortedItems(tagIndex: self.currentFolderIndex) {
278 |
279 | if indexPath.item < sortedItems.count {
280 | let item = sortedItems[(indexPath.item)]
281 | let identifier = item.identifier
282 |
283 | source.removeItem(withIdentifier: identifier)
284 | }
285 | }
286 | }
287 |
288 | }
289 | action.attributes = .destructive
290 | deleteActionsCommand.append(action)
291 | }
292 |
293 |
294 | let deleteMenu = UIMenu(title: "", image: nil, identifier: UIMenu.Identifier(rawValue: "SIDEBAR_DELETE"), options: .displayInline, children: deleteActionsCommand)
295 |
296 | return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [deleteMenu])
297 | }
298 |
299 | // MARK: - Keyboard Focus
300 |
301 | override func collectionView(_ collectionView: UICollectionView, selectionFollowsFocusForItemAt indexPath: IndexPath) -> Bool {
302 | return UIDevice.current.userInterfaceIdiom == .mac || view.window?.traitCollection.horizontalSizeClass == .regular
303 | }
304 | }
305 |
306 | extension CATItemListViewController: UISearchResultsUpdating, UISearchControllerDelegate {
307 | func updateSearchResults(for searchController: UISearchController) {
308 | if let source = self.source {
309 | source.searchString = searchController.searchBar.text ?? ""
310 | }
311 | }
312 | }
313 |
--------------------------------------------------------------------------------
/CatalystExample/Number+Scaling.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | /*
8 |
9 | Number+Scaling
10 |
11 | This function lets you wrap all UI-facing metrics in UIFloat()
12 | so that they will scale appropriately for iOS and macOS idioms
13 | without you having to have multiple metrics
14 |
15 | */
16 | import UIKit
17 |
18 | public let supportsMacIdiom = !(UIDevice.current.userInterfaceIdiom == .pad)
19 |
20 | @inlinable func UIFloat(_ value: CGFloat) -> CGFloat
21 | {
22 | #if targetEnvironment(macCatalyst)
23 | return round((value == 0.5) ? 0.5 : value * (supportsMacIdiom ? 0.77 : 1.0))
24 | #else
25 | return value
26 | #endif
27 | }
28 |
--------------------------------------------------------------------------------
/CatalystExample/Resources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "platform" : "ios",
6 | "reference" : "systemBlueColor"
7 | },
8 | "idiom" : "universal"
9 | }
10 | ],
11 | "info" : {
12 | "author" : "xcode",
13 | "version" : 1
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/CatalystExample/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/CatalystExample/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/CatalystExample/Resources/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 |
--------------------------------------------------------------------------------
/CatalystExample/Resources/Credits.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf2580
2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;}
3 | {\colortbl;\red255\green255\blue255;}
4 | {\*\expandedcolortbl;;}
5 | \paperw12240\paperh15840\margl1440\margr1440\vieww9000\viewh8400\viewkind0
6 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0
7 |
8 | \f0\b\fs24 \cf0 Engineering:
9 | \f1\b0 \
10 | Some people\
11 | \
12 |
13 | \f0\b Human Interface Design:
14 | \f1\b0 \
15 | Some other people\
16 | \
17 |
18 | \f0\b Testing:
19 | \f1\b0 \
20 | Hopefully not nobody\
21 | \
22 |
23 | \f0\b Documentation:
24 | \f1\b0 \
25 | Whoever\
26 | \
27 |
28 | \f0\b With special thanks to:
29 | \f1\b0 \
30 | Mom\
31 | }
--------------------------------------------------------------------------------
/CatalystExample/Resources/Example.json:
--------------------------------------------------------------------------------
1 | {
2 | "folders" : [
3 | {
4 | "title": "Folder A",
5 | "itemIDs" : [
6 | "2E6F2460-4B99-4287-8B42-C99991023E21"
7 | ]
8 | },
9 | {
10 | "title": "Folder B",
11 | "itemIDs" : [
12 | "2E6F2460-4B99-4287-8B42-C99991023E22"
13 | ]
14 | }
15 | ],
16 | "items" : [
17 | {
18 | "number" : 1,
19 | "closed" : false,
20 | "title" : "First",
21 | "created" : 544025779.74363601,
22 | "identifier" : "2E6F2460-4B99-4287-8B42-C99991023E21",
23 | "modified" : 644463201.65309298,
24 | "body" : "Lorem ipsum dolor sit amet",
25 | "due" : 644025779.74363601
26 | },
27 | {
28 | "number" : 2,
29 | "closed" : false,
30 | "title" : "Second",
31 | "created" : 644025779.74363601,
32 | "identifier" : "2E6F2460-4B99-4287-8B42-C99991023E22",
33 | "modified" : 644463201.65309298,
34 | "body" : "Lorem ipsum dolor sit amet",
35 | "due" : 644025779.74363601
36 | }
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/CatalystExample/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | CatalystExample
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | LSRequiresIPhoneOS
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 | UISceneConfigurations
30 |
31 | UIWindowSceneSessionRoleApplication
32 |
33 |
34 | UISceneConfigurationName
35 | Main
36 | UISceneDelegateClassName
37 | $(PRODUCT_MODULE_NAME).CATWindowSceneDelegate
38 |
39 |
40 |
41 |
42 | UIApplicationSupportsIndirectInputEvents
43 |
44 | UILaunchStoryboardName
45 | LaunchScreen
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/CatalystExample/Resources/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | "SIDEBAR_TITLE_LIBRARY" = "Library";
8 | "SIDEBAR_ALL_ITEMS" = "All Items";
9 | "SIDEBAR_RECENTS" = "Recent Items";
10 |
11 | "SIDEBAR_TITLE_FOLDERS" = "Folders";
12 |
13 | "ITEM_NONE_SELECTED_NEW_BUTTON" = "New Item";
14 | "ITEM_TITLE_UNTITLED" = "New Item";
15 |
16 | "SOURCE_VIEW_TITLE" = "Example";
17 |
18 | "MAIN_VIEW_HELLO_WORLD_LABEL" = "Hello World!";
19 | "MAIN_VIEW_DO_THING_BUTTON" = "Do thing";
20 |
21 | "TOOLBAR_SEARCH_PLACEHOLDER" = "Search Items";
22 |
23 | "CONTEXT_DELETE" = "Delete Item";
24 |
25 | "MENU_ITEM" = "Item";
26 | "MENU_ITEM_NEW_ITEM" = "New Item";
27 |
28 | "TOOLBAR_NEW_FOLDER_BUTTON" = "New Folder";
29 | "FOLDER_UNTITLED" = "New Folder";
30 |
--------------------------------------------------------------------------------
/CatalystExample/Sidebar/CATSourceListViewController+Reordering.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 |
9 | extension CATSourceListViewController: UICollectionViewDragDelegate, UICollectionViewDropDelegate {
10 |
11 | func shouldMoveItemAt(indexPath: IndexPath) -> Bool {
12 |
13 | if indexPath.isEmpty {
14 | return false
15 | }
16 |
17 | if indexPath.section == 1 && indexPath.item > 0 {
18 | return true
19 | }
20 |
21 | return false
22 | }
23 |
24 |
25 | override func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
26 | return shouldMoveItemAt(indexPath: indexPath)
27 | }
28 |
29 | func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
30 |
31 | if shouldMoveItemAt(indexPath: indexPath) == false {
32 | return []
33 | }
34 |
35 | if let folders = source?.dataStore.folders {
36 | let folder = folders[(indexPath.item - 1)]
37 | let context = CATElementWrapper(element: folder)
38 |
39 | let provider = NSItemProvider(object: context)
40 | let item = UIDragItem(itemProvider: provider)
41 |
42 | session.localContext = context
43 | return [item]
44 | }
45 |
46 | return []
47 | }
48 |
49 | func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {
50 | return (session.localDragSession?.localContext != nil)
51 | }
52 |
53 |
54 | func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
55 |
56 | if let destinationIndexPath = destinationIndexPath {
57 | if !shouldMoveItemAt(indexPath: destinationIndexPath) {
58 | return UICollectionViewDropProposal(operation:.cancel, intent: .unspecified)
59 | }
60 | }
61 |
62 | if session.localDragSession?.localContext != nil {
63 |
64 | if let wrapper = session.localDragSession?.localContext as? CATElementWrapper {
65 |
66 | if let _ = wrapper.element as? CATFolder {
67 |
68 | }
69 | else if let _ = wrapper.element as? CATItem {
70 | return UICollectionViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath)
71 | }
72 | }
73 | }
74 |
75 | return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
76 | }
77 |
78 | override func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
79 |
80 | if proposedIndexPath.isEmpty {
81 | return proposedIndexPath
82 | }
83 |
84 | if proposedIndexPath.section < 1 {
85 | return IndexPath(item: 1, section: 1)
86 | }
87 |
88 | if proposedIndexPath.item < 1 {
89 | return IndexPath(item: 1, section: proposedIndexPath.section)
90 | }
91 |
92 | return proposedIndexPath
93 | }
94 |
95 |
96 | override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
97 | let sourceIndex = UInt(sourceIndexPath.item - 1)
98 | let destinationIndex = UInt(destinationIndexPath.item - 1)
99 |
100 | if let source = source {
101 | source.reorderFolder(at:sourceIndex, to: destinationIndex)
102 | }
103 | }
104 |
105 | func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
106 |
107 | switch coordinator.proposal.operation {
108 | case .move:
109 |
110 | for _ in coordinator.session.items {
111 | if let wrapper = coordinator.session.localDragSession?.localContext as? CATElementWrapper {
112 | if let _ = wrapper.element as? CATFolder {
113 | for dropItem in coordinator.items {
114 | if let sourceIndexPath = dropItem.sourceIndexPath, let destination = coordinator.destinationIndexPath {
115 |
116 | let sourceIndex = UInt(sourceIndexPath.item - 1)
117 | let destinationIndex = UInt(destination.item - 1)
118 |
119 | if sourceIndex != destinationIndex && destination.section == 1 {
120 |
121 |
122 | if let source = source {
123 | source.reorderFolder(at:sourceIndex, to: destinationIndex)
124 | }
125 |
126 | coordinator.drop(dropItem.dragItem, toItemAt: destination)
127 | }
128 | }
129 | }
130 | }
131 | }
132 | }
133 | default:
134 | for dropItem in coordinator.items {
135 | if let sourceIndexPath = dropItem.sourceIndexPath {
136 | coordinator.drop(dropItem.dragItem, toItemAt: sourceIndexPath)
137 | }
138 | }
139 | break
140 | }
141 |
142 | }
143 |
144 | func collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession) {
145 | reload()
146 | }
147 |
148 | func collectionView(_ collectionView: UICollectionView, dropSessionDidExit session: UIDropSession) {
149 |
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/CatalystExample/Sidebar/CATSourceListViewController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 Steven Troughton-Smith (@stroughtonsmith)
3 | Provided as sample code to do with as you wish.
4 | No license or attribution required.
5 | */
6 |
7 | import UIKit
8 |
9 | class CATSourceListViewController: UICollectionViewController {
10 |
11 | var listViewController:CATItemListViewController?
12 | var documentViewController:CATMainViewController?
13 |
14 | static let cellIdentifier = "Cell"
15 | var diffableDataSource:UICollectionViewDiffableDataSource?
16 |
17 | var selectedIdentifier = "all"
18 |
19 | var source:CATSourceFile? {
20 | didSet {
21 | reload()
22 | }
23 | }
24 |
25 | init() {
26 |
27 | let layout = UICollectionViewCompositionalLayout() { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
28 |
29 | var configuration = UICollectionLayoutListConfiguration(appearance: .sidebar)
30 | configuration.showsSeparators = false
31 | configuration.headerMode = .firstItemInSection
32 |
33 | let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
34 |
35 | return section
36 | }
37 |
38 | super.init(collectionViewLayout: layout)
39 |
40 | diffableDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { collectionView, indexPath, ctx in
41 |
42 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CATSourceListViewController.cellIdentifier, for: indexPath)
43 |
44 | var content = UIListContentConfiguration.sidebarCell()
45 | content.textProperties.allowsDefaultTighteningForTruncation = false
46 |
47 | if indexPath.section == 0 {
48 |
49 | switch indexPath.row {
50 | case 0:
51 | content = UIListContentConfiguration.sidebarHeader()
52 |
53 | content.text = NSLocalizedString("SIDEBAR_TITLE_LIBRARY", comment:"")
54 | case 1:
55 | content.text = NSLocalizedString("SIDEBAR_ALL_ITEMS", comment:"")
56 | content.image = UIImage(systemName: "doc.text")
57 | case 2:
58 | content.text = NSLocalizedString("SIDEBAR_RECENTS", comment:"")
59 | content.image = UIImage(systemName: "clock")
60 | default:
61 | break
62 | }
63 |
64 | cell.contentConfiguration = content
65 | }
66 | else {
67 | if indexPath.row == 0 {
68 |
69 | content = UIListContentConfiguration.sidebarHeader()
70 | content.text = NSLocalizedString("SIDEBAR_TITLE_FOLDERS", comment:"")
71 | }
72 | else
73 | {
74 | if let folder = self.source?.dataStore.folders[(indexPath.item - 1)] {
75 | content.text = folder.title
76 | content.image = UIImage(systemName: "folder")
77 | }
78 | }
79 |
80 | cell.contentConfiguration = content
81 | }
82 |
83 | return cell
84 | })
85 |
86 |
87 | #if !targetEnvironment(macCatalyst)
88 | collectionView.backgroundColor = .systemGroupedBackground
89 | #endif
90 |
91 | collectionView.selectionFollowsFocus = true
92 |
93 | collectionView.register(UICollectionViewListCell.self, forCellWithReuseIdentifier: CATSourceListViewController.cellIdentifier)
94 |
95 | collectionView.dataSource = diffableDataSource
96 | collectionView.delegate = self
97 |
98 | collectionView.dragInteractionEnabled = true
99 | collectionView.dragDelegate = self
100 | collectionView.dropDelegate = self
101 |
102 | navigationItem.largeTitleDisplayMode = .always
103 |
104 | NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
105 | NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
106 |
107 | NotificationCenter.default.addObserver(forName: .documentChanged, object: nil, queue: nil) { [weak self] _ in
108 | DispatchQueue.main.async {
109 | self!.reload()
110 | }
111 | }
112 |
113 | title = NSLocalizedString("SOURCE_VIEW_TITLE", comment: "");
114 | }
115 |
116 | required init?(coder: NSCoder) {
117 | fatalError("init(coder:) has not been implemented")
118 | }
119 |
120 | override func viewDidLoad() {
121 | super.viewDidLoad()
122 |
123 | clearsSelectionOnViewWillAppear = false
124 | }
125 |
126 | // MARK: - Diffable Data Source
127 |
128 | func reload() {
129 |
130 | var snapshot = NSDiffableDataSourceSnapshot()
131 |
132 | snapshot.appendSections(["library"])
133 | snapshot.appendItems(["library.header", "all", "recents"])
134 |
135 | if let folders = self.source?.dataStore.folders {
136 | snapshot.appendSections(["folders"])
137 | snapshot.appendItems(["folders.header"])
138 | snapshot.appendItems(folders)
139 |
140 | diffableDataSource?.apply(snapshot, animatingDifferences: false)
141 | { [weak self] in
142 | self!.selectItemWithIdentifier(self!.selectedIdentifier)
143 | }
144 | }
145 | }
146 |
147 | func selectItemWithIdentifier(_ identifier:String) {
148 |
149 | var intendedIndexPath = IndexPath(item: 0, section: 0)
150 | var found = false
151 |
152 |
153 | if identifier == "all" {
154 | intendedIndexPath = IndexPath(item: 1, section: 0)
155 | found = true
156 | }
157 | else if identifier == "recents" {
158 | intendedIndexPath = IndexPath(item: 2, section: 0)
159 | found = true
160 | }
161 |
162 | if found == false {
163 | if let folders = self.source?.dataStore.folders {
164 | for i in 0 ..< folders.count {
165 | if folders[i].identifier == identifier {
166 | intendedIndexPath.item = (i + 1)
167 | intendedIndexPath.section = 1
168 | found = true
169 | break
170 | }
171 | }
172 | }
173 | }
174 |
175 |
176 | if found == true {
177 | collectionView.selectItem(at: intendedIndexPath, animated: false, scrollPosition: [])
178 | }
179 | }
180 |
181 | // MARK: - List View
182 |
183 | override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
184 |
185 | if let listViewController = listViewController {
186 |
187 | if let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first {
188 | if selectedIndexPath.section == 1 {
189 | if let folder = self.source?.dataStore.folders[(selectedIndexPath.item - 1)] {
190 |
191 | selectedIdentifier = folder.identifier
192 | }
193 | }
194 | else {
195 | if selectedIndexPath.item == 1 {
196 | selectedIdentifier = "all"
197 | }
198 | else if selectedIndexPath.item == 2 {
199 | selectedIdentifier = "recents"
200 | }
201 | }
202 | }
203 |
204 | if indexPath.section == 0 {
205 | if indexPath.item == 1 {
206 | listViewController.currentFolderIndex = CATSourceFile.CATFolderIndexAll
207 | }
208 | else {
209 | listViewController.currentFolderIndex = CATSourceFile.CATFolderIndexRecents
210 | }
211 | }
212 | else {
213 | listViewController.currentFolderIndex = (indexPath.item - 1)
214 | }
215 | }
216 |
217 | guard let listViewController = documentViewController?.listViewController else { return }
218 |
219 | if UIDevice.current.userInterfaceIdiom == .phone || view.window?.traitCollection.horizontalSizeClass == .compact {
220 | navigationController?.pushViewController(listViewController, animated: true)
221 | }
222 | }
223 |
224 | override func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
225 | if indexPath.item == 0 {
226 | return false
227 | }
228 | else {
229 |
230 | return true
231 | }
232 | }
233 |
234 | override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
235 | return (indexPath.item != 0)
236 | }
237 |
238 | // MARK: - Keyboard Avoidance
239 |
240 | @objc func adjustForKeyboard(notification: Notification) {
241 | guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
242 |
243 | let keyboardScreenEndFrame = keyboardValue.cgRectValue
244 | let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
245 |
246 | if notification.name == UIResponder.keyboardWillHideNotification {
247 | collectionView.contentInset = .zero
248 | } else {
249 | collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
250 | }
251 |
252 | collectionView.scrollIndicatorInsets = collectionView.contentInset
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Advanced Catalyst Example
2 |
3 | This is an example of a Catalyst app using a three-column layout, with a primary toolbar.
4 |
5 | It includes topics such as:
6 | - Drag & drop reordering of the sidebar items
7 | - Scaling UI metrics between Mac Idiom and iPad Idiom
8 | - Using Codable to load a data store from disk and present it
9 | - Using an AppKit bundle to access APIs not available to Catalyst, like NSSearchToolbarItem
10 | - Mixing a SwiftUI detail view into a UIKit app structure
11 | - Using the UIKit Focus Engine to provide keyboard navigation
12 | - Providing a Credits file for your About window
13 |
14 | ### Screenshot
15 | 
16 |
--------------------------------------------------------------------------------
/catalystapp.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B01C38A126628E8E0027D8A8 /* CATPreferencesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01C38A026628E8E0027D8A8 /* CATPreferencesController.swift */; };
11 | B01C38A3266291C60027D8A8 /* CATAppDelegate+MenuBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01C38A2266291C60027D8A8 /* CATAppDelegate+MenuBuilder.swift */; };
12 | B023052D2663104300776EA0 /* Example.json in Resources */ = {isa = PBXBuildFile; fileRef = B023052C2663104300776EA0 /* Example.json */; };
13 | B037A0362660821500FD3DB1 /* CATElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = B037A0352660821500FD3DB1 /* CATElement.swift */; };
14 | B037A0382660883900FD3DB1 /* CATSourceListViewController+Reordering.swift in Sources */ = {isa = PBXBuildFile; fileRef = B037A0372660883900FD3DB1 /* CATSourceListViewController+Reordering.swift */; };
15 | B03AD6882669C015002904A8 /* CATWindowSceneDelegate+NSToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03AD6872669C015002904A8 /* CATWindowSceneDelegate+NSToolbar.swift */; };
16 | B087F477265F20EA00FE6CD7 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = B087F476265F20EA00FE6CD7 /* Common.swift */; };
17 | B09975672669D7D200338AE1 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = B09975662669D7D100338AE1 /* Credits.rtf */; };
18 | B0AD66AB265D417B00338173 /* CATItemListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AD66AA265D417B00338173 /* CATItemListViewCell.swift */; };
19 | B0AD66AD265D5EA600338173 /* CATFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AD66AC265D5EA600338173 /* CATFolder.swift */; };
20 | B0AD66BC265D646F00338173 /* AppKitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AD66BB265D646F00338173 /* AppKitController.swift */; };
21 | B0AD66C0265D648A00338173 /* AppKitIntegration.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = B0AD66B3265D646100338173 /* AppKitIntegration.framework */; platformFilter = maccatalyst; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
22 | B0CD6D47265CA4C900640087 /* CATMainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0CD6D46265CA4C900640087 /* CATMainViewController.swift */; };
23 | B0D2F5C12517F4F0000FC64C /* CATAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0D2F5C02517F4F0000FC64C /* CATAppDelegate.swift */; };
24 | B0D2F5C72517F4F0000FC64C /* CATSourceFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0D2F5C62517F4F0000FC64C /* CATSourceFile.swift */; };
25 | B0D2F5CC2517F4F2000FC64C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B0D2F5CB2517F4F2000FC64C /* Assets.xcassets */; };
26 | B0D2F5CF2517F4F2000FC64C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B0D2F5CD2517F4F2000FC64C /* LaunchScreen.storyboard */; };
27 | B0D2F5DF2517F6E9000FC64C /* CATItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0D2F5DE2517F6E9000FC64C /* CATItem.swift */; };
28 | B0D2F5E22517F990000FC64C /* CATItemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0D2F5E12517F990000FC64C /* CATItemViewController.swift */; };
29 | B0D2F5E625180210000FC64C /* CATSourceListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0D2F5E525180210000FC64C /* CATSourceListViewController.swift */; };
30 | B0D2F5E92518021E000FC64C /* CATItemListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0D2F5E82518021E000FC64C /* CATItemListViewController.swift */; };
31 | B0D2F5EC251802A7000FC64C /* Number+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0D2F5EB251802A7000FC64C /* Number+Scaling.swift */; };
32 | B0D2F5EF25180583000FC64C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B0D2F5EE25180583000FC64C /* Localizable.strings */; };
33 | B0D2F5F625180704000FC64C /* CATWindowSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0D2F5F525180704000FC64C /* CATWindowSceneDelegate.swift */; };
34 | /* End PBXBuildFile section */
35 |
36 | /* Begin PBXContainerItemProxy section */
37 | B0AD66BD265D647D00338173 /* PBXContainerItemProxy */ = {
38 | isa = PBXContainerItemProxy;
39 | containerPortal = B0D2F5B52517F4F0000FC64C /* Project object */;
40 | proxyType = 1;
41 | remoteGlobalIDString = B0AD66B2265D646100338173;
42 | remoteInfo = AppKitIntegration;
43 | };
44 | /* End PBXContainerItemProxy section */
45 |
46 | /* Begin PBXCopyFilesBuildPhase section */
47 | B0AD66BF265D648200338173 /* CopyFiles */ = {
48 | isa = PBXCopyFilesBuildPhase;
49 | buildActionMask = 2147483647;
50 | dstPath = "";
51 | dstSubfolderSpec = 10;
52 | files = (
53 | B0AD66C0265D648A00338173 /* AppKitIntegration.framework in CopyFiles */,
54 | );
55 | runOnlyForDeploymentPostprocessing = 0;
56 | };
57 | /* End PBXCopyFilesBuildPhase section */
58 |
59 | /* Begin PBXFileReference section */
60 | B01C38A026628E8E0027D8A8 /* CATPreferencesController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATPreferencesController.swift; sourceTree = ""; };
61 | B01C38A2266291C60027D8A8 /* CATAppDelegate+MenuBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATAppDelegate+MenuBuilder.swift"; sourceTree = ""; };
62 | B023052C2663104300776EA0 /* Example.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Example.json; sourceTree = ""; };
63 | B037A0352660821500FD3DB1 /* CATElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATElement.swift; sourceTree = ""; };
64 | B037A0372660883900FD3DB1 /* CATSourceListViewController+Reordering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATSourceListViewController+Reordering.swift"; sourceTree = ""; };
65 | B03AD6872669C015002904A8 /* CATWindowSceneDelegate+NSToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATWindowSceneDelegate+NSToolbar.swift"; sourceTree = ""; };
66 | B087F476265F20EA00FE6CD7 /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; };
67 | B09975652669D67600338AE1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
68 | B09975662669D7D100338AE1 /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; };
69 | B0AD66AA265D417B00338173 /* CATItemListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATItemListViewCell.swift; sourceTree = ""; };
70 | B0AD66AC265D5EA600338173 /* CATFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATFolder.swift; sourceTree = ""; };
71 | B0AD66B3265D646100338173 /* AppKitIntegration.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppKitIntegration.framework; sourceTree = BUILT_PRODUCTS_DIR; };
72 | B0AD66B6265D646100338173 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
73 | B0AD66BB265D646F00338173 /* AppKitController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppKitController.swift; sourceTree = ""; };
74 | B0CD6D46265CA4C900640087 /* CATMainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATMainViewController.swift; sourceTree = ""; };
75 | B0D2F5BD2517F4F0000FC64C /* CatalystExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatalystExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
76 | B0D2F5C02517F4F0000FC64C /* CATAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATAppDelegate.swift; sourceTree = ""; };
77 | B0D2F5C62517F4F0000FC64C /* CATSourceFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATSourceFile.swift; sourceTree = ""; };
78 | B0D2F5CB2517F4F2000FC64C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
79 | B0D2F5CE2517F4F2000FC64C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
80 | B0D2F5D02517F4F2000FC64C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
81 | B0D2F5D72517F4F9000FC64C /* CatalystExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CatalystExample.entitlements; sourceTree = ""; };
82 | B0D2F5DE2517F6E9000FC64C /* CATItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATItem.swift; sourceTree = ""; };
83 | B0D2F5E12517F990000FC64C /* CATItemViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATItemViewController.swift; sourceTree = ""; };
84 | B0D2F5E525180210000FC64C /* CATSourceListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATSourceListViewController.swift; sourceTree = ""; };
85 | B0D2F5E82518021E000FC64C /* CATItemListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATItemListViewController.swift; sourceTree = ""; };
86 | B0D2F5EB251802A7000FC64C /* Number+Scaling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Number+Scaling.swift"; sourceTree = ""; };
87 | B0D2F5EE25180583000FC64C /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; };
88 | B0D2F5F525180704000FC64C /* CATWindowSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATWindowSceneDelegate.swift; sourceTree = ""; };
89 | /* End PBXFileReference section */
90 |
91 | /* Begin PBXFrameworksBuildPhase section */
92 | B0AD66B0265D646100338173 /* Frameworks */ = {
93 | isa = PBXFrameworksBuildPhase;
94 | buildActionMask = 2147483647;
95 | files = (
96 | );
97 | runOnlyForDeploymentPostprocessing = 0;
98 | };
99 | B0D2F5BA2517F4F0000FC64C /* Frameworks */ = {
100 | isa = PBXFrameworksBuildPhase;
101 | buildActionMask = 2147483647;
102 | files = (
103 | );
104 | runOnlyForDeploymentPostprocessing = 0;
105 | };
106 | /* End PBXFrameworksBuildPhase section */
107 |
108 | /* Begin PBXGroup section */
109 | B02ED7732664549E00ACA79A /* Main Window */ = {
110 | isa = PBXGroup;
111 | children = (
112 | B0D2F5F525180704000FC64C /* CATWindowSceneDelegate.swift */,
113 | B03AD6872669C015002904A8 /* CATWindowSceneDelegate+NSToolbar.swift */,
114 | B0CD6D46265CA4C900640087 /* CATMainViewController.swift */,
115 | );
116 | path = "Main Window";
117 | sourceTree = "";
118 | };
119 | B02ED774266454AE00ACA79A /* Controllers */ = {
120 | isa = PBXGroup;
121 | children = (
122 | B0D2F5C02517F4F0000FC64C /* CATAppDelegate.swift */,
123 | B01C38A2266291C60027D8A8 /* CATAppDelegate+MenuBuilder.swift */,
124 | B01C38A026628E8E0027D8A8 /* CATPreferencesController.swift */,
125 | );
126 | path = Controllers;
127 | sourceTree = "";
128 | };
129 | B0AD66B4265D646100338173 /* AppKitIntegration */ = {
130 | isa = PBXGroup;
131 | children = (
132 | B0AD66BB265D646F00338173 /* AppKitController.swift */,
133 | B0AD66B6265D646100338173 /* Info.plist */,
134 | );
135 | path = AppKitIntegration;
136 | sourceTree = "";
137 | };
138 | B0CD6D41265C9DA900640087 /* App */ = {
139 | isa = PBXGroup;
140 | children = (
141 | B02ED774266454AE00ACA79A /* Controllers */,
142 | B02ED7732664549E00ACA79A /* Main Window */,
143 | );
144 | path = App;
145 | sourceTree = "";
146 | };
147 | B0CD6D42265C9DB100640087 /* Sidebar */ = {
148 | isa = PBXGroup;
149 | children = (
150 | B0D2F5E525180210000FC64C /* CATSourceListViewController.swift */,
151 | B037A0372660883900FD3DB1 /* CATSourceListViewController+Reordering.swift */,
152 | );
153 | path = Sidebar;
154 | sourceTree = "";
155 | };
156 | B0CD6D43265C9DBB00640087 /* Middle Column */ = {
157 | isa = PBXGroup;
158 | children = (
159 | B0D2F5E82518021E000FC64C /* CATItemListViewController.swift */,
160 | B0AD66AA265D417B00338173 /* CATItemListViewCell.swift */,
161 | );
162 | path = "Middle Column";
163 | sourceTree = "";
164 | };
165 | B0CD6D44265C9DC900640087 /* Content View */ = {
166 | isa = PBXGroup;
167 | children = (
168 | B0D2F5E12517F990000FC64C /* CATItemViewController.swift */,
169 | );
170 | path = "Content View";
171 | sourceTree = "";
172 | };
173 | B0CD6D45265C9DE700640087 /* Data Model */ = {
174 | isa = PBXGroup;
175 | children = (
176 | B037A0352660821500FD3DB1 /* CATElement.swift */,
177 | B0D2F5DE2517F6E9000FC64C /* CATItem.swift */,
178 | B0AD66AC265D5EA600338173 /* CATFolder.swift */,
179 | B0D2F5C62517F4F0000FC64C /* CATSourceFile.swift */,
180 | );
181 | path = "Data Model";
182 | sourceTree = "";
183 | };
184 | B0D2F5B42517F4F0000FC64C = {
185 | isa = PBXGroup;
186 | children = (
187 | B09975652669D67600338AE1 /* README.md */,
188 | B0D2F5BF2517F4F0000FC64C /* CatalystExample */,
189 | B0AD66B4265D646100338173 /* AppKitIntegration */,
190 | B0D2F5BE2517F4F0000FC64C /* Products */,
191 | );
192 | sourceTree = "";
193 | };
194 | B0D2F5BE2517F4F0000FC64C /* Products */ = {
195 | isa = PBXGroup;
196 | children = (
197 | B0D2F5BD2517F4F0000FC64C /* CatalystExample.app */,
198 | B0AD66B3265D646100338173 /* AppKitIntegration.framework */,
199 | );
200 | name = Products;
201 | sourceTree = "";
202 | };
203 | B0D2F5BF2517F4F0000FC64C /* CatalystExample */ = {
204 | isa = PBXGroup;
205 | children = (
206 | B0D2F5EB251802A7000FC64C /* Number+Scaling.swift */,
207 | B087F476265F20EA00FE6CD7 /* Common.swift */,
208 | B0D2F5D72517F4F9000FC64C /* CatalystExample.entitlements */,
209 | B0CD6D41265C9DA900640087 /* App */,
210 | B0CD6D45265C9DE700640087 /* Data Model */,
211 | B0CD6D42265C9DB100640087 /* Sidebar */,
212 | B0CD6D43265C9DBB00640087 /* Middle Column */,
213 | B0CD6D44265C9DC900640087 /* Content View */,
214 | B0D2F5F12518058A000FC64C /* Resources */,
215 | );
216 | path = CatalystExample;
217 | sourceTree = "";
218 | };
219 | B0D2F5F12518058A000FC64C /* Resources */ = {
220 | isa = PBXGroup;
221 | children = (
222 | B0D2F5EE25180583000FC64C /* Localizable.strings */,
223 | B0D2F5CB2517F4F2000FC64C /* Assets.xcassets */,
224 | B0D2F5CD2517F4F2000FC64C /* LaunchScreen.storyboard */,
225 | B0D2F5D02517F4F2000FC64C /* Info.plist */,
226 | B023052C2663104300776EA0 /* Example.json */,
227 | B09975662669D7D100338AE1 /* Credits.rtf */,
228 | );
229 | path = Resources;
230 | sourceTree = "";
231 | };
232 | /* End PBXGroup section */
233 |
234 | /* Begin PBXHeadersBuildPhase section */
235 | B0AD66AE265D646100338173 /* Headers */ = {
236 | isa = PBXHeadersBuildPhase;
237 | buildActionMask = 2147483647;
238 | files = (
239 | );
240 | runOnlyForDeploymentPostprocessing = 0;
241 | };
242 | /* End PBXHeadersBuildPhase section */
243 |
244 | /* Begin PBXNativeTarget section */
245 | B0AD66B2265D646100338173 /* AppKitIntegration */ = {
246 | isa = PBXNativeTarget;
247 | buildConfigurationList = B0AD66B8265D646100338173 /* Build configuration list for PBXNativeTarget "AppKitIntegration" */;
248 | buildPhases = (
249 | B0AD66AE265D646100338173 /* Headers */,
250 | B0AD66AF265D646100338173 /* Sources */,
251 | B0AD66B0265D646100338173 /* Frameworks */,
252 | B0AD66B1265D646100338173 /* Resources */,
253 | );
254 | buildRules = (
255 | );
256 | dependencies = (
257 | );
258 | name = AppKitIntegration;
259 | productName = AppKitIntegration;
260 | productReference = B0AD66B3265D646100338173 /* AppKitIntegration.framework */;
261 | productType = "com.apple.product-type.framework";
262 | };
263 | B0D2F5BC2517F4F0000FC64C /* CatalystExample */ = {
264 | isa = PBXNativeTarget;
265 | buildConfigurationList = B0D2F5D32517F4F2000FC64C /* Build configuration list for PBXNativeTarget "CatalystExample" */;
266 | buildPhases = (
267 | B0D2F5B92517F4F0000FC64C /* Sources */,
268 | B0D2F5BA2517F4F0000FC64C /* Frameworks */,
269 | B0D2F5BB2517F4F0000FC64C /* Resources */,
270 | B0AD66BF265D648200338173 /* CopyFiles */,
271 | );
272 | buildRules = (
273 | );
274 | dependencies = (
275 | B0AD66BE265D647D00338173 /* PBXTargetDependency */,
276 | );
277 | name = CatalystExample;
278 | productName = issues;
279 | productReference = B0D2F5BD2517F4F0000FC64C /* CatalystExample.app */;
280 | productType = "com.apple.product-type.application";
281 | };
282 | /* End PBXNativeTarget section */
283 |
284 | /* Begin PBXProject section */
285 | B0D2F5B52517F4F0000FC64C /* Project object */ = {
286 | isa = PBXProject;
287 | attributes = {
288 | CLASSPREFIX = ISS;
289 | LastSwiftUpdateCheck = 1220;
290 | LastUpgradeCheck = 1220;
291 | TargetAttributes = {
292 | B0AD66B2265D646100338173 = {
293 | CreatedOnToolsVersion = 12.5;
294 | LastSwiftMigration = 1250;
295 | };
296 | B0D2F5BC2517F4F0000FC64C = {
297 | CreatedOnToolsVersion = 12.2;
298 | };
299 | };
300 | };
301 | buildConfigurationList = B0D2F5B82517F4F0000FC64C /* Build configuration list for PBXProject "catalystapp" */;
302 | compatibilityVersion = "Xcode 9.3";
303 | developmentRegion = en;
304 | hasScannedForEncodings = 0;
305 | knownRegions = (
306 | en,
307 | Base,
308 | );
309 | mainGroup = B0D2F5B42517F4F0000FC64C;
310 | productRefGroup = B0D2F5BE2517F4F0000FC64C /* Products */;
311 | projectDirPath = "";
312 | projectRoot = "";
313 | targets = (
314 | B0D2F5BC2517F4F0000FC64C /* CatalystExample */,
315 | B0AD66B2265D646100338173 /* AppKitIntegration */,
316 | );
317 | };
318 | /* End PBXProject section */
319 |
320 | /* Begin PBXResourcesBuildPhase section */
321 | B0AD66B1265D646100338173 /* Resources */ = {
322 | isa = PBXResourcesBuildPhase;
323 | buildActionMask = 2147483647;
324 | files = (
325 | );
326 | runOnlyForDeploymentPostprocessing = 0;
327 | };
328 | B0D2F5BB2517F4F0000FC64C /* Resources */ = {
329 | isa = PBXResourcesBuildPhase;
330 | buildActionMask = 2147483647;
331 | files = (
332 | B09975672669D7D200338AE1 /* Credits.rtf in Resources */,
333 | B0D2F5CF2517F4F2000FC64C /* LaunchScreen.storyboard in Resources */,
334 | B0D2F5EF25180583000FC64C /* Localizable.strings in Resources */,
335 | B0D2F5CC2517F4F2000FC64C /* Assets.xcassets in Resources */,
336 | B023052D2663104300776EA0 /* Example.json in Resources */,
337 | );
338 | runOnlyForDeploymentPostprocessing = 0;
339 | };
340 | /* End PBXResourcesBuildPhase section */
341 |
342 | /* Begin PBXSourcesBuildPhase section */
343 | B0AD66AF265D646100338173 /* Sources */ = {
344 | isa = PBXSourcesBuildPhase;
345 | buildActionMask = 2147483647;
346 | files = (
347 | B0AD66BC265D646F00338173 /* AppKitController.swift in Sources */,
348 | );
349 | runOnlyForDeploymentPostprocessing = 0;
350 | };
351 | B0D2F5B92517F4F0000FC64C /* Sources */ = {
352 | isa = PBXSourcesBuildPhase;
353 | buildActionMask = 2147483647;
354 | files = (
355 | B037A0362660821500FD3DB1 /* CATElement.swift in Sources */,
356 | B01C38A3266291C60027D8A8 /* CATAppDelegate+MenuBuilder.swift in Sources */,
357 | B0D2F5E22517F990000FC64C /* CATItemViewController.swift in Sources */,
358 | B0D2F5C72517F4F0000FC64C /* CATSourceFile.swift in Sources */,
359 | B087F477265F20EA00FE6CD7 /* Common.swift in Sources */,
360 | B0CD6D47265CA4C900640087 /* CATMainViewController.swift in Sources */,
361 | B01C38A126628E8E0027D8A8 /* CATPreferencesController.swift in Sources */,
362 | B0AD66AB265D417B00338173 /* CATItemListViewCell.swift in Sources */,
363 | B0AD66AD265D5EA600338173 /* CATFolder.swift in Sources */,
364 | B0D2F5F625180704000FC64C /* CATWindowSceneDelegate.swift in Sources */,
365 | B0D2F5EC251802A7000FC64C /* Number+Scaling.swift in Sources */,
366 | B0D2F5E92518021E000FC64C /* CATItemListViewController.swift in Sources */,
367 | B0D2F5C12517F4F0000FC64C /* CATAppDelegate.swift in Sources */,
368 | B0D2F5E625180210000FC64C /* CATSourceListViewController.swift in Sources */,
369 | B037A0382660883900FD3DB1 /* CATSourceListViewController+Reordering.swift in Sources */,
370 | B0D2F5DF2517F6E9000FC64C /* CATItem.swift in Sources */,
371 | B03AD6882669C015002904A8 /* CATWindowSceneDelegate+NSToolbar.swift in Sources */,
372 | );
373 | runOnlyForDeploymentPostprocessing = 0;
374 | };
375 | /* End PBXSourcesBuildPhase section */
376 |
377 | /* Begin PBXTargetDependency section */
378 | B0AD66BE265D647D00338173 /* PBXTargetDependency */ = {
379 | isa = PBXTargetDependency;
380 | platformFilter = maccatalyst;
381 | target = B0AD66B2265D646100338173 /* AppKitIntegration */;
382 | targetProxy = B0AD66BD265D647D00338173 /* PBXContainerItemProxy */;
383 | };
384 | /* End PBXTargetDependency section */
385 |
386 | /* Begin PBXVariantGroup section */
387 | B0D2F5CD2517F4F2000FC64C /* LaunchScreen.storyboard */ = {
388 | isa = PBXVariantGroup;
389 | children = (
390 | B0D2F5CE2517F4F2000FC64C /* Base */,
391 | );
392 | name = LaunchScreen.storyboard;
393 | sourceTree = "";
394 | };
395 | /* End PBXVariantGroup section */
396 |
397 | /* Begin XCBuildConfiguration section */
398 | B0AD66B9265D646100338173 /* Debug */ = {
399 | isa = XCBuildConfiguration;
400 | buildSettings = {
401 | CLANG_ENABLE_MODULES = YES;
402 | CODE_SIGN_STYLE = Automatic;
403 | COMBINE_HIDPI_IMAGES = YES;
404 | CURRENT_PROJECT_VERSION = 1;
405 | DEFINES_MODULE = YES;
406 | DEVELOPMENT_TEAM = 2ZDN69KUUV;
407 | DYLIB_COMPATIBILITY_VERSION = 1;
408 | DYLIB_CURRENT_VERSION = 1;
409 | DYLIB_INSTALL_NAME_BASE = "@rpath";
410 | INFOPLIST_FILE = AppKitIntegration/Info.plist;
411 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
412 | LD_RUNPATH_SEARCH_PATHS = (
413 | "$(inherited)",
414 | "@executable_path/../Frameworks",
415 | "@loader_path/Frameworks",
416 | );
417 | MACOSX_DEPLOYMENT_TARGET = 11.3;
418 | PRODUCT_BUNDLE_IDENTIFIER = com.highcaffeinecontent.AppKitIntegration;
419 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
420 | SDKROOT = macosx;
421 | SKIP_INSTALL = YES;
422 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
423 | SWIFT_VERSION = 5.0;
424 | VERSIONING_SYSTEM = "apple-generic";
425 | VERSION_INFO_PREFIX = "";
426 | };
427 | name = Debug;
428 | };
429 | B0AD66BA265D646100338173 /* Release */ = {
430 | isa = XCBuildConfiguration;
431 | buildSettings = {
432 | CLANG_ENABLE_MODULES = YES;
433 | CODE_SIGN_STYLE = Automatic;
434 | COMBINE_HIDPI_IMAGES = YES;
435 | CURRENT_PROJECT_VERSION = 1;
436 | DEFINES_MODULE = YES;
437 | DEVELOPMENT_TEAM = 2ZDN69KUUV;
438 | DYLIB_COMPATIBILITY_VERSION = 1;
439 | DYLIB_CURRENT_VERSION = 1;
440 | DYLIB_INSTALL_NAME_BASE = "@rpath";
441 | INFOPLIST_FILE = AppKitIntegration/Info.plist;
442 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
443 | LD_RUNPATH_SEARCH_PATHS = (
444 | "$(inherited)",
445 | "@executable_path/../Frameworks",
446 | "@loader_path/Frameworks",
447 | );
448 | MACOSX_DEPLOYMENT_TARGET = 11.3;
449 | PRODUCT_BUNDLE_IDENTIFIER = com.highcaffeinecontent.AppKitIntegration;
450 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
451 | SDKROOT = macosx;
452 | SKIP_INSTALL = YES;
453 | SWIFT_VERSION = 5.0;
454 | VERSIONING_SYSTEM = "apple-generic";
455 | VERSION_INFO_PREFIX = "";
456 | };
457 | name = Release;
458 | };
459 | B0D2F5D12517F4F2000FC64C /* Debug */ = {
460 | isa = XCBuildConfiguration;
461 | buildSettings = {
462 | ALWAYS_SEARCH_USER_PATHS = NO;
463 | CLANG_ANALYZER_NONNULL = YES;
464 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
465 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
466 | CLANG_CXX_LIBRARY = "libc++";
467 | CLANG_ENABLE_MODULES = YES;
468 | CLANG_ENABLE_OBJC_ARC = YES;
469 | CLANG_ENABLE_OBJC_WEAK = YES;
470 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
471 | CLANG_WARN_BOOL_CONVERSION = YES;
472 | CLANG_WARN_COMMA = YES;
473 | CLANG_WARN_CONSTANT_CONVERSION = YES;
474 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
475 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
476 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
477 | CLANG_WARN_EMPTY_BODY = YES;
478 | CLANG_WARN_ENUM_CONVERSION = YES;
479 | CLANG_WARN_INFINITE_RECURSION = YES;
480 | CLANG_WARN_INT_CONVERSION = YES;
481 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
482 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
483 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
484 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
485 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
486 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
487 | CLANG_WARN_STRICT_PROTOTYPES = YES;
488 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
489 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
490 | CLANG_WARN_UNREACHABLE_CODE = YES;
491 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
492 | COPY_PHASE_STRIP = NO;
493 | DEBUG_INFORMATION_FORMAT = dwarf;
494 | ENABLE_STRICT_OBJC_MSGSEND = YES;
495 | ENABLE_TESTABILITY = YES;
496 | GCC_C_LANGUAGE_STANDARD = gnu11;
497 | GCC_DYNAMIC_NO_PIC = NO;
498 | GCC_NO_COMMON_BLOCKS = YES;
499 | GCC_OPTIMIZATION_LEVEL = 0;
500 | GCC_PREPROCESSOR_DEFINITIONS = (
501 | "DEBUG=1",
502 | "$(inherited)",
503 | );
504 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
505 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
506 | GCC_WARN_UNDECLARED_SELECTOR = YES;
507 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
508 | GCC_WARN_UNUSED_FUNCTION = YES;
509 | GCC_WARN_UNUSED_VARIABLE = YES;
510 | IPHONEOS_DEPLOYMENT_TARGET = 14.2;
511 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
512 | MTL_FAST_MATH = YES;
513 | ONLY_ACTIVE_ARCH = YES;
514 | SDKROOT = iphoneos;
515 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
516 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
517 | };
518 | name = Debug;
519 | };
520 | B0D2F5D22517F4F2000FC64C /* Release */ = {
521 | isa = XCBuildConfiguration;
522 | buildSettings = {
523 | ALWAYS_SEARCH_USER_PATHS = NO;
524 | CLANG_ANALYZER_NONNULL = YES;
525 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
526 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
527 | CLANG_CXX_LIBRARY = "libc++";
528 | CLANG_ENABLE_MODULES = YES;
529 | CLANG_ENABLE_OBJC_ARC = YES;
530 | CLANG_ENABLE_OBJC_WEAK = YES;
531 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
532 | CLANG_WARN_BOOL_CONVERSION = YES;
533 | CLANG_WARN_COMMA = YES;
534 | CLANG_WARN_CONSTANT_CONVERSION = YES;
535 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
536 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
537 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
538 | CLANG_WARN_EMPTY_BODY = YES;
539 | CLANG_WARN_ENUM_CONVERSION = YES;
540 | CLANG_WARN_INFINITE_RECURSION = YES;
541 | CLANG_WARN_INT_CONVERSION = YES;
542 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
543 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
544 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
545 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
546 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
547 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
548 | CLANG_WARN_STRICT_PROTOTYPES = YES;
549 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
550 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
551 | CLANG_WARN_UNREACHABLE_CODE = YES;
552 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
553 | COPY_PHASE_STRIP = NO;
554 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
555 | ENABLE_NS_ASSERTIONS = NO;
556 | ENABLE_STRICT_OBJC_MSGSEND = YES;
557 | GCC_C_LANGUAGE_STANDARD = gnu11;
558 | GCC_NO_COMMON_BLOCKS = YES;
559 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
560 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
561 | GCC_WARN_UNDECLARED_SELECTOR = YES;
562 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
563 | GCC_WARN_UNUSED_FUNCTION = YES;
564 | GCC_WARN_UNUSED_VARIABLE = YES;
565 | IPHONEOS_DEPLOYMENT_TARGET = 14.2;
566 | MTL_ENABLE_DEBUG_INFO = NO;
567 | MTL_FAST_MATH = YES;
568 | SDKROOT = iphoneos;
569 | SWIFT_COMPILATION_MODE = wholemodule;
570 | SWIFT_OPTIMIZATION_LEVEL = "-O";
571 | VALIDATE_PRODUCT = YES;
572 | };
573 | name = Release;
574 | };
575 | B0D2F5D42517F4F2000FC64C /* Debug */ = {
576 | isa = XCBuildConfiguration;
577 | buildSettings = {
578 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
579 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
580 | CODE_SIGN_ENTITLEMENTS = CatalystExample/CatalystExample.entitlements;
581 | CODE_SIGN_IDENTITY = "Apple Development";
582 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
583 | CODE_SIGN_STYLE = Automatic;
584 | CURRENT_PROJECT_VERSION = 1;
585 | DEVELOPMENT_TEAM = 2ZDN69KUUV;
586 | INFOPLIST_FILE = CatalystExample/Resources/Info.plist;
587 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
588 | LD_RUNPATH_SEARCH_PATHS = (
589 | "$(inherited)",
590 | "@executable_path/Frameworks",
591 | );
592 | MARKETING_VERSION = 1.0;
593 | PRODUCT_BUNDLE_IDENTIFIER = com.highcaffeinecontent.catalystexample;
594 | PRODUCT_NAME = "$(TARGET_NAME)";
595 | PROVISIONING_PROFILE_SPECIFIER = "";
596 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
597 | RUN_CLANG_STATIC_ANALYZER = YES;
598 | SUPPORTS_MACCATALYST = YES;
599 | SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
600 | SWIFT_VERSION = 5.0;
601 | TARGETED_DEVICE_FAMILY = "1,2,6";
602 | };
603 | name = Debug;
604 | };
605 | B0D2F5D52517F4F2000FC64C /* Release */ = {
606 | isa = XCBuildConfiguration;
607 | buildSettings = {
608 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
609 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
610 | CODE_SIGN_ENTITLEMENTS = CatalystExample/CatalystExample.entitlements;
611 | CODE_SIGN_IDENTITY = "Apple Development";
612 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
613 | CODE_SIGN_STYLE = Automatic;
614 | CURRENT_PROJECT_VERSION = 1;
615 | DEVELOPMENT_TEAM = 2ZDN69KUUV;
616 | INFOPLIST_FILE = CatalystExample/Resources/Info.plist;
617 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
618 | LD_RUNPATH_SEARCH_PATHS = (
619 | "$(inherited)",
620 | "@executable_path/Frameworks",
621 | );
622 | MARKETING_VERSION = 1.0;
623 | PRODUCT_BUNDLE_IDENTIFIER = com.highcaffeinecontent.catalystexample;
624 | PRODUCT_NAME = "$(TARGET_NAME)";
625 | PROVISIONING_PROFILE_SPECIFIER = "";
626 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
627 | RUN_CLANG_STATIC_ANALYZER = YES;
628 | SUPPORTS_MACCATALYST = YES;
629 | SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
630 | SWIFT_VERSION = 5.0;
631 | TARGETED_DEVICE_FAMILY = "1,2,6";
632 | };
633 | name = Release;
634 | };
635 | /* End XCBuildConfiguration section */
636 |
637 | /* Begin XCConfigurationList section */
638 | B0AD66B8265D646100338173 /* Build configuration list for PBXNativeTarget "AppKitIntegration" */ = {
639 | isa = XCConfigurationList;
640 | buildConfigurations = (
641 | B0AD66B9265D646100338173 /* Debug */,
642 | B0AD66BA265D646100338173 /* Release */,
643 | );
644 | defaultConfigurationIsVisible = 0;
645 | defaultConfigurationName = Release;
646 | };
647 | B0D2F5B82517F4F0000FC64C /* Build configuration list for PBXProject "catalystapp" */ = {
648 | isa = XCConfigurationList;
649 | buildConfigurations = (
650 | B0D2F5D12517F4F2000FC64C /* Debug */,
651 | B0D2F5D22517F4F2000FC64C /* Release */,
652 | );
653 | defaultConfigurationIsVisible = 0;
654 | defaultConfigurationName = Release;
655 | };
656 | B0D2F5D32517F4F2000FC64C /* Build configuration list for PBXNativeTarget "CatalystExample" */ = {
657 | isa = XCConfigurationList;
658 | buildConfigurations = (
659 | B0D2F5D42517F4F2000FC64C /* Debug */,
660 | B0D2F5D52517F4F2000FC64C /* Release */,
661 | );
662 | defaultConfigurationIsVisible = 0;
663 | defaultConfigurationName = Release;
664 | };
665 | /* End XCConfigurationList section */
666 | };
667 | rootObject = B0D2F5B52517F4F0000FC64C /* Project object */;
668 | }
669 |
--------------------------------------------------------------------------------
/catalystapp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/catalystapp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/catalystapp.xcodeproj/xcshareddata/xcschemes/AppKitIntegration.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
57 |
58 |
59 |
60 |
62 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/catalystapp.xcodeproj/xcshareddata/xcschemes/CatalystExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------