├── .gitignore ├── CocoaSlideCollection.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── dev.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── dev.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── Cocoa Slides.xcscheme │ └── xcschememanagement.plist ├── CocoaSlideCollection ├── Base.lproj │ ├── BrowserWindow.xib │ ├── Footer.xib │ ├── Header.xib │ ├── MainMenu.xib │ └── Slide.xib ├── CocoaSlideCollection.entitlements ├── CocoaSlideCollection_Prefix.pch ├── Controller │ ├── AAPLAppDelegate.h │ ├── AAPLAppDelegate.m │ ├── AAPLAppDelegate.swift │ ├── AAPLBrowserWindowController.h │ ├── AAPLBrowserWindowController.m │ └── AAPLBrowserWindowController.swift ├── English.lproj │ ├── Credits.rtf │ └── InfoPlist.strings ├── Info.plist ├── Model │ ├── AAPLFileTreeWatcherThread.h │ ├── AAPLFileTreeWatcherThread.m │ ├── AAPLFileTreeWatcherThread.swift │ ├── AAPLImageCollection.h │ ├── AAPLImageCollection.m │ ├── AAPLImageCollection.swift │ ├── AAPLImageFile.h │ ├── AAPLImageFile.m │ ├── AAPLImageFile.swift │ ├── AAPLTag.h │ ├── AAPLTag.m │ └── AAPLTag.swift ├── OOPUtils │ ├── Array+sorted.swift │ └── ObjC.swift ├── Resources │ ├── AppIcon.sketch │ │ ├── Data │ │ ├── QuickLook │ │ │ ├── Preview.png │ │ │ └── Thumbnail.png │ │ ├── metadata │ │ └── version │ ├── SlideArt.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon_128x128.png │ │ │ ├── icon_128x128@2x.png │ │ │ ├── icon_512x512.png │ │ │ └── icon_512x512@2x.png │ │ ├── Contents.json │ │ └── SlideCarrier.imageset │ │ │ ├── Contents.json │ │ │ ├── SlideCarrier.png │ │ │ └── SlideCarrier@2x.png │ └── SlideCarrier.sketch │ │ ├── Data │ │ ├── QuickLook │ │ ├── Preview.png │ │ └── Thumbnail.png │ │ ├── metadata │ │ └── version ├── View │ ├── AAPLFooterView.h │ ├── AAPLFooterView.m │ ├── AAPLFooterView.swift │ ├── AAPLHeaderView.h │ ├── AAPLHeaderView.m │ ├── AAPLHeaderView.swift │ ├── AAPLSlide.h │ ├── AAPLSlide.m │ ├── AAPLSlide.swift │ ├── AAPLSlideBorderView.h │ ├── AAPLSlideBorderView.m │ ├── AAPLSlideBorderView.swift │ ├── AAPLSlideCarrierView.h │ ├── AAPLSlideCarrierView.m │ ├── AAPLSlideCarrierView.swift │ ├── AAPLSlideImageView.h │ ├── AAPLSlideImageView.m │ ├── AAPLSlideImageView.swift │ ├── AAPLSlideTableBackgroundView.h │ ├── AAPLSlideTableBackgroundView.m │ ├── AAPLSlideTableBackgroundView.swift │ └── Layouts │ │ ├── AAPLCircularLayout.h │ │ ├── AAPLCircularLayout.m │ │ ├── AAPLCircularLayout.swift │ │ ├── AAPLLoopLayout.h │ │ ├── AAPLLoopLayout.m │ │ ├── AAPLLoopLayout.swift │ │ ├── AAPLScatterLayout.h │ │ ├── AAPLScatterLayout.m │ │ ├── AAPLScatterLayout.swift │ │ ├── AAPLSlideLayout.h │ │ ├── AAPLSlideLayout.m │ │ ├── AAPLSlideLayout.swift │ │ ├── AAPLWrappedLayout.h │ │ ├── AAPLWrappedLayout.m │ │ └── AAPLWrappedLayout.swift └── main.m ├── LICENSE.txt ├── README.md └── README2.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | -------------------------------------------------------------------------------- /CocoaSlideCollection.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CocoaSlideCollection.xcodeproj/project.xcworkspace/xcuserdata/dev.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection.xcodeproj/project.xcworkspace/xcuserdata/dev.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CocoaSlideCollection.xcodeproj/xcuserdata/dev.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /CocoaSlideCollection.xcodeproj/xcuserdata/dev.xcuserdatad/xcschemes/Cocoa Slides.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CocoaSlideCollection.xcodeproj/xcuserdata/dev.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Cocoa Slides.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 8D15AC270486D014006FF6A4 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Base.lproj/BrowserWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 84 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Base.lproj/Footer.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Base.lproj/Header.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | CA 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Base.lproj/Slide.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /CocoaSlideCollection/CocoaSlideCollection.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CocoaSlideCollection/CocoaSlideCollection_Prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Controller/AAPLAppDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the application delegate declaration. 7 | */ 8 | 9 | #import 10 | 11 | /* 12 | The application delegate opens a browser window for 13 | "/Library/Desktop Pictures" on launch, and handles requests to open 14 | additional browser windows. 15 | */ 16 | 17 | @interface AAPLAppDelegate : NSObject 18 | { 19 | NSMutableSet *browserWindowControllers; 20 | } 21 | 22 | // CocoaSlideCollection's "File" -> "Browse Folder..." (Cmd+O) menu item sends this. 23 | - (IBAction)openBrowserWindow:(id)sender; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Controller/AAPLAppDelegate.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the application delegate implementation. 7 | */ 8 | 9 | #import "AAPLAppDelegate.h" 10 | #import "AAPLBrowserWindowController.h" 11 | 12 | @implementation AAPLAppDelegate 13 | 14 | /* 15 | Given a file:// URL that points to a folder, opens a new browser window that 16 | displays the image files in that folder. 17 | */ 18 | - (void)openBrowserWindowForFolderURL:(NSURL *)folderURL { 19 | AAPLBrowserWindowController *browserWindowController = [[AAPLBrowserWindowController alloc] initWithRootURL:folderURL]; 20 | if (browserWindowController) { 21 | [browserWindowController showWindow:self]; 22 | 23 | /* 24 | Add browserWindowController to browserWindowControllers, to keep it 25 | alive. 26 | */ 27 | if (browserWindowControllers == nil) { 28 | browserWindowControllers = [[NSMutableSet alloc] init]; 29 | } 30 | [browserWindowControllers addObject:browserWindowController]; 31 | 32 | /* 33 | Watch for the window to be closed, so we can let it and its 34 | controller go. 35 | */ 36 | NSWindow *browserWindow = [browserWindowController window]; 37 | if (browserWindow) { 38 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(browserWindowWillClose:) name:NSWindowWillCloseNotification object:browserWindow]; 39 | } 40 | } 41 | } 42 | 43 | /* 44 | Action method invoked by the "File" -> "Open Browser..." menu command. 45 | Prompts the user to choose a folder, using a standard Open panel, then opens 46 | a browser window for that folder using the method above. 47 | */ 48 | - (IBAction)openBrowserWindow:(id)sender { 49 | 50 | NSOpenPanel *openPanel = [NSOpenPanel openPanel]; 51 | openPanel.prompt = @"Choose"; 52 | openPanel.message = @"Choose a directory containing images:"; 53 | openPanel.title = @"Choose Directory"; 54 | openPanel.canChooseDirectories = YES; 55 | openPanel.canChooseFiles = NO; 56 | NSArray *pictureDirectories = NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, NSUserDomainMask, YES); 57 | openPanel.directoryURL = [NSURL fileURLWithPath:pictureDirectories[0]]; 58 | 59 | [openPanel beginWithCompletionHandler:^(NSInteger result) { 60 | if (result == NSModalResponseOK) { 61 | [self openBrowserWindowForFolderURL:openPanel.URLs[0]]; 62 | } 63 | }]; 64 | } 65 | 66 | // When a browser window is closed, release its BrowserWindowController. 67 | - (void)browserWindowWillClose:(NSNotification *)notification { 68 | NSWindow *browserWindow = (NSWindow *)(notification.object); 69 | [browserWindowControllers removeObject:browserWindow.delegate]; 70 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowWillCloseNotification object:browserWindow]; 71 | } 72 | 73 | #pragma mark NSApplicationDelegate Methods 74 | 75 | // Browse a default folder on launch. 76 | - (void)applicationDidFinishLaunching:(NSNotification *)notification { 77 | [self openBrowserWindowForFolderURL:[NSURL fileURLWithPath:@"/Library/Desktop Pictures"]]; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Controller/AAPLAppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLAppDelegate.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/27. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample‚Äôs licensing information 11 | 12 | Abstract: 13 | This is the application delegate declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | /* 19 | The application delegate opens a browser window for 20 | "/Library/Desktop Pictures" on launch, and handles requests to open 21 | additional browser windows. 22 | */ 23 | 24 | @NSApplicationMain 25 | @objc(AAPLAppDelegate) 26 | class AAPLAppDelegate: NSObject, NSApplicationDelegate { 27 | private var browserWindowControllers: Set = [] 28 | 29 | /* 30 | Given a file:// URL that points to a folder, opens a new browser window that 31 | displays the image files in that folder. 32 | */ 33 | private func openBrowserWindowForFolderURL(_ folderURL: URL) { 34 | let browserWindowController = AAPLBrowserWindowController(rootURL: folderURL) 35 | browserWindowController.showWindow(self) 36 | 37 | /* 38 | Add browserWindowController to browserWindowControllers, to keep it 39 | alive. 40 | */ 41 | browserWindowControllers.insert(browserWindowController) 42 | 43 | /* 44 | Watch for the window to be closed, so we can let it and its 45 | controller go. 46 | */ 47 | if let browserWindow = browserWindowController.window { 48 | NotificationCenter.default.addObserver(self, selector: #selector(AAPLAppDelegate.browserWindowWillClose(_:)), name: NSWindow.willCloseNotification, object: browserWindow) 49 | } 50 | } 51 | 52 | // CocoaSlideCollection's "File" -> "Browse Folder..." (Cmd+O) menu item sends this. 53 | /* 54 | Action method invoked by the "File" -> "Open Browser..." menu command. 55 | Prompts the user to choose a folder, using a standard Open panel, then opens 56 | a browser window for that folder using the method above. 57 | */ 58 | @IBAction func openBrowserWindow(_: AnyObject) { 59 | 60 | let openPanel = NSOpenPanel() 61 | openPanel.prompt = "Choose" 62 | openPanel.message = "Choose a directory containing images:" 63 | openPanel.title = "Choose Directory" 64 | openPanel.canChooseDirectories = true 65 | openPanel.canChooseFiles = false 66 | let pictureDirectories = NSSearchPathForDirectoriesInDomains(.picturesDirectory, .userDomainMask, true) 67 | openPanel.directoryURL = URL(fileURLWithPath: pictureDirectories[0]) 68 | 69 | openPanel.begin {result in 70 | if result == .OK { 71 | self.openBrowserWindowForFolderURL(openPanel.urls[0]) 72 | } 73 | } 74 | } 75 | 76 | // When a browser window is closed, release its BrowserWindowController. 77 | @objc func browserWindowWillClose(_ notification: Notification) { 78 | let browserWindow = notification.object as! NSWindow 79 | browserWindowControllers.remove(browserWindow.delegate as! AAPLBrowserWindowController) 80 | NotificationCenter.default.removeObserver(self, name: NSWindow.willCloseNotification, object: browserWindow) 81 | } 82 | 83 | //MARK: NSApplicationDelegate Methods 84 | 85 | // Browse a default folder on launch. 86 | func applicationDidFinishLaunching(_ notification: Notification) { 87 | self.openBrowserWindowForFolderURL(URL(fileURLWithPath: "/Library/Desktop Pictures")) 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Controller/AAPLBrowserWindowController.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the browser window controller declaration. 7 | */ 8 | 9 | #import 10 | 11 | @class AAPLImageCollection; 12 | 13 | typedef enum { 14 | SlideLayoutKindCircular = 0, 15 | SlideLayoutKindLoop = 1, 16 | SlideLayoutKindScatter = 2, 17 | SlideLayoutKindWrapped = 3 18 | } SlideLayoutKind; 19 | 20 | /* 21 | Each browser window is managed by a AAPLBrowserWindowController, which 22 | serves as its CollectionView's dataSource and delegate. (The 23 | CollectionView's dataSource and delegate outlets are wired up in 24 | BrowserWindow.xib, so there is no need to set these properties in code.) 25 | */ 26 | @interface AAPLBrowserWindowController : NSWindowController 27 | { 28 | // Model 29 | NSURL *rootURL; // URL of the folder whose image files the browser is displaying 30 | AAPLImageCollection *imageCollection; // the ImageFiles we found in the folder, which we can access as a flat list or grouped by AAPLTag 31 | 32 | // Views 33 | IBOutlet NSCollectionView *__weak imageCollectionView; // a CollectionView that displays items ("slides") representing the image files 34 | IBOutlet NSTextField *__weak statusTextField; // a TextField that shows informative status 35 | 36 | // UI State 37 | SlideLayoutKind layoutKind; // what kind of layout to use, per the above SlideLayoutKind enumeration 38 | BOOL groupByTag; // YES if our imageCollectionView should show its items grouped by tag, with header and footer views (usable with Wrapped layout only) 39 | BOOL autoUpdateResponseSuspended; // YES when we want to suppress our usual automatic KVO response to assets coming and going 40 | NSSet *indexPathsOfItemsBeingDragged; // when our imageCollectionView is the source for a drag operation, this array of NSIndexPaths identifies the items that are being dragged within or out of it 41 | } 42 | 43 | // Initializes a browser window that's pointed at the given folder URL. 44 | - (id)initWithRootURL:(NSURL *)newRootURL; 45 | 46 | 47 | #pragma mark Outlets 48 | 49 | @property(weak) IBOutlet NSCollectionView *imageCollectionView; 50 | @property(weak) IBOutlet NSTextField *statusTextField; 51 | 52 | 53 | #pragma mark Actions 54 | 55 | - (IBAction)refresh:(id)sender; 56 | 57 | 58 | #pragma mark Properties 59 | 60 | @property SlideLayoutKind layoutKind; 61 | @property BOOL groupByTag; 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /CocoaSlideCollection/English.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1378 2 | {\fonttbl\f0\fnil\fcharset0 LucidaGrande;} 3 | {\colortbl;\red255\green255\blue255;} 4 | \vieww9000\viewh8400\viewkind0 5 | \pard\tx960\tx1920\tx2880\tx3840\tx4800\tx5760\tx6720\tx7680\tx8640\tx9600\qc\partightenfactor0 6 | 7 | \f0\fs20 \cf0 WWDC 2015 Demo Application\ 8 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 9 | \cf0 Session 225 - "What's New in NSCollectionView"\ 10 | } -------------------------------------------------------------------------------- /CocoaSlideCollection/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /CocoaSlideCollection/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleGetInfoString 10 | Cocoa Slide Collection 2.0, © Apple Inc., 2015 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | 10.10 27 | NSHumanReadableCopyright 28 | © Apple Inc., 2015 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLFileTreeWatcherThread.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "FileTreeWatcherThread" class declaration. 7 | */ 8 | 9 | #import 10 | #import 11 | 12 | @interface AAPLFileTreeWatcherThread : NSThread 13 | { 14 | NSArray *paths; // array of paths we're watching (as NSStrings) 15 | void (^handler)(void); // the block to invoke when we sense a change 16 | FSEventStreamRef fsEventStream; // the FSEventStream that's informing us of changes 17 | } 18 | 19 | /* 20 | Creates a new AAPLFileTreeWatcherThread that monitors the file subtree 21 | specified by the given path, and invokes the given "changeHandler" block 22 | each time a change in the file subtree is detected. 23 | 24 | Send -start to the returned instance to start watching the file system. 25 | Send -cancel to stop. 26 | (AAPLFileTreeWatcherThread inherits these API methods from NSThread.) 27 | */ 28 | - initWithPath:(NSString *)pathToWatch changeHandler:(void (^)(void))changeHandler; 29 | 30 | /* 31 | Invoked by AAPLFileTreeWatcherThread to schedule main-thread invocation of 32 | its changeHandler. 33 | */ 34 | - (void)invokeChangeHandler; 35 | 36 | /* 37 | Invoke this to zero out the thread's "changeHandler" pointer when things it 38 | operates on are about to go away. (Sending -cancel to the thread isn't 39 | sufficient to ensure that it won't invoke its "changeHandler" one more 40 | time.) 41 | */ 42 | - (void)detachChangeHandler; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLFileTreeWatcherThread.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "FileTreeWatcherThread" class implementation. 7 | */ 8 | 9 | #import "AAPLFileTreeWatcherThread.h" 10 | 11 | static void AAPLFileTreeWatcherEventStreamCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]); 12 | 13 | @implementation AAPLFileTreeWatcherThread 14 | 15 | - initWithPath:(NSString *)pathToWatch changeHandler:(void (^)(void))changeHandler { 16 | NSParameterAssert(pathToWatch); 17 | NSParameterAssert(changeHandler); 18 | self = [self init]; 19 | if (self) { 20 | [self setName:@"AAPLFileTreeWatcherThread"]; 21 | paths = @[pathToWatch]; 22 | @synchronized(self) { 23 | handler = [changeHandler copy]; 24 | } 25 | } 26 | return self; 27 | } 28 | 29 | - (void)invokeChangeHandler { 30 | @synchronized(self) { 31 | if (handler) { 32 | [[NSOperationQueue mainQueue] addOperationWithBlock:handler]; 33 | } 34 | } 35 | } 36 | 37 | - (void)detachChangeHandler { 38 | @synchronized(self) { 39 | handler = nil; 40 | } 41 | } 42 | 43 | - (void)main { 44 | @autoreleasepool { 45 | 46 | // Create our fsEventStream. 47 | FSEventStreamContext context; 48 | context.version = 0; 49 | context.info = (__bridge void *)self; 50 | context.retain = NULL; 51 | context.release = NULL; 52 | context.copyDescription = NULL; 53 | fsEventStream = FSEventStreamCreate(kCFAllocatorDefault, AAPLFileTreeWatcherEventStreamCallback, &context, CFBridgingRetain(paths), kFSEventStreamEventIdSinceNow, 1.0, kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagWatchRoot | kFSEventStreamCreateFlagIgnoreSelf); 54 | if (fsEventStream != NULL) { 55 | 56 | // Schedule the fsEventStream on our thread's run loop. 57 | NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 58 | CFRunLoopRef cfRunLoop = [runLoop getCFRunLoop]; 59 | FSEventStreamScheduleWithRunLoop(fsEventStream, cfRunLoop, kCFRunLoopCommonModes); 60 | 61 | // Open the faucet. 62 | FSEventStreamStart(fsEventStream); 63 | 64 | // Run until we're asked to stop. 65 | while (![self isCancelled]) { 66 | [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]]; 67 | } 68 | 69 | // Shut off the faucet. 70 | FSEventStreamStop(fsEventStream); 71 | 72 | // Unschedule the fsEventStream on our thread's run loop. 73 | FSEventStreamUnscheduleFromRunLoop(fsEventStream, cfRunLoop, kCFRunLoopCommonModes); 74 | 75 | // Invalidate and release fsEventStream. 76 | FSEventStreamInvalidate(fsEventStream); 77 | FSEventStreamRelease(fsEventStream); 78 | fsEventStream = NULL; 79 | } 80 | } 81 | } 82 | 83 | @end 84 | 85 | static void AAPLFileTreeWatcherEventStreamCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) { 86 | if (numEvents > 0) { 87 | AAPLFileTreeWatcherThread *thread = (__bridge AAPLFileTreeWatcherThread *)clientCallBackInfo; 88 | 89 | [thread invokeChangeHandler]; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLFileTreeWatcherThread.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLFileTreeWatcherThread.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/24. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "FileTreeWatcherThread" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | import CoreServices 18 | 19 | @objc(AAPLFileTreeWatcherThread) 20 | class AAPLFileTreeWatcherThread: Thread { 21 | private var paths: [String] // array of paths we're watching (as NSStrings) 22 | private var handler: (()->Void)! // the block to invoke when we sense a change 23 | private var fsEventStream: FSEventStreamRef? = nil // the FSEventStream that's informing us of changes 24 | 25 | 26 | /* 27 | Creates a new AAPLFileTreeWatcherThread that monitors the file subtree 28 | specified by the given path, and invokes the given "changeHandler" block 29 | each time a change in the file subtree is detected. 30 | 31 | Send -start to the returned instance to start watching the file system. 32 | Send -cancel to stop. 33 | (AAPLFileTreeWatcherThread inherits these API methods from NSThread.) 34 | */ 35 | init(path pathToWatch: String, changeHandler: @escaping ()->Void) { 36 | paths = [pathToWatch] 37 | super.init() 38 | self.name = "AAPLFileTreeWatcherThread" 39 | synchronized(self) { 40 | handler = changeHandler 41 | } 42 | } 43 | 44 | /* 45 | Invoked by AAPLFileTreeWatcherThread to schedule main-thread invocation of 46 | its changeHandler. 47 | */ 48 | func invokeChangeHandler() { 49 | synchronized(self) { 50 | if let handler = handler { 51 | OperationQueue.main.addOperation(handler) 52 | } 53 | } 54 | } 55 | 56 | /* 57 | Invoke this to zero out the thread's "changeHandler" pointer when things it 58 | operates on are about to go away. (Sending -cancel to the thread isn't 59 | sufficient to ensure that it won't invoke its "changeHandler" one more 60 | time.) 61 | */ 62 | func detachChangeHandler() { 63 | synchronized(self) { 64 | handler = nil 65 | } 66 | } 67 | 68 | override func main() { 69 | autoreleasepool { 70 | 71 | // Create our fsEventStream. 72 | var context: FSEventStreamContext = FSEventStreamContext() 73 | context.version = 0 74 | context.info = Unmanaged.passUnretained(self).toOpaque() 75 | context.retain = nil 76 | context.release = nil 77 | context.copyDescription = nil 78 | fsEventStream = FSEventStreamCreate(kCFAllocatorDefault, AAPLFileTreeWatcherEventStreamCallback, &context, paths as CFArray, FSEventStreamEventId(kFSEventStreamEventIdSinceNow), 1.0, FSEventStreamCreateFlags(kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagWatchRoot | kFSEventStreamCreateFlagIgnoreSelf)) 79 | if fsEventStream != nil { 80 | 81 | // Schedule the fsEventStream on our thread's run loop. 82 | let runLoop = RunLoop.current 83 | let cfRunLoop = runLoop.getCFRunLoop() 84 | FSEventStreamScheduleWithRunLoop(fsEventStream!, cfRunLoop, CFRunLoopMode.commonModes.rawValue) 85 | 86 | // Open the faucet. 87 | FSEventStreamStart(fsEventStream!) 88 | 89 | // Run until we're asked to stop. 90 | while !self.isCancelled { 91 | runLoop.run(until: Date(timeIntervalSinceNow: 0.25)) 92 | } 93 | 94 | // Shut off the faucet. 95 | FSEventStreamStop(fsEventStream!) 96 | 97 | // Unschedule the fsEventStream on our thread's run loop. 98 | FSEventStreamUnscheduleFromRunLoop(fsEventStream!, cfRunLoop, CFRunLoopMode.commonModes.rawValue) 99 | 100 | // Invalidate and release fsEventStream. 101 | FSEventStreamInvalidate(fsEventStream!) 102 | FSEventStreamRelease(fsEventStream!) 103 | fsEventStream = nil 104 | } 105 | } 106 | } 107 | 108 | } 109 | 110 | private func AAPLFileTreeWatcherEventStreamCallback(_ streamRef: ConstFSEventStreamRef, clientCallBackInfo: UnsafeMutableRawPointer?, numEvents: Int, eventPaths: UnsafeMutableRawPointer, eventFlags: UnsafePointer, eventIds: UnsafePointer) 111 | { 112 | if numEvents > 0 { 113 | let thread = unsafeBitCast(clientCallBackInfo, to: AAPLFileTreeWatcherThread.self) 114 | 115 | thread.invokeChangeHandler() 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLImageCollection.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "ImageCollection" class declaration. 7 | */ 8 | 9 | #import 10 | 11 | @class AAPLImageFile; 12 | @class AAPLTag; 13 | @class AAPLFileTreeWatcherThread; 14 | 15 | // An AAPLImageCollection encapsulates a list of AAPLImageFile objects, together with a rootURL that identifies the folder (if any) where we found them. It also has a list of associated Tags, each of which can return the list of ImageFiles to which it's applied. 16 | @interface AAPLImageCollection : NSObject 17 | { 18 | NSURL *rootURL; // URL of folder in which we found our imageFiles 19 | AAPLFileTreeWatcherThread *fileTreeWatcherThread; // thread that watches the folder for changes 20 | NSOperationQueue *fileTreeScanQueue; // operation queue for asynchronous scans of the folder's contents 21 | 22 | NSMutableArray *imageFiles; // a flat, ordered list of the collection's ImageFiles 23 | NSMutableDictionary *imageFilesByURL; // an NSURL -> AAPLImageFile lookup table 24 | NSMutableArray *untaggedImageFiles; // a flat, ordered list of the ImageFiles that aren't referenced by any AAPLTag 25 | 26 | NSMutableArray *tags; // a flat, alphabetical list of the collection's Tags 27 | NSMutableDictionary *tagsByName; // an NSString -> AAPLTag lookup table 28 | } 29 | - (id)initWithRootURL:(NSURL *)newRootURL; 30 | 31 | 32 | #pragma mark Properties 33 | 34 | @property(readonly) NSURL *rootURL; 35 | @property(readonly) NSArray *imageFiles; // KVO observable 36 | 37 | 38 | #pragma mark Querying the List of ImageFiles 39 | 40 | - (AAPLImageFile *)imageFileForURL:(NSURL *)imageFileURL; 41 | 42 | 43 | #pragma mark Modifying the List of ImageFiles 44 | 45 | - (void)addImageFile:(AAPLImageFile *)imageFile; 46 | - (void)insertImageFile:(AAPLImageFile *)imageFile atIndex:(NSUInteger)index; 47 | - (void)removeImageFile:(AAPLImageFile *)imageFile; 48 | - (void)removeImageFileAtIndex:(NSUInteger)index; 49 | - (void)moveImageFileFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex; 50 | 51 | 52 | #pragma mark Modifying the List of Tags 53 | 54 | @property(readonly) NSArray *tags; 55 | - (AAPLTag *)tagWithName:(NSString *)name; 56 | - (AAPLTag *)addTagWithName:(NSString *)name; 57 | 58 | @property(readonly) NSArray *untaggedImageFiles; 59 | 60 | 61 | #pragma mark Finding Image Files 62 | 63 | - (void)startOrRestartFileTreeScan; 64 | - (void)stopFileTreeScan; 65 | 66 | - (void)stopWatchingFolder; 67 | 68 | @end 69 | 70 | extern NSString *imageFilesKey; 71 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLImageCollection.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "ImageCollection" class implementation. 7 | */ 8 | 9 | #import "AAPLImageCollection.h" 10 | #import "AAPLImageFile.h" 11 | #import "AAPLTag.h" 12 | #import "AAPLFileTreeWatcherThread.h" 13 | 14 | NSString *imageFilesKey = @"imageFiles"; 15 | 16 | @implementation AAPLImageCollection 17 | 18 | - (id)initWithRootURL:(NSURL *)newRootURL { 19 | 20 | self = [super init]; 21 | if (self) { 22 | rootURL = [newRootURL copy]; 23 | imageFiles = [[NSMutableArray alloc] init]; 24 | imageFilesByURL = [[NSMutableDictionary alloc] init]; 25 | untaggedImageFiles = [[NSMutableArray alloc] init]; 26 | tags = [[NSMutableArray alloc] init]; 27 | tagsByName = [[NSMutableDictionary alloc] init]; 28 | fileTreeScanQueue = [[NSOperationQueue alloc] init]; 29 | fileTreeScanQueue.name = @"AAPLImageCollection File Tree Scan Queue"; 30 | 31 | /* 32 | Start watching the folder for changes. Note that the "self" in this 33 | block creates a retain cycle. To break it, we must 34 | -stopWatchingFolder when closing a browser window. 35 | */ 36 | fileTreeWatcherThread = [[AAPLFileTreeWatcherThread alloc] initWithPath:[newRootURL path] changeHandler:^{ 37 | 38 | // When we detect a change in the folder, scan it to find out what changed. 39 | [self startOrRestartFileTreeScan]; 40 | }]; 41 | [fileTreeWatcherThread start]; 42 | } 43 | return self; 44 | } 45 | 46 | 47 | #pragma mark Property Accessors 48 | 49 | @synthesize rootURL; 50 | @synthesize imageFiles; 51 | @synthesize untaggedImageFiles; 52 | 53 | 54 | #pragma mark Querying the List of ImageFiles 55 | 56 | - (AAPLImageFile *)imageFileForURL:(NSURL *)imageFileURL { 57 | return imageFilesByURL[imageFileURL]; 58 | } 59 | 60 | 61 | #pragma mark Modifying the List of ImageFiles 62 | 63 | - (void)addImageFile:(AAPLImageFile *)imageFile { 64 | [self insertImageFile:imageFile atIndex:imageFiles.count]; 65 | } 66 | 67 | - (void)insertImageFile:(AAPLImageFile *)imageFile atIndex:(NSUInteger)index { 68 | 69 | // Add and update tags, based on the imageFile's tagNames. 70 | NSArray *tagNames = imageFile.tagNames; 71 | if (tagNames.count > 0) { 72 | for (NSString *tagName in imageFile.tagNames) { 73 | AAPLTag *tag = [self tagWithName:tagName]; 74 | if (tag == nil) { 75 | tag = [self addTagWithName:tagName]; 76 | } 77 | [tag insertImageFile:imageFile]; 78 | } 79 | } else { 80 | // ImageFile has no tags, so add it to "untaggedImageFiles" instead. 81 | NSUInteger insertionIndex = [untaggedImageFiles indexOfObject:imageFile inSortedRange:NSMakeRange(0, untaggedImageFiles.count) options:NSBinarySearchingInsertionIndex usingComparator:^NSComparisonResult(AAPLImageFile *imageFile1, AAPLImageFile *imageFile2) { 82 | return [imageFile1.filenameWithoutExtension caseInsensitiveCompare:imageFile2.filenameWithoutExtension]; 83 | }]; 84 | if (insertionIndex == NSNotFound) { 85 | NSLog(@"Failed to find insertion index for untaggedImageFiles"); 86 | } else { 87 | [untaggedImageFiles insertObject:imageFile atIndex:insertionIndex]; 88 | } 89 | } 90 | 91 | // Insert the imageFile into our "imageFiles" array (in a KVO-compliant way). 92 | [[self mutableArrayValueForKey:imageFilesKey] insertObject:imageFile atIndex:index]; 93 | 94 | // Add the imageFile into our "imageFilesByURL" dictionary. 95 | [imageFilesByURL setObject:imageFile forKey:imageFile.url]; 96 | } 97 | 98 | - (void)removeImageFile:(AAPLImageFile *)imageFile { 99 | 100 | // Remove the imageFile from our "imageFiles" array (in a KVO-compliant way). 101 | [[self mutableArrayValueForKey:imageFilesKey] removeObject:imageFile]; 102 | 103 | // Remove the imageFile from our "imageFilesByURL" dictionary. 104 | [imageFilesByURL removeObjectForKey:imageFile.url]; 105 | 106 | // Remove the imageFile from the "imageFiles" arrays of its AAPLTags (if any). 107 | for (NSString *tagName in imageFile.tagNames) { 108 | AAPLTag *tag = [self tagWithName:tagName]; 109 | if (tag) { 110 | [[tag mutableArrayValueForKey:@"imageFiles"] removeObject:imageFile]; 111 | } 112 | } 113 | } 114 | 115 | - (void)removeImageFileAtIndex:(NSUInteger)index { 116 | AAPLImageFile *imageFile = [imageFiles objectAtIndex:index]; 117 | [self removeImageFile:imageFile]; 118 | } 119 | 120 | - (void)moveImageFileFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex { 121 | NSUInteger imageFilesCount = imageFiles.count; 122 | NSParameterAssert(fromIndex < imageFilesCount); 123 | NSParameterAssert(fromIndex < imageFilesCount); 124 | AAPLImageFile *imageFile = [imageFiles objectAtIndex:fromIndex]; 125 | [self removeImageFileAtIndex:fromIndex]; 126 | [self insertImageFile:imageFile atIndex:(toIndex <= fromIndex) ? toIndex : (toIndex - 1)]; 127 | } 128 | 129 | 130 | #pragma mark Modifying the List of Tags 131 | 132 | @synthesize tags; 133 | 134 | - (AAPLTag *)tagWithName:(NSString *)name { 135 | return [tagsByName objectForKey:name]; 136 | } 137 | 138 | - (AAPLTag *)addTagWithName:(NSString *)name { 139 | AAPLTag *tag = [self tagWithName:name]; 140 | if (tag == nil) { 141 | tag = [[AAPLTag alloc] initWithName:name]; 142 | if (tag) { 143 | [tagsByName setObject:tag forKey:name]; 144 | 145 | // Binary-search and insert, in alphabetized tags array. 146 | NSUInteger insertionIndex = [tags indexOfObject:tag inSortedRange:NSMakeRange(0, [tags count]) options:NSBinarySearchingInsertionIndex usingComparator:^NSComparisonResult(AAPLTag *tag1, AAPLTag *tag2) { 147 | return [tag1.name caseInsensitiveCompare:tag2.name]; 148 | }]; 149 | if (insertionIndex == NSNotFound) { 150 | NSLog(@"** ERROR: Can't find insertion index in 'tags' array"); 151 | } else { 152 | [tags insertObject:tag atIndex:insertionIndex]; 153 | } 154 | } 155 | } 156 | return tag; 157 | } 158 | 159 | 160 | #pragma mark Finding Image Files 161 | 162 | - (void)startOrRestartFileTreeScan { 163 | @synchronized(fileTreeScanQueue) { 164 | // Cancel any pending file tree scan operations. 165 | [self stopFileTreeScan]; 166 | 167 | // Enqueue a new file tree scan operation. 168 | [fileTreeScanQueue addOperationWithBlock:^{ 169 | 170 | /* 171 | Enumerate all of the image files in our given rootURL. As we 172 | go, identify three groups of image files: 173 | 174 | (1) files that are in the catalog, but have since changed (the 175 | file's modification date is later than its last-cached date) 176 | 177 | (2) files that exist on disk but are not yet in the catalog 178 | (presumably the file was added and we should create an 179 | ImageFile instance for it) 180 | 181 | (3) files that exist in the ImageCollection but not in the 182 | folder (presumably the file was deleted and we should remove 183 | the corresponding ImageFile instance) 184 | */ 185 | NSMutableArray *filesToProcess = [self.imageFiles mutableCopy]; 186 | AAPLImageFile *imageFile; 187 | NSMutableArray *filesChanged = [NSMutableArray array]; 188 | NSMutableArray *urlsAdded = [NSMutableArray array]; 189 | NSMutableArray *filesRemoved = [NSMutableArray array]; 190 | 191 | NSDirectoryEnumerator *directoryEnumerator = [[NSFileManager defaultManager] enumeratorAtURL:rootURL includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLIsRegularFileKey, NSURLTypeIdentifierKey, NSURLContentModificationDateKey, nil] options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsPackageDescendants) errorHandler:^BOOL(NSURL *url, NSError *error) { 192 | NSLog(@"directoryEnumerator error: %@", error); 193 | return YES; 194 | }]; 195 | for (NSURL *url in directoryEnumerator) { 196 | NSError *error; 197 | NSNumber *isRegularFile = nil; 198 | if ([url getResourceValue:&isRegularFile forKey:NSURLIsRegularFileKey error:&error]) { 199 | if ([isRegularFile boolValue]) { 200 | NSString *fileType = nil; 201 | if ([url getResourceValue:&fileType forKey:NSURLTypeIdentifierKey error:&error]) { 202 | if (UTTypeConformsTo((__bridge CFStringRef)fileType, CFSTR("public.image"))) { 203 | 204 | // Look for a corresponding entry in the catalog. 205 | imageFile = [self imageFileForURL:url]; 206 | if (imageFile != nil) { 207 | // Check whether file has changed. 208 | NSDate *modificationDate = nil; 209 | if ([url getResourceValue:&modificationDate forKey:NSURLContentModificationDateKey error:&error]) { 210 | if ([modificationDate compare:imageFile.dateLastUpdated] == NSOrderedDescending) { 211 | [filesChanged addObject:imageFile]; 212 | } 213 | } 214 | [filesToProcess removeObject:imageFile]; 215 | } else { 216 | // File was added. 217 | [urlsAdded addObject:url]; 218 | } 219 | } 220 | } 221 | } 222 | } 223 | } 224 | 225 | // Check for images in the catalog for which no corresponding file was found. 226 | [filesRemoved addObjectsFromArray:filesToProcess]; 227 | filesToProcess = nil; 228 | 229 | /* 230 | Perform our ImageCollection modifications on the main thread, so 231 | that corresponding KVO notifications and CollectionView updates will 232 | also happen on the main thread. 233 | */ 234 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 235 | 236 | // Remove ImageFiles for files we knew about that have disappeared. 237 | for (AAPLImageFile *imageFile in filesRemoved) { 238 | [self removeImageFile:imageFile]; 239 | } 240 | 241 | // Add ImageFiles for files we've newly discovered. 242 | for (NSURL *imageFileURL in urlsAdded) { 243 | AAPLImageFile *imageFile = [[AAPLImageFile alloc] initWithURL:imageFileURL]; 244 | if (imageFile != nil) { 245 | [self addImageFile:imageFile]; 246 | } 247 | } 248 | }]; 249 | }]; 250 | } 251 | } 252 | 253 | - (void)stopFileTreeScan { 254 | @synchronized(fileTreeScanQueue) { 255 | [fileTreeScanQueue cancelAllOperations]; 256 | } 257 | } 258 | 259 | - (void)stopWatchingFolder { 260 | [fileTreeWatcherThread detachChangeHandler]; 261 | [fileTreeWatcherThread cancel]; 262 | fileTreeWatcherThread = nil; 263 | } 264 | 265 | 266 | #pragma mark Teardown 267 | 268 | - (void)dealloc { 269 | [self stopWatchingFolder]; 270 | } 271 | 272 | @end 273 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLImageCollection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLImageCollection.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/24. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample‚Äôs licensing information 11 | 12 | Abstract: 13 | This is the "ImageCollection" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | let imageFilesKey = "imageFiles" 19 | 20 | // An AAPLImageCollection encapsulates a list of AAPLImageFile objects, together with a rootURL that identifies the folder (if any) where we found them. It also has a list of associated Tags, each of which can return the list of ImageFiles to which it's applied. 21 | @objc(AAPLImageCollection) 22 | class AAPLImageCollection: NSObject { 23 | 24 | //MARK: Properties 25 | 26 | private(set) var rootURL: URL? 27 | @objc private(set) dynamic var imageFiles: [AAPLImageFile] = [] 28 | 29 | private var fileTreeWatcherThread: AAPLFileTreeWatcherThread? 30 | private var fileTreeScanQueue: OperationQueue 31 | 32 | private var imageFilesByURL: [URL: AAPLImageFile] = [:] 33 | private(set) var untaggedImageFiles: [AAPLImageFile] = [] 34 | 35 | @objc dynamic private(set) var tags: [AAPLTag] = [] 36 | private var tagsByName: [String: AAPLTag] = [:] 37 | 38 | 39 | init(rootURL newRootURL: URL) { 40 | 41 | rootURL = newRootURL 42 | let queue = OperationQueue() 43 | queue.name = "AAPLImageCollection File Tree Scan Queue" 44 | fileTreeScanQueue = queue 45 | 46 | /* 47 | Start watching the folder for changes. Note that the "self" in this 48 | block creates a retain cycle. To break it, we must 49 | -stopWatchingFolder when closing a browser window. 50 | */ 51 | super.init() 52 | fileTreeWatcherThread = AAPLFileTreeWatcherThread(path: newRootURL.path) { 53 | 54 | // When we detect a change in the folder, scan it to find out what changed. 55 | self.startOrRestartFileTreeScan() 56 | } 57 | fileTreeWatcherThread!.start() 58 | } 59 | 60 | 61 | //MARK: Querying the List of ImageFiles 62 | 63 | func imageFileForURL(_ imageFileURL: URL) -> AAPLImageFile? { 64 | return imageFilesByURL[imageFileURL] 65 | } 66 | 67 | 68 | //MARK: Modifying the List of ImageFiles 69 | 70 | func addImageFile(_ imageFile: AAPLImageFile) { 71 | self.insertImageFile(imageFile, atIndex: imageFiles.count) 72 | } 73 | 74 | func insertImageFile(_ imageFile: AAPLImageFile, atIndex index: Int) { 75 | 76 | // Add and update tags, based on the imageFile's tagNames. 77 | let tagNames = imageFile.tagNames 78 | if !tagNames.isEmpty { 79 | for tagName in imageFile.tagNames { 80 | var tag = self.tagWithName(tagName) 81 | if tag == nil { 82 | tag = self.addTagWithName(tagName) 83 | } 84 | tag!.insertImageFile(imageFile) 85 | } 86 | } else { 87 | // ImageFile has no tags, so add it to "untaggedImageFiles" instead. 88 | let insertionIndex = untaggedImageFiles.indexOf(imageFile, inSortedRange: untaggedImageFiles.startIndex.. AAPLTag? { 135 | return tagsByName[name] 136 | } 137 | 138 | func addTagWithName(_ name: String) -> AAPLTag { 139 | var tag = self.tagWithName(name) 140 | if tag == nil { 141 | tag = AAPLTag(name: name) 142 | tagsByName[name] = tag 143 | 144 | // Binary-search and insert, in alphabetized tags array. 145 | let insertionIndex = tags.indexOf(tag!, inSortedRange: tags.startIndex.. 10 | 11 | // This is our Model representation of an image file. It provides access to the file's properties and its contained image, including pixel dimensions and a thumbnail preview. 12 | @interface AAPLImageFile : NSObject 13 | { 14 | CGImageSourceRef imageSource; // NULL until metadata is loaded 15 | NSDictionary *imageProperties; // nil until metadata is loaded 16 | } 17 | 18 | - (id)initWithURL:(NSURL *)newURL; 19 | 20 | 21 | #pragma mark File Properties 22 | 23 | @property(copy) NSURL *url; 24 | @property(copy) NSString *fileType; 25 | @property unsigned long long fileSize; 26 | @property(copy) NSDate *dateLastUpdated; 27 | @property(copy) NSArray *tagNames; 28 | 29 | @property(readonly) NSString *filename; 30 | @property(readonly) NSString *filenameWithoutExtension; 31 | @property(readonly) NSString *localizedTypeDescription; 32 | @property(readonly) NSString *dimensionsDescription; 33 | 34 | 35 | #pragma mark Image Properties 36 | 37 | @property(readonly) NSInteger pixelsWide; 38 | @property(readonly) NSInteger pixelsHigh; 39 | 40 | @property(strong) NSImage *previewImage; 41 | 42 | 43 | #pragma mark Loading 44 | 45 | // These are triggered automatically the first time relevant properties are requested, but can be invoked explicitly to force loading earlier. 46 | - (BOOL)loadMetadata; 47 | 48 | - (void)requestPreviewImage; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLImageFile.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "ImageFile" class implementation. 7 | */ 8 | 9 | #import 10 | #import "AAPLImageFile.h" 11 | 12 | @interface AAPLImageFile (Internals) 13 | + (NSOperationQueue *)previewLoadingOperationQueue; 14 | //@property(copy) NSURL *url; 15 | @property(copy) NSString *fileType; 16 | @property(assign) unsigned long long fileSize; 17 | @property(copy) NSDate *dateLastUpdated; 18 | @property(strong) NSImage *previewImage; 19 | @end 20 | 21 | @implementation AAPLImageFile 22 | 23 | + (nonnull NSSet *)keyPathsForValuesAffectingValueForKey:(nonnull NSString *)key { 24 | if ([key isEqual:@"localizedTypeDescription"]) { 25 | return [NSSet setWithObject:@"fileType"]; 26 | } else { 27 | return [super keyPathsForValuesAffectingValueForKey:key]; 28 | } 29 | } 30 | 31 | + (NSOperationQueue *)previewLoadingOperationQueue { 32 | static NSOperationQueue *queue; 33 | if (queue == nil) { 34 | queue = [[NSOperationQueue alloc] init]; 35 | queue.name = @"AAPLImageFile Preview Loading Queue"; 36 | } 37 | return queue; 38 | } 39 | 40 | + (NSDictionary *)demoTagNamesDictionary { 41 | static NSDictionary *demoTagNames; 42 | if (demoTagNames == nil) { 43 | demoTagNames = @{ 44 | @"Abstract" : @[@"Texture"], 45 | @"Antelope Canyon" : @[@"Landscape", @"Texture"], 46 | @"Bahamas Aerial" : @[@"Landscape", @"Texture"], 47 | @"Beach" : @[@"Landscape", @"Water"], 48 | @"Blue Pond" : @[@"Flora", @"Landscape", @"Snow", @"Water"], 49 | @"Bristle Grass" : @[@"Flora", @"Landscape"], 50 | @"Brushes" : @[@"Texture"], 51 | @"Circles" : @[@"Texture"], 52 | @"Death Valley" : @[@"Landscape"], 53 | @"Desert" : @[@"Landscape", @"Texture"], 54 | @"Ducks on a Misty Pond" : @[@"Fauna", @"Landscape", @"Water"], 55 | @"Eagle & Waterfall" : @[@"Fauna", @"Landscape", @"Water"], 56 | @"Earth and Moon" : @[@"Space"], 57 | @"Earth Horizon" : @[@"Space"], 58 | @"Elephant" : @[@"Fauna", @"Landscape"], 59 | @"Flamingos" : @[@"Fauna", @"Landscape", @"Water"], 60 | @"Floating Ice" : @[@"Landscape", @"Snow", @"Water"] 61 | }; 62 | } 63 | return demoTagNames; 64 | } 65 | 66 | + (NSArray *)demoTagNamesForImageFileURL:(NSURL *)url { 67 | NSString *filenameWithoutExtension = [[url lastPathComponent] stringByDeletingPathExtension]; 68 | return [[self demoTagNamesDictionary] objectForKey:filenameWithoutExtension]; 69 | } 70 | 71 | - (id)initWithURL:(NSURL *)newURL { 72 | self = [super init]; 73 | if (self) { 74 | self.url = newURL; 75 | 76 | // Get properties that we can obtain from the URL. 77 | id value; 78 | NSError *error; 79 | if ([self.url getResourceValue:&value forKey:NSURLTypeIdentifierKey error:&error]) { 80 | self.fileType = (NSString *)value; 81 | } 82 | if ([self.url getResourceValue:&value forKey:NSURLFileSizeKey error:&error]) { 83 | self.fileSize = ((NSNumber *)value).unsignedLongLongValue; 84 | } 85 | if ([self.url getResourceValue:&value forKey:NSURLContentModificationDateKey error:&error]) { 86 | self.dateLastUpdated = (NSDate *)value; 87 | } 88 | if ([self.url getResourceValue:&value forKey:NSURLTagNamesKey error:&error]) { 89 | self.tagNames = (NSArray *)value; 90 | } 91 | if (self.tagNames == nil) { 92 | // For Demo purposes, since the image files in "/Library/Desktop Pictures" don't have tags assigned to them, hardwire tagNames of our own. 93 | self.tagNames = [[self class] demoTagNamesForImageFileURL:self.url]; 94 | } 95 | } 96 | return self; 97 | } 98 | 99 | 100 | #pragma mark File Properties 101 | 102 | @synthesize url; 103 | @synthesize fileType; 104 | @synthesize fileSize; 105 | @synthesize dateLastUpdated; 106 | 107 | - (NSString *)filename { 108 | return self.url.lastPathComponent; 109 | } 110 | 111 | - (NSString *)filenameWithoutExtension { 112 | return self.filename.stringByDeletingPathExtension; 113 | } 114 | 115 | - (NSString *)localizedTypeDescription { 116 | NSString *type = self.fileType; 117 | return type ? [[NSWorkspace sharedWorkspace] localizedDescriptionForType:self.fileType] : nil; 118 | } 119 | 120 | - (NSString *)dimensionsDescription { 121 | return [NSString stringWithFormat:@"%ld x %ld", (long)(self.pixelsWide), (long)(self.pixelsHigh)]; 122 | } 123 | 124 | 125 | #pragma mark Image Properties 126 | 127 | - (NSInteger)pixelsWide { 128 | if (imageProperties == nil) { 129 | [self loadMetadata]; 130 | } 131 | return [[imageProperties valueForKey:(NSString *)kCGImagePropertyPixelWidth] intValue]; 132 | } 133 | 134 | - (NSInteger)pixelsHigh { 135 | if (imageProperties == nil) { 136 | [self loadMetadata]; 137 | } 138 | return [[imageProperties valueForKey:(NSString *)kCGImagePropertyPixelHeight] intValue]; 139 | } 140 | 141 | @synthesize previewImage; 142 | 143 | 144 | #pragma mark Loading 145 | 146 | /* Many kinds of image files contain prerendered thumbnail images that can be quickly loaded without having to decode the entire contents of the image file and reconstruct the full-size image. The ImageIO framework's CGImageSource API provides a means to do this, using the CGImageSourceCreateThumbnailAtIndex() function. For more information on CGImageSource objects and their capabilities, see the CGImageSource reference on the Apple Developer Connection website, at http://developer.apple.com/documentation/GraphicsImaging/Reference/CGImageSource/Reference/reference.html 147 | */ 148 | - (BOOL)createImageSource { 149 | 150 | if (imageSource == NULL) { 151 | // Compose absolute URL to file. 152 | NSURL *sourceURL = [[self url] absoluteURL]; 153 | if (sourceURL == nil) { 154 | return NO; 155 | } 156 | 157 | // Create a CGImageSource from the URL. 158 | imageSource = CGImageSourceCreateWithURL((CFURLRef)sourceURL, NULL); 159 | if (imageSource == NULL) { 160 | return NO; 161 | } 162 | CFStringRef imageSourceType = CGImageSourceGetType(imageSource); 163 | if (imageSourceType == NULL) { 164 | CFRelease(imageSource); 165 | return NO; 166 | } 167 | } 168 | return imageSource ? YES : NO; 169 | } 170 | 171 | - (BOOL)loadMetadata { 172 | if (imageProperties == NULL) { 173 | 174 | // Get image properties. 175 | if (![self createImageSource]) { 176 | return NO; 177 | } 178 | 179 | // This code looks at the first image only. 180 | // To be truly general, we'd need to handle the possibility of an image source 181 | // having more than one image to offer us. 182 | // 183 | NSInteger index = 0; 184 | imageProperties = (NSDictionary *)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(imageSource, index, NULL)); 185 | } 186 | 187 | // Return indicating success! 188 | return imageProperties ? YES : NO; 189 | } 190 | 191 | - (void)requestPreviewImage { 192 | if (self.previewImage == nil) { 193 | [[[self class] previewLoadingOperationQueue] addOperationWithBlock:^{ 194 | if ([self createImageSource]) { 195 | NSDictionary *options = [[NSDictionary alloc] initWithObjectsAndKeys: 196 | // Ask ImageIO to create a thumbnail from the file's image data, if it can't find 197 | // a suitable existing thumbnail image in the file. We could comment out the following 198 | // line if only existing thumbnails were desired for some reason (maybe to favor 199 | // performance over being guaranteed a complete set of thumbnails). 200 | [NSNumber numberWithBool:YES], (NSString *)kCGImageSourceCreateThumbnailFromImageIfAbsent, 201 | [NSNumber numberWithInt:160], (NSString *)kCGImageSourceThumbnailMaxPixelSize, 202 | nil]; 203 | CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (CFDictionaryRef)options); 204 | if (thumbnail) { 205 | NSImage *image = [[NSImage alloc] initWithCGImage:thumbnail size:NSZeroSize]; 206 | if (image) { 207 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 208 | self.previewImage = image; 209 | }]; 210 | } 211 | CGImageRelease(thumbnail); 212 | } 213 | } 214 | }]; 215 | } 216 | } 217 | 218 | 219 | #pragma mark Debugging Assistance 220 | 221 | - (NSString *)description { 222 | return [NSString stringWithFormat:@"{ImageFile: %@, tags=%@}", self.url.absoluteString, self.tagNames]; 223 | } 224 | 225 | 226 | #pragma mark Teardown 227 | 228 | - (void)dealloc { 229 | if (imageSource) { 230 | CFRelease(imageSource); 231 | } 232 | } 233 | 234 | @end 235 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLImageFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLImageFile.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/23. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample‚Äôs licensing information 11 | 12 | Abstract: 13 | This is the "ImageFile" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | // This is our Model representation of an image file. It provides access to the file's properties and its contained image, including pixel dimensions and a thumbnail preview. 19 | @objc(AAPLImageFile) 20 | class AAPLImageFile: NSObject { 21 | private var imageSource: CGImageSource? // NULL until metadata is loaded 22 | private var imageProperties: [AnyHashable: Any]? // nil until metadata is loaded 23 | 24 | 25 | //MARK: File Properties 26 | 27 | var url: URL 28 | @objc dynamic var fileType: String? 29 | var fileSize: UInt64 = 0 30 | var dateLastUpdated: Date? 31 | var tagNames: [String] = [] 32 | 33 | 34 | //MARK: Image Properties 35 | 36 | @objc dynamic var previewImage: NSImage? 37 | 38 | 39 | override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set { 40 | if key == "localizedTypeDescription" { 41 | return ["fileType"] 42 | } else { 43 | return super.keyPathsForValuesAffectingValue(forKey: key) 44 | } 45 | } 46 | 47 | private static var previewLoadingOperationQueue: OperationQueue = { 48 | let queue = OperationQueue() 49 | queue.name = "AAPLImageFile Preview Loading Queue" 50 | return queue 51 | }() 52 | 53 | private static let demoTagNamesDictionary: [String: [String]] = [ 54 | "Abstract" : ["Texture"], 55 | "Antelope Canyon" : ["Landscape", "Texture"], 56 | "Bahamas Aerial" : ["Landscape", "Texture"], 57 | "Beach" : ["Landscape", "Water"], 58 | "Blue Pond" : ["Flora", "Landscape", "Snow", "Water"], 59 | "Bristle Grass" : ["Flora", "Landscape"], 60 | "Brushes" : ["Texture"], 61 | "Circles" : ["Texture"], 62 | "Death Valley" : ["Landscape"], 63 | "Desert" : ["Landscape", "Texture"], 64 | "Ducks on a Misty Pond" : ["Fauna", "Landscape", "Water"], 65 | "Eagle & Waterfall" : ["Fauna", "Landscape", "Water"], 66 | "Earth and Moon" : ["Space"], 67 | "Earth Horizon" : ["Space"], 68 | "Elephant" : ["Fauna", "Landscape"], 69 | "Flamingos" : ["Fauna", "Landscape", "Water"], 70 | "Floating Ice" : ["Landscape", "Snow", "Water"] 71 | ] 72 | 73 | class func demoTagNamesForImageFileURL(_ url: URL) -> [String]? { 74 | let filenameWithoutExtension = url.deletingPathExtension().lastPathComponent 75 | let firstWord = filenameWithoutExtension.components(separatedBy: " ")[0] 76 | return self.demoTagNamesDictionary[filenameWithoutExtension] 77 | ?? self.demoTagNamesDictionary[firstWord] 78 | } 79 | 80 | init(URL newURL: URL) { 81 | self.url = newURL 82 | 83 | // Get properties that we can obtain from the URL. 84 | do { 85 | let resource = try newURL.resourceValues(forKeys: [.typeIdentifierKey, .fileSizeKey, .contentModificationDateKey, .tagNamesKey]) 86 | self.fileType = resource.typeIdentifier! 87 | self.fileSize = UInt64(resource.fileSize!) 88 | self.dateLastUpdated = resource.contentModificationDate! 89 | self.tagNames = resource.tagNames ?? [] 90 | } catch _ {} 91 | super.init() 92 | if self.tagNames.isEmpty { 93 | // For Demo purposes, since the image files in "/Library/Desktop Pictures" don't have tags assigned to them, hardwire tagNames of our own. 94 | self.tagNames = type(of: self).demoTagNamesForImageFileURL(self.url) ?? [] 95 | } 96 | } 97 | 98 | 99 | //MARK: File Properties 100 | 101 | var filename: String { 102 | return self.url.lastPathComponent 103 | } 104 | 105 | @objc var filenameWithoutExtension: String? { 106 | return self.url.deletingPathExtension().lastPathComponent 107 | } 108 | 109 | @objc var localizedTypeDescription: String? { 110 | if let type = self.fileType { 111 | return NSWorkspace.shared.localizedDescription(forType: type) 112 | } else { 113 | return nil 114 | } 115 | } 116 | 117 | @objc var dimensionsDescription: String { 118 | return "\(self.pixelsWide) x \(self.pixelsHigh)" 119 | } 120 | 121 | 122 | //MARK: Image Properties 123 | 124 | var pixelsWide: Int { 125 | if imageProperties == nil { 126 | self.loadMetadata() 127 | } 128 | return imageProperties![kCGImagePropertyPixelWidth as AnyHashable] as! Int 129 | } 130 | 131 | var pixelsHigh: Int { 132 | if imageProperties == nil { 133 | self.loadMetadata() 134 | } 135 | return imageProperties![kCGImagePropertyPixelHeight as AnyHashable] as! Int 136 | } 137 | 138 | 139 | //MARK: Loading 140 | 141 | /* Many kinds of image files contain prerendered thumbnail images that can be quickly loaded without having to decode the entire contents of the image file and reconstruct the full-size image. The ImageIO framework's CGImageSource API provides a means to do this, using the CGImageSourceCreateThumbnailAtIndex() function. For more information on CGImageSource objects and their capabilities, see the CGImageSource reference on the Apple Developer Connection website, at http://developer.apple.com/documentation/GraphicsImaging/Reference/CGImageSource/Reference/reference.html 142 | */ 143 | private func createImageSource() -> Bool { 144 | 145 | guard imageSource == nil else {return true} 146 | // Compose absolute URL to file. 147 | let sourceURL = self.url.absoluteURL 148 | 149 | // Create a CGImageSource from the URL. 150 | guard let imageSource = CGImageSourceCreateWithURL(sourceURL as CFURL, nil) else { 151 | return false 152 | } 153 | guard let _ = CGImageSourceGetType(imageSource) else { 154 | return false 155 | } 156 | self.imageSource = imageSource 157 | return true 158 | } 159 | 160 | //MARK: Loading 161 | 162 | // These are triggered automatically the first time relevant properties are requested, but can be invoked explicitly to force loading earlier. 163 | 164 | @discardableResult 165 | func loadMetadata() -> Bool { 166 | guard imageProperties == nil else {return true} 167 | 168 | // Get image properties. 169 | guard self.createImageSource() else { 170 | return false 171 | } 172 | 173 | // This code looks at the first image only. 174 | // To be truly general, we'd need to handle the possibility of an image source 175 | // having more than one image to offer us. 176 | // 177 | let index = 0 178 | imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource!, index, nil) as! [AnyHashable: Any]? 179 | 180 | // Return indicating success! 181 | return imageProperties != nil 182 | } 183 | 184 | func requestPreviewImage() { 185 | guard self.previewImage == nil else {return} 186 | type(of: self).previewLoadingOperationQueue.addOperation { 187 | guard self.createImageSource() else {return} 188 | let options: [AnyHashable: Any] = [ 189 | // Ask ImageIO to create a thumbnail from the file's image data, if it can't find 190 | // a suitable existing thumbnail image in the file. We could comment out the following 191 | // line if only existing thumbnails were desired for some reason (maybe to favor 192 | // performance over being guaranteed a complete set of thumbnails). 193 | kCGImageSourceCreateThumbnailFromImageIfAbsent as AnyHashable: true, 194 | kCGImageSourceThumbnailMaxPixelSize as AnyHashable: 160 195 | ] 196 | guard let thumbnail = CGImageSourceCreateThumbnailAtIndex(self.imageSource!, 0, options as CFDictionary) else {return} 197 | let image = NSImage(cgImage: thumbnail, size: NSZeroSize) 198 | OperationQueue.main.addOperation{ 199 | self.previewImage = image 200 | } 201 | } 202 | } 203 | 204 | 205 | //MARK: Debugging Assistance 206 | 207 | override var description: String { 208 | return "{ImageFile: \(self.url.absoluteString), tags=\(self.tagNames)}" 209 | } 210 | 211 | 212 | } 213 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLTag.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "Tag" class declaration. 7 | */ 8 | 9 | #import 10 | 11 | @class AAPLImageFile; 12 | 13 | // An AAPLTag is a label string that can be applied to ImageFiles. An AAPLImageCollection has a list of Tags, each of which has associated ImageFiles. 14 | @interface AAPLTag : NSObject 15 | { 16 | NSString *name; // the tag string (e.g. "Vacation") 17 | NSMutableArray *imageFiles; // the ImageFiles that have this tag, ordered for display using our desired sort 18 | } 19 | - initWithName:(NSString *)newName; 20 | 21 | @property(readonly) NSString *name; 22 | 23 | @property(readonly) NSArray *imageFiles; 24 | 25 | - (void)insertImageFile:(AAPLImageFile *)imageFile; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLTag.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "Tag" class implementation. 7 | */ 8 | 9 | #import "AAPLTag.h" 10 | #import "AAPLImageFile.h" 11 | 12 | @implementation AAPLTag 13 | 14 | - (id)initWithName:(NSString *)newName { 15 | self = [super init]; 16 | if (self) { 17 | name = [newName copy]; 18 | imageFiles = [[NSMutableArray alloc] init]; 19 | } 20 | return self; 21 | } 22 | 23 | @synthesize name; 24 | @synthesize imageFiles; 25 | 26 | - (void)insertImageFile:(AAPLImageFile *)imageFile { 27 | NSUInteger insertionIndex = [imageFiles indexOfObject:imageFile inSortedRange:NSMakeRange(0, [imageFiles count]) options:NSBinarySearchingInsertionIndex usingComparator:^NSComparisonResult(AAPLImageFile *imageFile1, AAPLImageFile *imageFile2) { 28 | return [imageFile1.filenameWithoutExtension caseInsensitiveCompare:imageFile2.filenameWithoutExtension]; 29 | }]; 30 | if (insertionIndex == NSNotFound) { 31 | NSLog(@"** Couldn't determine insertionIndex for imageFiles array"); 32 | } else { 33 | [imageFiles insertObject:imageFile atIndex:insertionIndex]; 34 | } 35 | } 36 | 37 | - (NSString *)description { 38 | return [NSString stringWithFormat:@"{Tag: %@}", self.name]; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Model/AAPLTag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLTag.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/23. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "Tag" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | // An AAPLTag is a label string that can be applied to ImageFiles. An AAPLImageCollection has a list of Tags, each of which has associated ImageFiles. 19 | @objc(AAPLTag) 20 | class AAPLTag: NSObject { 21 | private(set) var name: String // the tag string (e.g. "Vacation") 22 | @objc dynamic private(set) var imageFiles: [AAPLImageFile] = [] // the ImageFiles that have this tag, ordered for display using our desired sort 23 | 24 | init(name newName: String) { 25 | name = newName 26 | super.init() 27 | } 28 | 29 | func insertImageFile(_ imageFile: AAPLImageFile) { 30 | let insertionIndex = imageFiles.indexOf(imageFile, inSortedRange: imageFiles.startIndex.., usingComparison comparison: (Element, Element)->ComparisonResult) -> Int { 43 | if r.isEmpty { 44 | return r.lowerBound 45 | } 46 | var s = r.lowerBound 47 | var e = r.upperBound - 1 48 | var cr: ComparisonResult = .orderedSame 49 | while s <= e { 50 | let m = (s + e)/2 51 | cr = comparison(element, self[m]) 52 | if cr == .orderedSame { 53 | return m 54 | } 55 | if cr == .orderedAscending { 56 | e = m - 1 57 | } else { 58 | s = m + 1 59 | } 60 | } 61 | return s 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /CocoaSlideCollection/OOPUtils/ObjC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObjC.swift 3 | // OOPUtils 4 | // 5 | // Created by OOPer in cooperation with shlab.jp, on 2015/1/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | func synchronized(_ object: AnyObject, block: () -> Void) { 11 | objc_sync_enter(object) 12 | block() 13 | objc_sync_exit(object) 14 | } 15 | func synchronized(_ object: AnyObject, block: () -> T) -> T { 16 | objc_sync_enter(object) 17 | let result: T = block() 18 | objc_sync_exit(object) 19 | return result 20 | } 21 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/AppIcon.sketch/Data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/AppIcon.sketch/Data -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/AppIcon.sketch/QuickLook/Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/AppIcon.sketch/QuickLook/Preview.png -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/AppIcon.sketch/QuickLook/Thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/AppIcon.sketch/QuickLook/Thumbnail.png -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/AppIcon.sketch/metadata: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | app 6 | com.bohemiancoding.sketch 7 | build 8 | 5355 9 | commit 10 | b7d299b0a34651d1a0e066786b75aa36168d5809 11 | fonts 12 | 13 | HelveticaNeue 14 | 15 | length 16 | 290641 17 | version 18 | 18 19 | 20 | 21 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/AppIcon.sketch/version: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideArt.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "size" : "128x128", 25 | "idiom" : "mac", 26 | "filename" : "icon_128x128.png", 27 | "scale" : "1x" 28 | }, 29 | { 30 | "size" : "128x128", 31 | "idiom" : "mac", 32 | "filename" : "icon_128x128@2x.png", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "mac", 37 | "size" : "256x256", 38 | "scale" : "1x" 39 | }, 40 | { 41 | "idiom" : "mac", 42 | "size" : "256x256", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "512x512", 47 | "idiom" : "mac", 48 | "filename" : "icon_512x512.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512@2x.png", 55 | "scale" : "2x" 56 | } 57 | ], 58 | "info" : { 59 | "version" : 1, 60 | "author" : "xcode" 61 | } 62 | } -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideArt.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/SlideArt.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideArt.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/SlideArt.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideArt.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/SlideArt.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideArt.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/SlideArt.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideArt.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideArt.xcassets/SlideCarrier.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "SlideCarrier.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "filename" : "SlideCarrier@2x.png", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideArt.xcassets/SlideCarrier.imageset/SlideCarrier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/SlideArt.xcassets/SlideCarrier.imageset/SlideCarrier.png -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideArt.xcassets/SlideCarrier.imageset/SlideCarrier@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/SlideArt.xcassets/SlideCarrier.imageset/SlideCarrier@2x.png -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideCarrier.sketch/Data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/SlideCarrier.sketch/Data -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideCarrier.sketch/QuickLook/Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/SlideCarrier.sketch/QuickLook/Preview.png -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideCarrier.sketch/QuickLook/Thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooper-shlab/CocoaSlideCollection-Swift/6cb521aaca9ca95d11ca48f9295d2086ba5bbfeb/CocoaSlideCollection/Resources/SlideCarrier.sketch/QuickLook/Thumbnail.png -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideCarrier.sketch/metadata: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | app 6 | com.bohemiancoding.sketch 7 | build 8 | 5355 9 | commit 10 | b7d299b0a34651d1a0e066786b75aa36168d5809 11 | fonts 12 | 13 | length 14 | 13290 15 | version 16 | 18 17 | 18 | 19 | -------------------------------------------------------------------------------- /CocoaSlideCollection/Resources/SlideCarrier.sketch/version: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLFooterView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "FooterView" class declaration. 7 | */ 8 | 9 | #import 10 | #import "AAPLHeaderView.h" 11 | 12 | @interface AAPLFooterView : AAPLHeaderView 13 | @end 14 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLFooterView.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideCarrierView" class implementation. 7 | */ 8 | 9 | #import "AAPLFooterView.h" 10 | 11 | @implementation AAPLFooterView 12 | 13 | - (void)drawRect:(NSRect)dirtyRect { 14 | [[NSColor colorWithCalibratedWhite:0.85 alpha:0.8] set]; 15 | NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver); 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLFooterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLFooterView.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "FooterView" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | @objc(AAPLFooterView) 19 | class AAPLFooterView: AAPLHeaderView { 20 | 21 | override func draw(_ dirtyRect: NSRect) { 22 | NSColor(calibratedWhite: 0.85, alpha: 0.8).set() 23 | dirtyRect.fill(using: .sourceOver) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLHeaderView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "HeaderView" class declaration. 7 | */ 8 | 9 | #import 10 | #import 11 | 12 | @interface AAPLHeaderView : NSView 13 | 14 | @property(readonly) NSTextField *titleTextField; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLHeaderView.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "HeaderView" class implementation. 7 | */ 8 | 9 | #import "AAPLHeaderView.h" 10 | 11 | @implementation AAPLHeaderView 12 | 13 | // Returns the HeaderView's title NSTextField (if it currently has one). 14 | - (NSTextField *)titleTextField { 15 | for (NSView *view in self.subviews) { 16 | if ([view isKindOfClass:[NSTextField class]]) { 17 | return (NSTextField *)view; 18 | } 19 | } 20 | return nil; 21 | } 22 | 23 | // Draws the HeaderView's background: semitransparent white fill, with highlight and shadow lines at top and bottom. 24 | - (void)drawRect:(NSRect)dirtyRect { 25 | // Fill with semitransparent white. 26 | [[NSColor colorWithCalibratedWhite:0.95 alpha:0.8] set]; 27 | NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver); 28 | 29 | // Fill bottom and top edges with semitransparent gray. 30 | [[NSColor colorWithCalibratedWhite:0.75 alpha:0.8] set]; 31 | NSRect bounds = self.bounds; 32 | NSRect bottomEdgeRect = bounds; 33 | bottomEdgeRect.size.height = 1.0; 34 | NSRectFillUsingOperation(bottomEdgeRect, NSCompositeSourceOver); 35 | 36 | NSRect topEdgeRect = bottomEdgeRect; 37 | topEdgeRect.origin.y = NSMaxY(bounds) - 1.0; 38 | NSRectFillUsingOperation(topEdgeRect, NSCompositeSourceOver); 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLHeaderView.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "HeaderView" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | @objc(AAPLHeaderView) 19 | class AAPLHeaderView: NSView, NSCollectionViewElement { 20 | 21 | // Returns the HeaderView's title NSTextField (if it currently has one). 22 | var titleTextField: NSTextField? { 23 | for view in self.subviews { 24 | if let textField = view as? NSTextField { 25 | return textField 26 | } 27 | } 28 | return nil 29 | } 30 | 31 | // Draws the HeaderView's background: semitransparent white fill, with highlight and shadow lines at top and bottom. 32 | override func draw(_ dirtyRect: NSRect) { 33 | // Fill with semitransparent white. 34 | NSColor(calibratedWhite: 0.95, alpha: 0.7).set() 35 | dirtyRect.fill(using: .sourceOver) 36 | 37 | // Fill bottom and top edges with semitransparent gray. 38 | NSColor(calibratedWhite: 0.75, alpha: 0.8).set() 39 | let bounds = self.bounds 40 | var bottomEdgeRect = bounds 41 | bottomEdgeRect.size.height = 1.0 42 | bottomEdgeRect.fill(using: .sourceOver) 43 | 44 | var topEdgeRect = bottomEdgeRect 45 | topEdgeRect.origin.y = NSMaxY(bounds) - 1.0 46 | topEdgeRect.fill(using: .sourceOver) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlide.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "Slide" NSCollectionViewItem subclass declaration. 7 | */ 8 | 9 | #import 10 | 11 | /* 12 | An NSCollectionViewItem that visually represents an AAPLImageFile in an 13 | NSCollectionView. A Slide's "representedObject" property points to its 14 | AAPLImageFile. 15 | */ 16 | @interface AAPLSlide : NSCollectionViewItem 17 | 18 | #pragma mark Outlets 19 | 20 | // From NSCollectionViewItem, we also inherit an "imageView" outlet (which we wire up to the AAPLSlideImageView that shows our ImageFile's previewImage) and a "textField" outlet (which we wire up to the NSTextField that shows the ImageFile's filenameWithoutExtension). 21 | 22 | // An NSTextField that shows a description of the ImageFile's kind (e.g. "JPEG image", "PNG image") 23 | @property(weak) IBOutlet NSTextField *kindTextField; 24 | 25 | // An NSTextField that shows the pixel dimensions of the ImageFile's main image (e.g. "5120 x 2880") 26 | @property(weak) IBOutlet NSTextField *dimensionsTextField; 27 | 28 | 29 | #pragma mark Actions 30 | 31 | - (IBAction)openImageFile:(id)sender; 32 | - (IBAction)setCollectionViewBackground:(id)sender; 33 | - (IBAction)clearCollectionViewBackground:(id)sender; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlide.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "Slide" NSCollectionViewItem subclass implementation. 7 | */ 8 | 9 | #import "AAPLSlide.h" 10 | #import "AAPLImageFile.h" 11 | #import "AAPLSlideCarrierView.h" 12 | #import "AAPLSlideLayout.h" 13 | #import "AAPLSlideTableBackgroundView.h" 14 | #import 15 | 16 | @implementation AAPLSlide 17 | 18 | #pragma mark Selection and Highlighting Support 19 | 20 | - (void)setHighlightState:(NSCollectionViewItemHighlightState)newHighlightState { 21 | [super setHighlightState:newHighlightState]; 22 | 23 | // Relay the newHighlightState to our AAPLSlideCarrierView. 24 | [(AAPLSlideCarrierView *)[self view] setHighlightState:newHighlightState]; 25 | } 26 | 27 | - (void)setSelected:(BOOL)selected { 28 | [super setSelected:selected]; 29 | 30 | // Relay the new "selected" state to our AAPLSlideCarrierView. 31 | [(AAPLSlideCarrierView *)[self view] setSelected:selected]; 32 | } 33 | 34 | 35 | #pragma mark Represented Object 36 | 37 | - (AAPLImageFile *)imageFile { 38 | return (AAPLImageFile *)(self.representedObject); 39 | } 40 | 41 | // We set a Slide's representedObject to point to the AAPLImageFile it stands for. If you aren't using Bindings to provide the desired content for your item's views, an override of -setRepresentedObject: is a handy place to manually set such content when the model object (AAPLImageFile) is first associated with the item (AAPLSlide). (Another good place to do that is in the -collectionView:willDisplayItem:forRepresentedObjectAtIndexPath: delegate method, depending how your like to factor your code.) Our project uses Bindings to populate a Slide's imageView and NSTextFields, but we do use -setRepresentedObject: as an opportunity to request asynchronous loading of the ImageFile's previewImage. When the previewImage has finished loading on a background thread, the AAPLImageFile will get a -setPreviewImage: message, scheduled for delivery on the main thread. The Slide's imageView, whose content is bound to our representedObject's previewImage property, will then automatically show the loaded preview image. 42 | - (void)setRepresentedObject:(id)newRepresentedObject { 43 | [super setRepresentedObject:newRepresentedObject]; 44 | 45 | // Request loading of the ImageFile's previewImage. 46 | [self.imageFile requestPreviewImage]; 47 | } 48 | 49 | 50 | #pragma mark Event Handling 51 | 52 | // When a slide is double-clicked, open the image file. 53 | - (void)mouseDown:(NSEvent *)theEvent { 54 | if ([theEvent clickCount] == 2) { 55 | [self openImageFile:self]; 56 | } else { 57 | [super mouseDown:theEvent]; 58 | } 59 | } 60 | 61 | 62 | #pragma mark Actions 63 | 64 | // Open the image file, using the default app for files of its type. 65 | - (IBAction)openImageFile:(id)sender { 66 | NSURL *url = self.imageFile.url; 67 | if (url) { 68 | [[NSWorkspace sharedWorkspace] openURL:url]; 69 | } 70 | } 71 | 72 | - (AAPLSlideTableBackgroundView *)slideTableBackgroundView { 73 | // Find our AAPLSlideTableBackgroundView via NSCollectionViewItem's "collectionView" property. 74 | NSView *backgroundView = self.collectionView.backgroundView; 75 | return [backgroundView isKindOfClass:[AAPLSlideTableBackgroundView class]] ? (AAPLSlideTableBackgroundView *)backgroundView : nil; 76 | } 77 | 78 | // Set the image as the CollectionView's background (using the "backgroundView" property). 79 | - (IBAction)setCollectionViewBackground:(id)sender { 80 | self.slideTableBackgroundView.image = [[NSImage alloc] initByReferencingURL:self.imageFile.url]; 81 | } 82 | 83 | // Clear the CollectionView's background back to its default appearance. 84 | - (IBAction)clearCollectionViewBackground:(id)sender { 85 | self.slideTableBackgroundView.image = nil; 86 | } 87 | 88 | 89 | #pragma mark Drag and Drop Support 90 | 91 | // Override NSCollectionViewItem's -draggingImageComponents getter to return a snapshot of the entire slide as its dragging image. 92 | - (NSArray *)draggingImageComponents { 93 | 94 | // Image itemRootView. 95 | NSView *itemRootView = self.view; 96 | NSRect itemBounds = itemRootView.bounds; 97 | NSBitmapImageRep *bitmap = [itemRootView bitmapImageRepForCachingDisplayInRect:itemBounds]; 98 | unsigned char *bitmapData = bitmap.bitmapData; 99 | if (bitmapData) { 100 | bzero(bitmapData, bitmap.bytesPerRow * bitmap.pixelsHigh); 101 | } 102 | 103 | /* 104 | -cacheDisplayInRect:toBitmapImageRep: won't capture the "SlideCarrier" 105 | image, since it's rendered via the layer contents property. Work around 106 | that by drawing the image into the bitmap ourselves, using a bitmap 107 | graphics context. 108 | */ 109 | // Work around SlideCarrierView layer contents not being rendered to bitmap. 110 | NSImage *slideCarrierImage = [NSImage imageNamed:@"SlideCarrier"]; 111 | [NSGraphicsContext saveGraphicsState]; 112 | NSGraphicsContext *oldContext = [NSGraphicsContext currentContext]; 113 | [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]]; 114 | [slideCarrierImage drawInRect:itemBounds fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; 115 | [NSGraphicsContext setCurrentContext:oldContext]; 116 | [NSGraphicsContext restoreGraphicsState]; 117 | 118 | /* 119 | Invoke -cacheDisplayInRect:toBitmapImageRep: to render the rest of the 120 | itemRootView subtree into the bitmap. 121 | */ 122 | [itemRootView cacheDisplayInRect:itemBounds toBitmapImageRep:bitmap]; 123 | NSImage *image = [[NSImage alloc] initWithSize:[bitmap size]]; 124 | [image addRepresentation:bitmap]; 125 | 126 | NSDraggingImageComponent *component = [[NSDraggingImageComponent alloc] initWithKey:NSDraggingImageComponentIconKey]; 127 | component.frame = itemBounds; 128 | component.contents = image; 129 | 130 | return [NSArray arrayWithObject:component]; 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlide.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLSlide.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample‚Äôs licensing information 11 | 12 | Abstract: 13 | This is the "Slide" NSCollectionViewItem subclass declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | /* 19 | An NSCollectionViewItem that visually represents an AAPLImageFile in an 20 | NSCollectionView. A Slide's "representedObject" property points to its 21 | AAPLImageFile. 22 | */ 23 | @objc(AAPLSlide) 24 | class AAPLSlide: NSCollectionViewItem { 25 | 26 | //MARK: Outlets 27 | 28 | // From NSCollectionViewItem, we also inherit an "imageView" outlet (which we wire up to the AAPLSlideImageView that shows our ImageFile's previewImage) and a "textField" outlet (which we wire up to the NSTextField that shows the ImageFile's filenameWithoutExtension). 29 | 30 | // An NSTextField that shows a description of the ImageFile's kind (e.g. "JPEG image", "PNG image") 31 | @IBOutlet weak var kindTextField: NSTextField! 32 | 33 | // An NSTextField that shows the pixel dimensions of the ImageFile's main image (e.g. "5120 x 2880") 34 | @IBOutlet weak var dimensionsTextField: NSTextField! 35 | 36 | 37 | //MARK: Selection and Highlighting Support 38 | 39 | override var highlightState: NSCollectionViewItem.HighlightState { 40 | get { 41 | return super.highlightState 42 | } 43 | set(newHighlightState) { 44 | super.highlightState = newHighlightState 45 | 46 | // Relay the newHighlightState to our AAPLSlideCarrierView. 47 | (self.view as! AAPLSlideCarrierView).highlightState = newHighlightState 48 | } 49 | } 50 | 51 | override var isSelected: Bool { 52 | get { 53 | return super.isSelected 54 | } 55 | set { 56 | super.isSelected = newValue 57 | 58 | // Relay the new "selected" state to our AAPLSlideCarrierView. 59 | (self.view as! AAPLSlideCarrierView).selected = newValue 60 | } 61 | } 62 | 63 | 64 | //MARK: Represented Object 65 | 66 | var imageFile: AAPLImageFile? { 67 | return self.representedObject as! AAPLImageFile? 68 | } 69 | 70 | // We set a Slide's representedObject to point to the AAPLImageFile it stands for. If you aren't using Bindings to provide the desired content for your item's views, an override of -setRepresentedObject: is a handy place to manually set such content when the model object (AAPLImageFile) is first associated with the item (AAPLSlide). (Another good place to do that is in the -collectionView:willDisplayItem:forRepresentedObjectAtIndexPath: delegate method, depending how your like to factor your code.) Our project uses Bindings to populate a Slide's imageView and NSTextFields, but we do use -setRepresentedObject: as an opportunity to request asynchronous loading of the ImageFile's previewImage. When the previewImage has finished loading on a background thread, the AAPLImageFile will get a -setPreviewImage: message, scheduled for delivery on the main thread. The Slide's imageView, whose content is bound to our representedObject's previewImage property, will then automatically show the loaded preview image. 71 | override var representedObject: Any? { 72 | get { 73 | return super.representedObject as AnyObject? 74 | } 75 | set(newRepresentedObject) { 76 | super.representedObject = newRepresentedObject 77 | 78 | // Request loading of the ImageFile's previewImage. 79 | self.imageFile?.requestPreviewImage() 80 | } 81 | } 82 | 83 | 84 | //MARK: Event Handling 85 | 86 | // When a slide is double-clicked, open the image file. 87 | override func mouseDown(with theEvent: NSEvent) { 88 | if theEvent.clickCount == 2 { 89 | self.openImageFile(self) 90 | } else { 91 | super.mouseDown(with: theEvent) 92 | } 93 | } 94 | 95 | 96 | //MARK: Actions 97 | 98 | // Open the image file, using the default app for files of its type. 99 | @IBAction func openImageFile(_: AnyObject) { 100 | if let url = self.imageFile?.url { 101 | NSWorkspace.shared.open(url as URL) 102 | } 103 | } 104 | 105 | private var slideTableBackgroundView: AAPLSlideTableBackgroundView? { 106 | // Find our AAPLSlideTableBackgroundView via NSCollectionViewItem's "collectionView" property. 107 | let backgroundView = self.collectionView?.backgroundView as? AAPLSlideTableBackgroundView 108 | return backgroundView 109 | } 110 | 111 | // Set the image as the CollectionView's background (using the "backgroundView" property). 112 | @IBAction func setCollectionViewBackground(_: AnyObject) { 113 | self.slideTableBackgroundView?.image = NSImage(byReferencing: self.imageFile!.url) 114 | } 115 | 116 | // Clear the CollectionView's background back to its default appearance. 117 | @IBAction func clearCollectionViewBackground(_: AnyObject) { 118 | self.slideTableBackgroundView?.image = nil 119 | } 120 | 121 | 122 | //MARK: Drag and Drop Support 123 | 124 | // Override NSCollectionViewItem's -draggingImageComponents getter to return a snapshot of the entire slide as its dragging image. 125 | override var draggingImageComponents: [NSDraggingImageComponent] { 126 | 127 | // Image itemRootView. 128 | let itemRootView = self.view 129 | let itemBounds = itemRootView.bounds 130 | let bitmap = itemRootView.bitmapImageRepForCachingDisplay(in: itemBounds)! 131 | let bitmapData = bitmap.bitmapData 132 | if bitmapData != nil { 133 | bzero(bitmapData, bitmap.bytesPerRow * bitmap.pixelsHigh) 134 | } 135 | 136 | /* 137 | -cacheDisplayInRect:toBitmapImageRep: won't capture the "SlideCarrier" 138 | image, since it's rendered via the layer contents property. Work around 139 | that by drawing the image into the bitmap ourselves, using a bitmap 140 | graphics context. 141 | */ 142 | // Work around SlideCarrierView layer contents not being rendered to bitmap. 143 | let slideCarrierImage = NSImage(named: "SlideCarrier") 144 | NSGraphicsContext.saveGraphicsState() 145 | let oldContext = NSGraphicsContext.current 146 | NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmap) 147 | slideCarrierImage?.draw(in: itemBounds, from: NSZeroRect, operation: .sourceOver, fraction: 1.0) 148 | NSGraphicsContext.current = oldContext 149 | NSGraphicsContext.restoreGraphicsState() 150 | 151 | /* 152 | Invoke -cacheDisplayInRect:toBitmapImageRep: to render the rest of the 153 | itemRootView subtree into the bitmap. 154 | */ 155 | itemRootView.cacheDisplay(in: itemBounds, to: bitmap) 156 | let image = NSImage(size: bitmap.size) 157 | image.addRepresentation(bitmap) 158 | 159 | let component = NSDraggingImageComponent(key: .icon) 160 | component.frame = itemBounds 161 | component.contents = image 162 | 163 | return [component] 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideBorderView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideBorderView" class declaration. 7 | */ 8 | 9 | #import 10 | 11 | // Added as a subview of a AAPLSlideCarrierView, when we want to frame the slide's shape with a stroked outline to indicate selection or highlighting. 12 | @interface AAPLSlideBorderView : NSView 13 | { 14 | NSColor *borderColor; 15 | } 16 | 17 | @property(copy) NSColor *borderColor; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideBorderView.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideBorderView" class implementation. 7 | */ 8 | 9 | #import "AAPLSlideBorderView.h" 10 | #import "AAPLSlideCarrierView.h" 11 | 12 | @implementation AAPLSlideBorderView 13 | 14 | #pragma mark Property Accessors 15 | 16 | - (NSColor *)borderColor { 17 | return borderColor; 18 | } 19 | 20 | - (void)setBorderColor:(NSColor *)newBorderColor { 21 | if (borderColor != newBorderColor) { 22 | borderColor = [newBorderColor copy]; 23 | [self setNeedsDisplay:YES]; 24 | } 25 | } 26 | 27 | #pragma mark Visual State 28 | 29 | // A AAPLSlideCarrierView wants to receive -updateLayer so it can set its backing layer's contents property, instead of being sent -drawRect: to draw its content procedurally. 30 | - (BOOL)wantsUpdateLayer { 31 | return YES; 32 | } 33 | 34 | - (void)updateLayer { 35 | CALayer *layer = self.layer; 36 | layer.borderColor = borderColor.CGColor; 37 | layer.borderWidth = (borderColor ? SLIDE_BORDER_WIDTH : 0.0); 38 | layer.cornerRadius = SLIDE_CORNER_RADIUS; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideBorderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLSlideBorderView.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "SlideBorderView" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | // Added as a subview of a AAPLSlideCarrierView, when we want to frame the slide's shape with a stroked outline to indicate selection or highlighting. 19 | @objc(AAPLSlideBorderView) 20 | class AAPLSlideBorderView: NSView { 21 | private var _borderColor: NSColor? 22 | 23 | //MARK: Property Accessors 24 | 25 | var borderColor: NSColor? { 26 | get { 27 | return _borderColor 28 | } 29 | 30 | set(newBorderColor) { 31 | if _borderColor != newBorderColor { 32 | _borderColor = newBorderColor?.copy() as! NSColor? 33 | self.needsDisplay = true 34 | } 35 | } 36 | } 37 | 38 | //MARK: Visual State 39 | 40 | // A AAPLSlideCarrierView wants to receive -updateLayer so it can set its backing layer's contents property, instead of being sent -drawRect: to draw its content procedurally. 41 | override var wantsUpdateLayer: Bool { 42 | return true 43 | } 44 | 45 | override func updateLayer() { 46 | if let layer = self.layer { 47 | layer.borderColor = borderColor?.cgColor 48 | layer.borderWidth = (borderColor != nil ? SLIDE_BORDER_WIDTH : 0.0) 49 | layer.cornerRadius = SLIDE_CORNER_RADIUS 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideCarrierView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideCarrierView" class declaration. 7 | */ 8 | 9 | #import 10 | 11 | #define SLIDE_WIDTH 140.0 // width of the SlideCarrier image (which includes shadow margins) in points, and thus the width that we give to a Slide's root view 12 | #define SLIDE_HEIGHT 140.0 // height of the SlideCarrier image (which includes shadow margins) in points, and thus the height that we give to a Slide's root view 13 | 14 | #define SLIDE_SHADOW_MARGIN 10.0 // margin on each side between the actual slide shape edge and the edge of the SlideCarrier image 15 | #define SLIDE_CORNER_RADIUS 8.0 // corner radius of the slide shape in points 16 | #define SLIDE_BORDER_WIDTH 4.0 // thickness of border when shown, in points 17 | 18 | // A AAPLSlideCarrierView serves as the container view for each AAPLSlide item. It displays a "SlideCarrier" slide shape image with built-in shadow, customizes hit-testing to account for the slide shape's rounded corners, and implements visual indication of item selection and highlighting state. 19 | @interface AAPLSlideCarrierView : NSView 20 | { 21 | NSCollectionViewItemHighlightState highlightState; 22 | BOOL selected; 23 | } 24 | 25 | // To leave the specifics of highlighted and selected appearance to the SlideCarrierView's implementation, we mirror NSCollectionViewItem's "highlightState" and "selected" properties to it. 26 | @property NSCollectionViewItemHighlightState highlightState; 27 | @property (getter=isSelected) BOOL selected; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideCarrierView.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideCarrierView" class implementation. 7 | */ 8 | 9 | #import "AAPLSlideCarrierView.h" 10 | #import "AAPLSlideBorderView.h" 11 | #import "AAPLSlideImageView.h" 12 | #import 13 | 14 | @implementation AAPLSlideCarrierView 15 | 16 | #pragma mark Animation 17 | 18 | // Override the default @"frameOrigin" animation for SlideCarrierViews, to use an "EaseInEaseOut" timing curve. 19 | + (id)defaultAnimationForKey:(NSString *)key { 20 | static CABasicAnimation *basicAnimation = nil; 21 | if ([key isEqual:@"frameOrigin"]) { 22 | if (basicAnimation == nil) { 23 | basicAnimation = [[CABasicAnimation alloc] init]; 24 | [basicAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; 25 | } 26 | return basicAnimation; 27 | } else { 28 | return [super defaultAnimationForKey:key]; 29 | } 30 | } 31 | 32 | 33 | #pragma mark Initializing 34 | 35 | - (nonnull instancetype)initWithFrame:(NSRect)frameRect { 36 | self = [super initWithFrame:frameRect]; 37 | if (self) { 38 | highlightState = NSCollectionViewItemHighlightNone; 39 | } 40 | return self; 41 | } 42 | 43 | 44 | #pragma mark Property Accessors 45 | 46 | - (NSCollectionViewItemHighlightState)highlightState { 47 | return highlightState; 48 | } 49 | 50 | - (void)setHighlightState:(NSCollectionViewItemHighlightState)newHighlightState { 51 | if (highlightState != newHighlightState) { 52 | highlightState = newHighlightState; 53 | 54 | // Cause our -updateLayer method to be invoked, so we can update our appearance to reflect the new state. 55 | [self setNeedsDisplay:YES]; 56 | } 57 | } 58 | 59 | - (BOOL)isSelected { 60 | return selected; 61 | } 62 | 63 | - (void)setSelected:(BOOL)flag { 64 | if (selected != flag) { 65 | selected = flag; 66 | 67 | // Cause our -updateLayer method to be invoked, so we can update our appearance to reflect the new state. 68 | [self setNeedsDisplay:YES]; 69 | } 70 | } 71 | 72 | 73 | #pragma mark Visual State 74 | 75 | // A AAPLSlideCarrierView wants to receive -updateLayer so it can set its backing layer's contents property, instead of being sent -drawRect: to draw its content procedurally. 76 | - (BOOL)wantsUpdateLayer { 77 | return YES; 78 | } 79 | 80 | // Returns the slide's AAPLSlideBorderView (if it currently has one). 81 | - (AAPLSlideBorderView *)borderView { 82 | for (NSView *subview in self.subviews) { 83 | if ([subview isKindOfClass:[AAPLSlideBorderView class]]) { 84 | return (AAPLSlideBorderView *)subview; 85 | } 86 | } 87 | return nil; 88 | } 89 | 90 | // Invoked from our -updateLayer override. Adds a AAPLSlideBorderView subview with appropriate properties (or removes an existing AAPLSlideBorderView), as appropriate to visually indicate the slide's "highlightState" and whether the slide is "selected". 91 | - (void)updateBorderView { 92 | NSColor *borderColor = nil; 93 | if (highlightState == NSCollectionViewItemHighlightForSelection) { 94 | 95 | // Item is a candidate to become selected: Show an orange border around it. 96 | borderColor = [NSColor orangeColor]; 97 | 98 | } else if (highlightState == NSCollectionViewItemHighlightAsDropTarget) { 99 | 100 | // Item is a candidate to receive dropped items: Show a red border around it. 101 | borderColor = [NSColor redColor]; 102 | 103 | } else if (selected && highlightState != NSCollectionViewItemHighlightForDeselection) { 104 | 105 | // Item is selected, and is not indicated for proposed deselection: Show an Aqua border around it. 106 | borderColor = [NSColor colorWithCalibratedRed:0.0 green:0.5 blue:1.0 alpha:1.0]; // Aqua 107 | 108 | } else { 109 | // Item is either not selected, or is selected but not highlighted for deselection: Sbhow no border around it. 110 | borderColor = nil; 111 | } 112 | 113 | // Add/update or remove a AAPLSlideBorderView subview, according to whether borderColor != nil. 114 | AAPLSlideBorderView *borderView = self.borderView; 115 | if (borderColor) { 116 | if (borderView == nil) { 117 | NSRect bounds = self.bounds; 118 | NSRect shapeBox = NSInsetRect(bounds, (SLIDE_SHADOW_MARGIN - 0.5 * SLIDE_BORDER_WIDTH), (SLIDE_SHADOW_MARGIN - 0.5 * SLIDE_BORDER_WIDTH)); 119 | borderView = [[AAPLSlideBorderView alloc] initWithFrame:shapeBox]; 120 | [self addSubview:borderView]; 121 | } 122 | borderView.borderColor = borderColor; 123 | } else { 124 | [borderView removeFromSuperview]; 125 | } 126 | } 127 | 128 | - (void)updateLayer { 129 | // Provide the SlideCarrierView's backing layer's contents directly, instead of via -drawRect:. 130 | self.layer.contents = [NSImage imageNamed:@"SlideCarrier"]; 131 | 132 | // Use this as an opportunity to update our AAPLSlideBorderView. 133 | [self updateBorderView]; 134 | } 135 | 136 | // Used by our -hitTest: method, below. Returns the slide's rounded-rectangle hit-testing shape, expressed as an NSBezierPath. 137 | - (NSBezierPath *)slideShape { 138 | NSRect bounds = self.bounds; 139 | NSRect shapeBox = NSInsetRect(bounds, SLIDE_SHADOW_MARGIN, SLIDE_SHADOW_MARGIN); 140 | return [NSBezierPath bezierPathWithRoundedRect:shapeBox xRadius:SLIDE_CORNER_RADIUS yRadius:SLIDE_CORNER_RADIUS]; 141 | } 142 | 143 | - (NSView *)hitTest:(NSPoint)aPoint { 144 | // Hit-test against the slide's rounded-rect shape. 145 | NSPoint pointInSelf = [self convertPoint:aPoint fromView:self.superview]; 146 | NSRect bounds = self.bounds; 147 | if (!NSPointInRect(pointInSelf, bounds)) { 148 | return NO; 149 | } else if (![self.slideShape containsPoint:pointInSelf]) { 150 | return NO; 151 | } else { 152 | return [super hitTest:aPoint]; 153 | } 154 | } 155 | 156 | @end 157 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideCarrierView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLSlideCarrierView.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample‚Äôs licensing information 11 | 12 | Abstract: 13 | This is the "SlideCarrierView" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | let SLIDE_WIDTH: CGFloat = 140.0 // width of the SlideCarrier image (which includes shadow margins) in points, and thus the width that we give to a Slide's root view 19 | let SLIDE_HEIGHT: CGFloat = 140.0 // height of the SlideCarrier image (which includes shadow margins) in points, and thus the height that we give to a Slide's root view 20 | 21 | let SLIDE_SHADOW_MARGIN: CGFloat = 10.0 // margin on each side between the actual slide shape edge and the edge of the SlideCarrier image 22 | let SLIDE_CORNER_RADIUS: CGFloat = 8.0 // corner radius of the slide shape in points 23 | let SLIDE_BORDER_WIDTH: CGFloat = 4.0 // thickness of border when shown, in points 24 | 25 | // A AAPLSlideCarrierView serves as the container view for each AAPLSlide item. It displays a "SlideCarrier" slide shape image with built-in shadow, customizes hit-testing to account for the slide shape's rounded corners, and implements visual indication of item selection and highlighting state. 26 | @objc(AAPLSlideCarrierView) 27 | class AAPLSlideCarrierView: NSView { 28 | private var _highlightState: NSCollectionViewItem.HighlightState = .none 29 | private var _selected: Bool = false 30 | 31 | //MARK: Animation 32 | 33 | // Override the default @"frameOrigin" animation for SlideCarrierViews, to use an "EaseInEaseOut" timing curve. 34 | override class func defaultAnimation(forKey key: String) -> Any? { 35 | struct My { 36 | static var basicAnimation: CABasicAnimation? = nil 37 | } 38 | if key == "frameOrigin" { 39 | if My.basicAnimation == nil { 40 | My.basicAnimation = CABasicAnimation() 41 | My.basicAnimation!.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 42 | } 43 | return My.basicAnimation! 44 | } else { 45 | return super.defaultAnimation(forKey: key) 46 | } 47 | } 48 | 49 | 50 | //MARK: Initializing 51 | 52 | override init(frame frameRect: NSRect) { 53 | super.init(frame: frameRect) 54 | _highlightState = .none 55 | } 56 | 57 | required init?(coder: NSCoder) { 58 | fatalError("init(coder:) has not been implemented") 59 | } 60 | 61 | 62 | //MARK: Property Accessors 63 | 64 | // To leave the specifics of highlighted and selected appearance to the SlideCarrierView's implementation, we mirror NSCollectionViewItem's "highlightState" and "selected" properties to it. 65 | var highlightState: NSCollectionViewItem.HighlightState { 66 | get { 67 | return _highlightState 68 | } 69 | 70 | set(newHighlightState) { 71 | if _highlightState != newHighlightState { 72 | _highlightState = newHighlightState 73 | 74 | // Cause our -updateLayer method to be invoked, so we can update our appearance to reflect the new state. 75 | self.needsDisplay = true 76 | } 77 | } 78 | } 79 | 80 | var selected: Bool { 81 | get { 82 | return _selected 83 | } 84 | 85 | set(flag) { 86 | if _selected != flag { 87 | _selected = flag 88 | 89 | // Cause our -updateLayer method to be invoked, so we can update our appearance to reflect the new state. 90 | self.needsDisplay = true 91 | } 92 | } 93 | } 94 | 95 | 96 | //MARK: Visual State 97 | 98 | // A AAPLSlideCarrierView wants to receive -updateLayer so it can set its backing layer's contents property, instead of being sent -drawRect: to draw its content procedurally. 99 | override var wantsUpdateLayer: Bool { 100 | return true 101 | } 102 | 103 | // Returns the slide's AAPLSlideBorderView (if it currently has one). 104 | private var borderView: AAPLSlideBorderView? { 105 | 106 | for subview in self.subviews { 107 | if let borderView = subview as? AAPLSlideBorderView { 108 | return borderView 109 | } 110 | } 111 | return nil 112 | } 113 | 114 | // Invoked from our -updateLayer override. Adds a AAPLSlideBorderView subview with appropriate properties (or removes an existing AAPLSlideBorderView), as appropriate to visually indicate the slide's "highlightState" and whether the slide is "selected". 115 | private func updateBorderView() { 116 | var borderColor: NSColor? = nil 117 | if highlightState == .forSelection { 118 | 119 | // Item is a candidate to become selected: Show an orange border around it. 120 | borderColor = NSColor.orange 121 | 122 | } else if highlightState == .asDropTarget { 123 | 124 | // Item is a candidate to receive dropped items: Show a red border around it. 125 | borderColor = NSColor.red 126 | 127 | } else if selected && highlightState != .forDeselection { 128 | 129 | // Item is selected, and is not indicated for proposed deselection: Show an Aqua border around it. 130 | borderColor = NSColor(calibratedRed: 0.0, green: 0.5, blue: 1.0, alpha: 1.0) // Aqua 131 | 132 | } else { 133 | // Item is either not selected, or is selected but not highlighted for deselection: Sbhow no border around it. 134 | borderColor = nil 135 | } 136 | 137 | // Add/update or remove a AAPLSlideBorderView subview, according to whether borderColor != nil. 138 | var borderView = self.borderView 139 | if let borderColor = borderColor { 140 | if borderView == nil { 141 | let bounds = self.bounds 142 | let shapeBox = NSInsetRect(bounds, (SLIDE_SHADOW_MARGIN - 0.5 * SLIDE_BORDER_WIDTH), (SLIDE_SHADOW_MARGIN - 0.5 * SLIDE_BORDER_WIDTH)) 143 | borderView = AAPLSlideBorderView(frame: shapeBox) 144 | self.addSubview(borderView!) 145 | } 146 | borderView!.borderColor = borderColor 147 | } else { 148 | borderView?.removeFromSuperview() 149 | } 150 | } 151 | 152 | override func updateLayer() { 153 | // Provide the SlideCarrierView's backing layer's contents directly, instead of via -drawRect:. 154 | self.layer?.contents = NSImage(named: "SlideCarrier") 155 | 156 | // Use this as an opportunity to update our AAPLSlideBorderView. 157 | self.updateBorderView() 158 | } 159 | 160 | // Used by our -hitTest: method, below. Returns the slide's rounded-rectangle hit-testing shape, expressed as an NSBezierPath. 161 | private var slideShape: NSBezierPath { 162 | let bounds = self.bounds 163 | let shapeBox = NSInsetRect(bounds, SLIDE_SHADOW_MARGIN, SLIDE_SHADOW_MARGIN) 164 | return NSBezierPath(roundedRect: shapeBox, xRadius: SLIDE_CORNER_RADIUS, yRadius: SLIDE_CORNER_RADIUS) 165 | } 166 | 167 | override func hitTest(_ aPoint: NSPoint) -> NSView? { 168 | // Hit-test against the slide's rounded-rect shape. 169 | let pointInSelf = self.convert(aPoint, from: self.superview) 170 | let bounds = self.bounds 171 | if !NSPointInRect(pointInSelf, bounds) { 172 | return nil 173 | } else if !self.slideShape.contains(pointInSelf) { 174 | return nil 175 | } else { 176 | return super.hitTest(aPoint) 177 | } 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideImageView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideImageView" class declaration. 7 | */ 8 | 9 | #import 10 | 11 | // A AAPLSlideImageView is a slightly customized NSImageView, that composites semitransparent bands over areas the slide's image doesn't cover, for a more slide-like appearance. 12 | @interface AAPLSlideImageView : NSImageView 13 | @end 14 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideImageView.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideCarrierView" class implementation. 7 | */ 8 | 9 | #import "AAPLSlideImageView.h" 10 | 11 | @implementation AAPLSlideImageView 12 | 13 | // Fill in semitransparent gray bands in any areas that the image doesn't cover, to give a more slide-like appearance. 14 | - (void)drawRect:(NSRect)rect { 15 | NSImage *image = [self image]; 16 | if (image != nil && [self imageScaling] == NSImageScaleProportionallyUpOrDown) { 17 | NSSize imageSize = [image size]; 18 | NSSize viewSize = [self bounds].size; 19 | if (imageSize.height > 0.0 && viewSize.height > 0.0) { 20 | CGFloat imageAspectRatio = imageSize.width / imageSize.height; 21 | CGFloat viewAspectRatio = viewSize.width / viewSize.height; 22 | [[NSColor colorWithCalibratedWhite:0.0 alpha:0.2] set]; 23 | if (imageAspectRatio > viewAspectRatio) { 24 | // Fill in bands at top and bottom. 25 | CGFloat thumbnailHeight = viewSize.width / imageAspectRatio; 26 | CGFloat bandHeight = 0.5 * (viewSize.height - thumbnailHeight); 27 | NSRectFillUsingOperation(NSMakeRect(0, 0, viewSize.width, bandHeight), NSCompositeSourceOver); 28 | NSRectFillUsingOperation(NSMakeRect(0, viewSize.height - bandHeight, viewSize.width, bandHeight), NSCompositeSourceOver); 29 | } else if (imageAspectRatio < viewAspectRatio) { 30 | // Fill in bands at left and right. 31 | CGFloat thumbnailWidth = viewSize.height * imageAspectRatio; 32 | CGFloat bandWidth = 0.5 * (viewSize.width - thumbnailWidth); 33 | NSRectFillUsingOperation(NSMakeRect(0, 0, bandWidth, viewSize.height), NSCompositeSourceOver); 34 | NSRectFillUsingOperation(NSMakeRect(viewSize.width - bandWidth, 0, bandWidth, viewSize.height), NSCompositeSourceOver); 35 | } 36 | } 37 | } 38 | 39 | // Now let NSImageView do its drawing. 40 | [super drawRect:rect]; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLSlideImageView.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample‚Äôs licensing information 11 | 12 | Abstract: 13 | This is the "SlideImageView" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | // A AAPLSlideImageView is a slightly customized NSImageView, that composites semitransparent bands over areas the slide's image doesn't cover, for a more slide-like appearance. 19 | @objc(AAPLSlideImageView) 20 | class AAPLSlideImageView: NSImageView { 21 | 22 | // Fill in semitransparent gray bands in any areas that the image doesn't cover, to give a more slide-like appearance. 23 | override func draw(_ rect: NSRect) { 24 | if let image = self.image , self.imageScaling == .scaleProportionallyDown { 25 | let imageSize = image.size 26 | let viewSize = self.bounds.size 27 | if imageSize.height > 0.0 && viewSize.height > 0.0 { 28 | let imageAspectRatio = imageSize.width / imageSize.height 29 | let viewAspectRatio = viewSize.width / viewSize.height 30 | NSColor(calibratedWhite: 0.0, alpha: 0.2).set() 31 | if imageAspectRatio > viewAspectRatio { 32 | // Fill in bands at top and bottom. 33 | let thumbnailHeight = viewSize.width / imageAspectRatio 34 | let bandHeight = 0.5 * (viewSize.height - thumbnailHeight) 35 | NSMakeRect(0, 0, viewSize.width, bandHeight).fill(using: .sourceOver) 36 | NSMakeRect(0, viewSize.height - bandHeight, viewSize.width, bandHeight).fill(using: .sourceOver) 37 | } else if imageAspectRatio < viewAspectRatio { 38 | // Fill in bands at left and right. 39 | let thumbnailWidth = viewSize.height * imageAspectRatio 40 | let bandWidth = 0.5 * (viewSize.width - thumbnailWidth) 41 | NSMakeRect(0, 0, bandWidth, viewSize.height).fill(using: .sourceOver) 42 | NSMakeRect(viewSize.width - bandWidth, 0, bandWidth, viewSize.height).fill(using: .sourceOver) 43 | } 44 | } 45 | } 46 | 47 | // Now let NSImageView do its drawing. 48 | super.draw(rect) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideTableBackgroundView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideTableBackgroundView" class declaration. 7 | */ 8 | 9 | #import 10 | 11 | // A simple background view for our NSCollectionView, that draws a subtle radial gradient using NSGradient, or an NSImage scaled to cover the entire background. 12 | @interface AAPLSlideTableBackgroundView : NSView 13 | { 14 | NSGradient *gradient; 15 | NSImage *image; 16 | } 17 | @property(strong) NSImage *image; 18 | @end 19 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideTableBackgroundView.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideTableBackgroundView" class implementation. 7 | */ 8 | 9 | #import "AAPLSlideTableBackgroundView.h" 10 | 11 | @implementation AAPLSlideTableBackgroundView 12 | 13 | - (instancetype)initWithFrame:(NSRect)frameRect { 14 | self = [super initWithFrame:frameRect]; 15 | if (self) { 16 | NSColor *centerColor = [NSColor colorWithCalibratedRed:0.94 green:0.99 blue:0.98 alpha:1.0]; 17 | NSColor *outerColor = [NSColor colorWithCalibratedRed:0.91 green:1.0 blue:0.98 alpha:1.0]; 18 | gradient = [[NSGradient alloc] initWithStartingColor:centerColor endingColor:outerColor]; 19 | } 20 | return self; 21 | } 22 | 23 | - (BOOL)isOpaque { 24 | return YES; 25 | } 26 | 27 | - (NSImage *)image { 28 | return image; 29 | } 30 | 31 | - (void)setImage:(NSImage *)newImage { 32 | if (image != newImage) { 33 | image = newImage; 34 | [self setNeedsDisplay:YES]; 35 | } 36 | } 37 | 38 | - (void)drawRect:(NSRect)dirtyRect { 39 | if (image) { 40 | // Draw an image, scaled proportionally to fill the view's entire bounds. 41 | NSSize imageSize = image.size; 42 | NSRect bounds = self.bounds; 43 | CGFloat scaleToFillWidth = bounds.size.width / imageSize.width; 44 | CGFloat scaleToFillHeight = bounds.size.height / imageSize.height; 45 | 46 | // Choose the greater of the scale factor required to fill the view's width, and the scale factor required to fill the view's height, and compute the destination rect accordingly. 47 | NSRect destRect; 48 | if (scaleToFillWidth > scaleToFillHeight) { 49 | destRect = NSMakeRect(bounds.origin.x, NSMidY(bounds) - 0.5 * scaleToFillWidth * imageSize.height, bounds.size.width, scaleToFillWidth * imageSize.height); 50 | } else { 51 | destRect = NSMakeRect(NSMidX(bounds) - 0.5 * scaleToFillHeight * imageSize.width, bounds.origin.y, scaleToFillHeight * imageSize.width, bounds.size.height); 52 | } 53 | [image drawInRect:destRect fromRect:NSMakeRect(0, 0, imageSize.width, imageSize.height) operation:NSCompositeSourceOver fraction:1.0]; 54 | } else { 55 | // Draw a slight radial gradient. 56 | [gradient drawInRect:self.bounds relativeCenterPosition:NSZeroPoint]; 57 | } 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/AAPLSlideTableBackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLSlideTableBackgroundView.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "SlideTableBackgroundView" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | // A simple background view for our NSCollectionView, that draws a subtle radial gradient using NSGradient, or an NSImage scaled to cover the entire background. 19 | @objc(AAPLSlideTableBackgroundView) 20 | class AAPLSlideTableBackgroundView: NSView { 21 | private var gradient: NSGradient 22 | private var _image: NSImage? 23 | 24 | override init(frame frameRect: NSRect) { 25 | let centerColor = NSColor(calibratedRed: 0.94, green: 0.99, blue: 0.98, alpha: 1.0) 26 | let outerColor = NSColor(calibratedRed: 0.91, green: 1.0, blue: 0.98, alpha: 1.0) 27 | gradient = NSGradient(starting: centerColor, ending: outerColor)! 28 | super.init(frame: frameRect) 29 | } 30 | 31 | required init?(coder: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | 35 | override var isOpaque: Bool { 36 | return true 37 | } 38 | 39 | var image: NSImage? { 40 | get { 41 | return _image 42 | } 43 | 44 | set(newImage) { 45 | if _image !== newImage { 46 | _image = newImage 47 | self.needsDisplay = true 48 | } 49 | } 50 | } 51 | 52 | override func draw(_ dirtyRect: NSRect) { 53 | if let image = image { 54 | // Draw an image, scaled proportionally to fill the view's entire bounds. 55 | let imageSize = image.size 56 | let bounds = self.bounds 57 | let scaleToFillWidth = bounds.size.width / imageSize.width 58 | let scaleToFillHeight = bounds.size.height / imageSize.height 59 | 60 | // Choose the greater of the scale factor required to fill the view's width, and the scale factor required to fill the view's height, and compute the destination rect accordingly. 61 | var destRect: NSRect 62 | if scaleToFillWidth > scaleToFillHeight { 63 | destRect = NSMakeRect(bounds.origin.x, NSMidY(bounds) - 0.5 * scaleToFillWidth * imageSize.height, bounds.size.width, scaleToFillWidth * imageSize.height) 64 | } else { 65 | destRect = NSMakeRect(NSMidX(bounds) - 0.5 * scaleToFillHeight * imageSize.width, bounds.origin.y, scaleToFillHeight * imageSize.width, bounds.size.height) 66 | } 67 | image.draw(in: destRect, from: NSMakeRect(0, 0, imageSize.width, imageSize.height), operation: .sourceOver, fraction: 1.0) 68 | } else { 69 | // Draw a slight radial gradient. 70 | gradient.draw(in: self.bounds, relativeCenterPosition: NSZeroPoint) 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLCircularLayout.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "CircularLayout" class declaration. 7 | */ 8 | 9 | #import "AAPLSlideLayout.h" 10 | 11 | // Positions items in a circle, within the available area. 12 | @interface AAPLCircularLayout : AAPLSlideLayout 13 | { 14 | NSPoint circleCenter; 15 | CGFloat circleRadius; 16 | } 17 | @end 18 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLCircularLayout.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "CircularLayout" class implementation. 7 | */ 8 | 9 | #import "AAPLCircularLayout.h" 10 | 11 | @implementation AAPLCircularLayout 12 | 13 | - (void)prepareLayout { 14 | [super prepareLayout]; 15 | 16 | CGFloat halfItemWidth = 0.5 * itemSize.width; 17 | CGFloat halfItemHeight = 0.5 * itemSize.height; 18 | CGFloat radiusInset = sqrt(halfItemWidth * halfItemWidth + halfItemHeight * halfItemHeight); 19 | circleCenter = NSMakePoint(NSMidX(box), NSMidY(box)); 20 | circleRadius = MIN(box.size.width, box.size.height) * 0.5 - radiusInset; 21 | } 22 | 23 | - (NSCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { 24 | NSInteger count = [[self collectionView] numberOfItemsInSection:0]; 25 | if (count == 0) { 26 | return nil; 27 | } 28 | 29 | NSUInteger itemIndex = [indexPath item]; 30 | CGFloat angleInRadians = ((CGFloat)itemIndex / (CGFloat)count) * (2.0 * M_PI); 31 | NSPoint subviewCenter; 32 | subviewCenter.x = circleCenter.x + circleRadius * cos(angleInRadians); 33 | subviewCenter.y = circleCenter.y + circleRadius * sin(angleInRadians); 34 | NSRect itemFrame = NSMakeRect(subviewCenter.x - 0.5 * itemSize.width, subviewCenter.y - 0.5 * itemSize.height, itemSize.width, itemSize.height); 35 | 36 | NSCollectionViewLayoutAttributes *attributes = [[[self class] layoutAttributesClass] layoutAttributesForItemWithIndexPath:indexPath]; 37 | [attributes setFrame:NSRectToCGRect(itemFrame)]; 38 | [attributes setZIndex:itemIndex]; 39 | return attributes; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLCircularLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLCircularLayout.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "CircularLayout" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | // Positions items in a circle, within the available area. 19 | @objc(AAPLCircularLayout) 20 | class AAPLCircularLayout: AAPLSlideLayout { 21 | private var circleCenter: NSPoint = NSPoint() 22 | private var circleRadius: CGFloat = 0.0 23 | 24 | override func prepare() { 25 | super.prepare() 26 | 27 | let halfItemWidth = 0.5 * itemSize.width 28 | let halfItemHeight = 0.5 * itemSize.height 29 | let radiusInset = sqrt(halfItemWidth * halfItemWidth + halfItemHeight * halfItemHeight) 30 | circleCenter = NSMakePoint(NSMidX(box), NSMidY(box)) 31 | circleRadius = min(box.size.width, box.size.height) * 0.5 - radiusInset 32 | } 33 | 34 | override func layoutAttributesForItem(at indexPath: IndexPath) -> NSCollectionViewLayoutAttributes? { 35 | guard let count = self.collectionView?.numberOfItems(inSection: 0) , count != 0 else { 36 | return nil 37 | } 38 | 39 | let itemIndex = indexPath.item 40 | let angleInRadians = (CGFloat(itemIndex) / CGFloat(count)) * (2.0 * .pi) 41 | var subviewCenter: NSPoint = NSPoint() 42 | subviewCenter.x = circleCenter.x + circleRadius * cos(angleInRadians) 43 | subviewCenter.y = circleCenter.y + circleRadius * sin(angleInRadians) 44 | let itemFrame = NSMakeRect(subviewCenter.x - 0.5 * itemSize.width, subviewCenter.y - 0.5 * itemSize.height, itemSize.width, itemSize.height) 45 | 46 | let attributes = (type(of: self).layoutAttributesClass as! NSCollectionViewLayoutAttributes.Type).init(forItemWith: indexPath) 47 | attributes.frame = NSRectToCGRect(itemFrame) 48 | attributes.zIndex = itemIndex 49 | return attributes 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLLoopLayout.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "LoopLayout" class declaration. 7 | */ 8 | 9 | #import "AAPLSlideLayout.h" 10 | 11 | // Positions items in an "infinity"-shaped loop, within the available area. 12 | @interface AAPLLoopLayout : AAPLSlideLayout 13 | { 14 | NSPoint loopCenter; 15 | NSSize loopSize; 16 | } 17 | @end 18 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLLoopLayout.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "LoopLayout" class implementation. 7 | */ 8 | 9 | #import "AAPLLoopLayout.h" 10 | 11 | @implementation AAPLLoopLayout 12 | 13 | - (void)prepareLayout { 14 | [super prepareLayout]; 15 | 16 | CGFloat halfItemWidth = 0.5 * itemSize.width; 17 | CGFloat halfItemHeight = 0.5 * itemSize.height; 18 | CGFloat radiusInset = sqrt(halfItemWidth * halfItemWidth + halfItemHeight * halfItemHeight); 19 | loopCenter = NSMakePoint(NSMidX(box), NSMidY(box)); 20 | loopSize = NSMakeSize(0.5 * (box.size.width - 2.0 * radiusInset), 0.5 * (box.size.height - 2.0 * radiusInset)); 21 | } 22 | 23 | - (NSCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { 24 | 25 | NSInteger count = [[self collectionView] numberOfItemsInSection:0]; 26 | if (count == 0) { 27 | return nil; 28 | } 29 | 30 | NSUInteger itemIndex = [indexPath item]; 31 | CGFloat angleInRadians = ((CGFloat)itemIndex / (CGFloat)count) * (2.0 * M_PI); 32 | NSPoint subviewCenter; 33 | subviewCenter.x = loopCenter.x + loopSize.width * cos(angleInRadians); 34 | subviewCenter.y = loopCenter.y + loopSize.height * sin(2.0 * angleInRadians); 35 | NSRect itemFrame = NSMakeRect(subviewCenter.x - 0.5 * itemSize.width, subviewCenter.y - 0.5 * itemSize.height, itemSize.width, itemSize.height); 36 | 37 | NSCollectionViewLayoutAttributes *attributes = [[[self class] layoutAttributesClass] layoutAttributesForItemWithIndexPath:indexPath]; 38 | [attributes setFrame:NSRectToCGRect(itemFrame)]; 39 | [attributes setZIndex:[indexPath item]]; 40 | return attributes; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLLoopLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLLoopLayout.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "LoopLayout" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | // Positions items in an "infinity"-shaped loop, within the available area. 19 | @objc(AAPLLoopLayout) 20 | class AAPLLoopLayout: AAPLSlideLayout { 21 | private var loopCenter: NSPoint = NSPoint() 22 | private var loopSize: NSSize = NSSize() 23 | 24 | override func prepare() { 25 | super.prepare() 26 | 27 | let halfItemWidth = 0.5 * itemSize.width 28 | let halfItemHeight = 0.5 * itemSize.height 29 | let radiusInset = sqrt(halfItemWidth * halfItemWidth + halfItemHeight * halfItemHeight) 30 | loopCenter = NSMakePoint(NSMidX(box), NSMidY(box)) 31 | loopSize = NSMakeSize(0.5 * (box.size.width - 2.0 * radiusInset), 0.5 * (box.size.height - 2.0 * radiusInset)) 32 | } 33 | 34 | override func layoutAttributesForItem(at indexPath: IndexPath) -> NSCollectionViewLayoutAttributes? { 35 | 36 | guard let count = self.collectionView?.numberOfItems(inSection: 0) , count != 0 else { 37 | return nil 38 | } 39 | 40 | let itemIndex = indexPath.item 41 | let angleInRadians = (CGFloat(itemIndex) / CGFloat(count)) * (2.0 * .pi) 42 | var subviewCenter: NSPoint = NSPoint() 43 | subviewCenter.x = loopCenter.x + loopSize.width * cos(angleInRadians) 44 | subviewCenter.y = loopCenter.y + loopSize.height * sin(2.0 * angleInRadians) 45 | let itemFrame = NSMakeRect(subviewCenter.x - 0.5 * itemSize.width, subviewCenter.y - 0.5 * itemSize.height, itemSize.width, itemSize.height) 46 | 47 | let attributes = (type(of: self).layoutAttributesClass as! NSCollectionViewLayoutAttributes.Type).init(forItemWith: indexPath) 48 | attributes.frame = NSRectToCGRect(itemFrame) 49 | attributes.zIndex = indexPath.item 50 | return attributes 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLScatterLayout.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "ScatterLayout" class declaration. 7 | */ 8 | 9 | #import "AAPLSlideLayout.h" 10 | 11 | // Positions items randomly, within the available area. 12 | @interface AAPLScatterLayout : AAPLSlideLayout 13 | { 14 | NSMutableDictionary *cachedItemFrames; 15 | } 16 | @end 17 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLScatterLayout.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "ScatterLayout" class implementation. 7 | */ 8 | 9 | #import "AAPLScatterLayout.h" 10 | 11 | @implementation AAPLScatterLayout 12 | 13 | - (instancetype)init { 14 | self = [super init]; 15 | if (self) { 16 | cachedItemFrames = [[NSMutableDictionary alloc] init]; 17 | } 18 | return self; 19 | } 20 | 21 | - (NSCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { 22 | NSValue *frameValue = [cachedItemFrames objectForKey:indexPath]; 23 | if (frameValue == nil) { 24 | NSPoint p; 25 | p.x = box.origin.x + drand48() * (box.size.width - itemSize.width); 26 | p.y = box.origin.y + drand48() * (box.size.height - itemSize.height); 27 | frameValue = [NSValue valueWithRect:NSMakeRect(p.x, p.y, itemSize.width, itemSize.height)]; 28 | [cachedItemFrames setObject:frameValue forKey:indexPath]; 29 | } 30 | 31 | NSCollectionViewLayoutAttributes *attributes = [[[self class] layoutAttributesClass] layoutAttributesForItemWithIndexPath:indexPath]; 32 | [attributes setFrame:[frameValue rectValue]]; 33 | [attributes setZIndex:[indexPath item]]; 34 | return attributes; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLScatterLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLScatterLayout.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "ScatterLayout" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | // Positions items randomly, within the available area. 19 | @objc(AAPLScatterLayout) 20 | class AAPLScatterLayout: AAPLSlideLayout { 21 | private var cachedItemFrames: [IndexPath: NSRect] = [:] 22 | 23 | override func layoutAttributesForItem(at indexPath: IndexPath) -> NSCollectionViewLayoutAttributes? { 24 | var frameValue = cachedItemFrames[indexPath] 25 | if frameValue == nil { 26 | var p: NSPoint = NSPoint() 27 | p.x = box.origin.x + CGFloat(drand48()) * (box.size.width - itemSize.width) 28 | p.y = box.origin.y + CGFloat(drand48()) * (box.size.height - itemSize.height) 29 | frameValue = NSMakeRect(p.x, p.y, itemSize.width, itemSize.height) 30 | cachedItemFrames[indexPath] = frameValue! 31 | } 32 | 33 | let attributes = (type(of: self).layoutAttributesClass as! NSCollectionViewLayoutAttributes.Type).init(forItemWith: indexPath) 34 | attributes.frame = frameValue! 35 | attributes.zIndex = indexPath.item 36 | return attributes 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLSlideLayout.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideLayout" class declaration. 7 | */ 8 | 9 | #import 10 | 11 | #define X_PADDING 10.0 12 | #define Y_PADDING 10.0 13 | 14 | // The base class for our custom slide layouts. It provides a foundation for layouts that show all of a CollectionView's items within the CollectionView's visibleRect (so that no scrolling is required). 15 | @interface AAPLSlideLayout : NSCollectionViewLayout 16 | { 17 | NSRect box; 18 | NSSize itemSize; 19 | } 20 | @end 21 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLSlideLayout.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "SlideLayout" class implementation. 7 | */ 8 | 9 | #import "AAPLSlideLayout.h" 10 | #import "AAPLSlideCarrierView.h" // for SLIDE_WIDTH, SLIDE_HEGIHT 11 | 12 | @implementation AAPLSlideLayout 13 | 14 | - (instancetype)init { 15 | self = [super init]; 16 | if (self) { 17 | itemSize = NSMakeSize(SLIDE_WIDTH, SLIDE_HEIGHT); 18 | } 19 | return self; 20 | } 21 | 22 | - (NSSize)collectionViewContentSize { 23 | NSRect clipBounds = [[[self collectionView] superview] bounds]; 24 | return clipBounds.size; // Lay our slides out within the available area. 25 | } 26 | 27 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(NSRect)newBounds { 28 | return YES; // Our custom SlideLayouts show all items within the CollectionView's visible rect, and must recompute their layouts for a good fit when that rect changes. 29 | } 30 | 31 | - (void)prepareLayout { 32 | [super prepareLayout]; 33 | 34 | // Inset by (X_PADDING,Y_PADDING) to precompute the box we need to fix the slides in. 35 | CGSize collectionViewContentSize = [self collectionViewContentSize]; 36 | box = NSInsetRect(NSMakeRect(0, 0, collectionViewContentSize.width, collectionViewContentSize.height), X_PADDING, Y_PADDING); 37 | } 38 | 39 | // A layout derived from this base class always displays all items, within the visible rectangle. So we can implement -layoutAttributesForElementsInRect: quite simply, by enumerating all item index paths and obtaining the -layoutAttributesForItemAtIndexPath: for each. Our subclasses then just have to implement -layoutAttributesForItemAtIndexPath:. 40 | - (NSArray *)layoutAttributesForElementsInRect:(NSRect)rect { 41 | NSInteger itemCount = [[self collectionView] numberOfItemsInSection:0]; 42 | NSMutableArray *layoutAttributesArray = [NSMutableArray arrayWithCapacity:itemCount]; 43 | for (NSInteger index = 0; index < itemCount; index++) { 44 | NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; 45 | NSCollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForItemAtIndexPath:indexPath]; 46 | if (layoutAttributes) { 47 | [layoutAttributesArray addObject:layoutAttributes]; 48 | } 49 | } 50 | return layoutAttributesArray; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLSlideLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLSlideLayout.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "SlideLayout" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | let X_PADDING: CGFloat = 10.0 19 | let Y_PADDING: CGFloat = 10.0 20 | 21 | // The base class for our custom slide layouts. It provides a foundation for layouts that show all of a CollectionView's items within the CollectionView's visibleRect (so that no scrolling is required). 22 | @objc(AAPLSlideLayout) 23 | class AAPLSlideLayout: NSCollectionViewLayout { 24 | var box: NSRect = NSRect() 25 | var itemSize: NSSize 26 | 27 | override init() { 28 | itemSize = NSMakeSize(SLIDE_WIDTH, SLIDE_HEIGHT) 29 | super.init() 30 | } 31 | 32 | required init?(coder aDecoder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | override var collectionViewContentSize: NSSize { 37 | let clipBounds = self.collectionView?.superview?.bounds ?? NSRect() 38 | return clipBounds.size // Lay our slides out within the available area. 39 | } 40 | 41 | override func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool { 42 | return true // Our custom SlideLayouts show all items within the CollectionView's visible rect, and must recompute their layouts for a good fit when that rect changes. 43 | } 44 | 45 | override func prepare() { 46 | super.prepare() 47 | 48 | // Inset by (X_PADDING,Y_PADDING) to precompute the box we need to fix the slides in. 49 | let collectionViewContentSize = self.collectionViewContentSize 50 | box = NSInsetRect(NSMakeRect(0, 0, collectionViewContentSize.width, collectionViewContentSize.height), X_PADDING, Y_PADDING) 51 | } 52 | 53 | // A layout derived from this base class always displays all items, within the visible rectangle. So we can implement -layoutAttributesForElementsInRect: quite simply, by enumerating all item index paths and obtaining the -layoutAttributesForItemAtIndexPath: for each. Our subclasses then just have to implement -layoutAttributesForItemAtIndexPath:. 54 | override func layoutAttributesForElements(in rect: NSRect) -> [NSCollectionViewLayoutAttributes] { 55 | let itemCount = self.collectionView?.numberOfItems(inSection: 0) ?? 0 56 | var layoutAttributesArray: [NSCollectionViewLayoutAttributes] = [] 57 | layoutAttributesArray.reserveCapacity(itemCount) 58 | for index in 0.. 10 | 11 | // Flows items in rows. 12 | @interface AAPLWrappedLayout : NSCollectionViewFlowLayout 13 | @end 14 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLWrappedLayout.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the "WrappedLayout" class implementation. 7 | */ 8 | 9 | #import "AAPLWrappedLayout.h" 10 | #import "AAPLSlideLayout.h" // for X_PADDING, Y_PADDING 11 | #import "AAPLSlideCarrierView.h" // for SLIDE_WIDTH, SLIDE_HEGIHT 12 | 13 | @implementation AAPLWrappedLayout 14 | 15 | - (instancetype)init { 16 | self = [super init]; 17 | if (self) { 18 | [self setItemSize:NSMakeSize(SLIDE_WIDTH, SLIDE_HEIGHT)]; 19 | [self setMinimumInteritemSpacing:X_PADDING]; 20 | [self setMinimumLineSpacing:Y_PADDING]; 21 | [self setSectionInset:NSEdgeInsetsMake(Y_PADDING, X_PADDING, Y_PADDING, X_PADDING)]; 22 | } 23 | return self; 24 | } 25 | 26 | - (NSCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { 27 | NSCollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath]; 28 | [attributes setZIndex:[indexPath item]]; 29 | return attributes; 30 | } 31 | 32 | - (NSArray *)layoutAttributesForElementsInRect:(NSRect)rect { 33 | NSArray *layoutAttributesArray = [super layoutAttributesForElementsInRect:rect]; 34 | for (NSCollectionViewLayoutAttributes *attributes in layoutAttributesArray) { 35 | [attributes setZIndex:[[attributes indexPath] item]]; 36 | } 37 | return layoutAttributesArray; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /CocoaSlideCollection/View/Layouts/AAPLWrappedLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPLWrappedLayout.swift 3 | // CocoaSlideCollection 4 | // 5 | // Translated by OOPer in cooperation with shlab.jp, on 2015/12/26. 6 | // 7 | // 8 | /* 9 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 10 | See LICENSE.txt for this sample’s licensing information 11 | 12 | Abstract: 13 | This is the "WrappedLayout" class declaration. 14 | */ 15 | 16 | import Cocoa 17 | 18 | // Flows items in rows. 19 | @objc(AAPLWrappedLayout) 20 | class AAPLWrappedLayout: NSCollectionViewFlowLayout { 21 | 22 | override init() { 23 | super.init() 24 | self.itemSize = NSMakeSize(SLIDE_WIDTH, SLIDE_HEIGHT) 25 | self.minimumInteritemSpacing = X_PADDING 26 | self.minimumLineSpacing = Y_PADDING 27 | self.sectionInset = NSEdgeInsetsMake(Y_PADDING, X_PADDING, Y_PADDING, X_PADDING) 28 | } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | override func layoutAttributesForItem(at indexPath: IndexPath) -> NSCollectionViewLayoutAttributes? { 35 | let attributes = super.layoutAttributesForItem(at: indexPath) 36 | attributes?.zIndex = indexPath.item 37 | return attributes 38 | } 39 | 40 | override func layoutAttributesForElements(in rect: NSRect) -> [NSCollectionViewLayoutAttributes] { 41 | let layoutAttributesArray = super.layoutAttributesForElements(in: rect) 42 | for attributes in layoutAttributesArray { 43 | attributes.zIndex = attributes.indexPath?.item ?? 0 44 | } 45 | return layoutAttributesArray 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /CocoaSlideCollection/main.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the application's main entry point. 7 | */ 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Sample code project: CocoaSlideCollection: Using NSCollectionView on OS X 10.11 2 | Version: 1.0 3 | 4 | IMPORTANT: This Apple software is supplied to you by Apple 5 | Inc. ("Apple") in consideration of your agreement to the following 6 | terms, and your use, installation, modification or redistribution of 7 | this Apple software constitutes acceptance of these terms. If you do 8 | not agree with these terms, please do not use, install, modify or 9 | redistribute this Apple software. 10 | 11 | In consideration of your agreement to abide by the following terms, and 12 | subject to these terms, Apple grants you a personal, non-exclusive 13 | license, under Apple's copyrights in this original Apple software (the 14 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 15 | Software, with or without modifications, in source and/or binary forms; 16 | provided that if you redistribute the Apple Software in its entirety and 17 | without modifications, you must retain this notice and the following 18 | text and disclaimers in all such redistributions of the Apple Software. 19 | Neither the name, trademarks, service marks or logos of Apple Inc. may 20 | be used to endorse or promote products derived from the Apple Software 21 | without specific prior written permission from Apple. Except as 22 | expressly stated in this notice, no other rights or licenses, express or 23 | implied, are granted by Apple herein, including but not limited to any 24 | patent rights that may be infringed by your derivative works or by other 25 | works in which the Apple Software may be incorporated. 26 | 27 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 28 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 29 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 30 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 31 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 32 | 33 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 35 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 36 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 37 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 38 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 39 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 40 | POSSIBILITY OF SUCH DAMAGE. 41 | 42 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CocoaSlideCollection 2 | 3 | ## Version 4 | 5 | 1.0 6 | 7 | ## Requirements 8 | 9 | ### Build 10 | 11 | Xcode 7.0, OS X 10.11 12 | 13 | ### Runtime 14 | 15 | OS X 10.11 16 | 17 | ## About CocoaSlideCollection 18 | 19 | CocoaSlideCollection demonstrates how to use the new NSCollectionView API added in OS X 10.11. Using an NSCollectionView, it enables browsing folders of image files, with support for custom NSCollectionViewLayouts, item selection and highlighting, grouping by image file tag with header and footer views, and drag-and-drop. 20 | 21 | An AAPLBrowserWindowController manages a window and its NSCollectionView, and serves as the NSCollectionView's dataSource and delegate. The collection of images to display is represented by an AAPLImageCollection that owns AAPLImageFiles and associated AAPLTags. The AAPLImageCollection monitors its associated folder, and notifies the AAPLBrowserWindowController of the comings and goings of image files, so that the CollectionView can be updated accordingly. Each AAPLImageFile model object is represented in the NSCollectionView by an AAPLSlide and its associated view tree. 22 | 23 | CocoaSlideCollection implements and uses several custom NSCollectionViewLayout subclasses, that can arrange the slides in a variety of ways. 24 | 25 | Checking the "Group by Tag" checkbox demonstrates the ability to group NSCollectionView items into sections, and give each section a header and footer view (currently only supported in the "Wrapped"/Flow layout). 26 | 27 | See the Application Kit Release Notes and WWDC 2015 Session 225 - What's New in NSCollectionView for more information on using the new NSCollectionView APIs. 28 | 29 | Copyright (C) 2015 Apple Inc. All rights reserved. 30 | -------------------------------------------------------------------------------- /README2.md: -------------------------------------------------------------------------------- 1 | # CocoaSlideCollection: Using NSCollectionView on OS X 10.11 2 | 3 | Translated by OOPer in cooperation with shlab.jp, on 2015/12/27. 4 | 5 | Based on 6 | 7 | 2015-09-16. 8 | 9 | As this is a line-by-line translation from the original sample code, "redistribute the Apple Software in its entirety and without modifications" would apply. See LICENSE.txt . 10 | Some faults caused by my translation may exist. Not all features tested. 11 | You should not contact to Apple or SHLab(jp) about any faults caused by my translation. 12 | 13 | Some utility files are used to make line-by-line translation easier. They have other license terms. 14 | See license terms in each file. 15 | 16 | ## Requirements 17 | 18 | ### Build 19 | 20 | Xcode 10.2, OS X 10.14.4 SDK 21 | --------------------------------------------------------------------------------