├── .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 |
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 |
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 |
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 |
104 |
110 |
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 |
--------------------------------------------------------------------------------