Exhibition/ImageToolsViewController.swift
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Contains the `ImageToolsViewController` class, which manages the UI of a stack of tools buttons. |
*/ |
import Cocoa |
/** |
`ImageToolsViewController` manages a list of tool controls. The controls can be |
detached from the tool stack once their contained view becomes too small and |
displayed in an overflow menu. |
*/ |
class ImageToolsViewController: NSViewController, NSStackViewDelegate { |
// MARK: Properties |
@IBOutlet var stackView: NSStackView! |
/** |
The button shown when the tool stack is too small to present all of its |
buttons. Clicking on the overflow button shows a menu with representations |
of the detached buttons. |
*/ |
lazy var overflowRevealButton: NSButton = { |
let overflowRevealButton = NSButton() |
overflowRevealButton.bezelStyle = .shadowlessSquare |
overflowRevealButton.translatesAutoresizingMaskIntoConstraints = false |
overflowRevealButton.attributedTitle = NSAttributedString(string: "...", attributes: [ |
NSFontAttributeName: NSFont.boldSystemFont(ofSize: 13.0) |
]) |
overflowRevealButton.alphaValue = 0.75 |
overflowRevealButton.target = self |
overflowRevealButton.action = #selector(ImageToolsViewController.popUpOverflowMenu(_:)) |
return overflowRevealButton |
}() |
// MARK: Life Cycle |
override func viewDidLoad() { |
super.viewDidLoad() |
stackView.detachesHiddenViews = true |
} |
// MARK: Overflow Menu |
/** |
Creates a menu for the detached views in the stack. The title, target, |
and action of the menu items match the associated buttons. If there were |
no detached items, returns nil. |
*/ |
var overflowMenu: NSMenu? { |
let detachedButtons = stackView.detachedViews.flatMap { $0 as? NSButton } |
if !detachedButtons.isEmpty { |
let menu = NSMenu(title: "Detached Items") |
/* |
Do not use auto-validation, the menu items will have the same |
enabled state as their represented button. |
*/ |
menu.autoenablesItems = false |
/* |
Add a menu item for each detached button, transferring the applicable |
button state to the menu item. |
*/ |
for button in detachedButtons { |
let menuItem = menu.addItem(withTitle: button.title, action: button.action, keyEquivalent: button.keyEquivalent) |
menuItem.target = button.target |
menuItem.isEnabled = button.isEnabled |
menuItem.representedObject = button |
} |
return menu |
} |
return nil |
} |
@objc fileprivate func popUpOverflowMenu(_ sender: AnyObject?) { |
// Show the overflow menu (if we one exists) underneath the overflow button. |
let popUpMenuLocation = NSPoint(x: overflowRevealButton.bounds.minX, y: overflowRevealButton.bounds.maxY) |
overflowMenu?.popUp(positioning: nil, at: popUpMenuLocation, in: overflowRevealButton) |
} |
// MARK: NSStackView Delegate |
func stackView(_ stackView: NSStackView, willDetach views: [NSView]) { |
/* |
If the stack view did not previously have any detached views, these |
are the first, so the overflow button needs to be added and constrained. |
*/ |
if stackView.detachedViews.isEmpty { |
/* |
Add the overflow button (not as an arranged subview), and constraints |
to position it on the trailing edge of the stack view. |
*/ |
stackView.addSubview(overflowRevealButton) |
overflowRevealButton.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true |
overflowRevealButton.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true |
overflowRevealButton.bottomAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true |
} |
} |
func stackView(_ stackView: NSStackView, didReattach views: [NSView]) { |
/* |
If the stack view no longer has any detached views, these were the last, |
so the overflow button needs to be removed. |
*/ |
if stackView.detachedViews.isEmpty { |
// Removing the overflow button will also remove constraints associated with it. |
overflowRevealButton.removeFromSuperview() |
} |
} |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-28