Exhibition/ImageCollectionViewItem.swift
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Contains the `ImageCollectionViewItem` class, which is a collection view item that displays an `ImageFile`'s thumbnail. |
Also contains `DoubleActionImageView`, which is an image view subclass that has a double click action. |
*/ |
import Cocoa |
/** |
The KVO context used for all `ImageCollectionViewItem` instances. This provides |
a stable address to use as the context parameter for the KVO observation methods. |
*/ |
private var imageCollectionViewItemKVOContext = 0 |
/** |
`ImageCollectionViewItem` is a collection view item that displays `ImageFile`s. |
They display their selection and take into consideration the active state of |
their containing `collectionView`. Double clicking on the item displays a |
`StandaloneImageWindowController` with the represented `ImageFile`. |
*/ |
class ImageCollectionViewItem: NSCollectionViewItem { |
// MARK: Properties |
var imageFile: ImageFile? { |
didSet { |
guard isViewLoaded else { return } |
updateImageViewWithImageFile(imageFile) |
} |
} |
override var isSelected: Bool { |
didSet { |
updateAppearanceFromKeyState() |
} |
} |
override var highlightState: NSCollectionViewItemHighlightState { |
didSet { |
updateAppearanceFromKeyState() |
} |
} |
// MARK: Life Cycle |
override func viewDidLoad() { |
super.viewDidLoad() |
/* |
If the set image view is a `DoubleActionImageView`, set the `doubleAction` |
to be the double click handler. |
*/ |
if let imageView = imageView as? DoubleActionImageView { |
imageView.doubleAction = #selector(ImageCollectionViewItem.handleDoubleClickInImageView(_:)) |
imageView.target = self |
} |
updateImageViewWithImageFile(imageFile) |
view.layer?.cornerRadius = 2.0 |
/* |
Watch for when the containing `collectionView` changes in order to |
observe its `firstResponder`. Include the old and new `collectionView` |
in the change dictionary to be able to unobserve the old one. |
*/ |
addObserver(self, forKeyPath: "collectionView", options: [.initial, .old, .new], context: &imageCollectionViewItemKVOContext) |
} |
override func viewDidAppear() { |
super.viewDidAppear() |
// Observe the window for `keyWindow` state changes. |
if let window = view.window { |
NotificationCenter.default.addObserver(self, selector: #selector(ImageCollectionViewItem.windowDidChangeKeyState(_:)), name: NSNotification.Name.NSWindowDidBecomeKey, object: window) |
NotificationCenter.default.addObserver(self, selector: #selector(ImageCollectionViewItem.windowDidChangeKeyState(_:)), name: NSNotification.Name.NSWindowDidResignKey, object: window) |
} |
// Update the selection appearance now that the item is in a window |
updateAppearanceFromKeyState() |
} |
override func viewWillDisappear() { |
super.viewWillDisappear() |
// Stop observing the window for `keyWindow` state changes. |
if let window = view.window { |
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSWindowDidBecomeKey, object: window) |
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSWindowDidResignKey, object: window) |
} |
} |
deinit { |
// Stop observing for containing `collectionView` changes. |
self.removeObserver(self, forKeyPath: "collectionView", context: &imageCollectionViewItemKVOContext) |
NotificationCenter.default.removeObserver(self) |
} |
func updateImageViewWithImageFile(_ imageFile: ImageFile?) { |
if let imageFile = imageFile { |
/* |
Fetch the `ImageFile`'s image representation and update set it |
on the image view. |
*/ |
imageFile.fetchThumbnailWithCompletionHandler { thumbnail in |
/* |
Verify that the loaded image representation is from the currently |
set `ImageFile`. |
*/ |
if self.imageFile == imageFile { |
self.imageView?.image = thumbnail |
} |
} |
} |
else { |
imageView?.image = nil |
} |
} |
// MARK: Key Appearance Updating |
fileprivate func updateAppearanceFromKeyState() { |
/* |
If the containing collection view is the first responder in a window |
that is key, then its items have a key appearance. |
*/ |
let hasKeyAppearance = collectionView.isFirstResponder && (collectionView.window?.isKeyWindow ?? false) |
/* |
If the item has key appearance, it uses the user's set selection color, |
otherwise uses the standard inactive selection color. |
*/ |
let baseHighlightColor = hasKeyAppearance ? NSColor.alternateSelectedControlColor : NSColor.secondarySelectedControlColor |
let backgroundColor: CGColor |
switch highlightState { |
case .forSelection: |
backgroundColor = baseHighlightColor.withAlphaComponent(0.4).cgColor |
case .asDropTarget: |
backgroundColor = baseHighlightColor.withAlphaComponent(1.0).cgColor |
default: |
if isSelected { |
backgroundColor = baseHighlightColor.withAlphaComponent(0.8).cgColor |
} |
else { |
backgroundColor = NSColor.clear.cgColor |
} |
} |
view.layer?.backgroundColor = backgroundColor |
} |
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { |
guard context == &imageCollectionViewItemKVOContext else { |
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) |
return |
} |
if let newCollectionView = object as? NSCollectionView, newCollectionView == collectionView && keyPath == "isFirstResponder" { |
/* |
If the item's collection view changed first responder state, the |
selection appearance may have changed. |
*/ |
updateAppearanceFromKeyState() |
} |
else if let newImageCollectionViewItem = object as? NSCollectionViewItem, newImageCollectionViewItem == self && keyPath == "collectionView" { |
if let oldCollectionView = change?[NSKeyValueChangeKey.oldKey] as? NSCollectionView { |
// Stop observing the containing collection view for first responder changes. |
oldCollectionView.removeObserver(self, forKeyPath: "isFirstResponder", context: &imageCollectionViewItemKVOContext) |
} |
if let newCollectionView = change?[NSKeyValueChangeKey.newKey] as? NSCollectionView { |
// Observe the containing collection view for `firstResponder` state changes. |
newCollectionView.addObserver(self, forKeyPath: "isFirstResponder", options: .new, context: &imageCollectionViewItemKVOContext) |
} |
} |
} |
@objc fileprivate func windowDidChangeKeyState(_ notification: Notification) { |
// When the window changes key state, the selection appearance may have changed. |
updateAppearanceFromKeyState() |
} |
// MARK: IBActions |
@IBAction func handleDoubleClickInImageView(_ sender: AnyObject?) { |
// On double click, show a new standalone window for the set `image`. |
if let imageFile = imageFile { |
StandaloneImageWindowController.showStandaloneImage(imageFile) |
} |
} |
} |
/** |
An `NSImageView` subclass with a `doubleAction` that fires on double clicks. |
*/ |
class DoubleActionImageView: NSImageView { |
// MARK: Properties |
var doubleAction: Selector? |
override var mouseDownCanMoveWindow: Bool { |
return true |
} |
// MARK: Event Handling |
override func mouseDown(with event: NSEvent) { |
if let doubleAction = doubleAction , event.clickCount == 2 { |
NSApp.sendAction(doubleAction, to: target, from: self) |
} |
else { |
super.mouseDown(with: event) |
} |
} |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-28