将 NSToolbarItems 与 NSSplitView 列对齐
Align NSToolbarItems with NSSplitView columns
Finder 和 Notes 有一个特殊的行为,我正在寻求重现。 NSToolbar 中的“flexible space”似乎考虑了拆分视图的尺寸。例如,第一组按钮在侧边栏的左侧与右侧对齐。第二组图标与第一列的右侧对齐。当我加宽侧边栏时,工具栏项目会随之移动。
可以重现吗?
解决方案
根据@KenThomases提供的解决方案,我实现如下:
final class MainWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
window?.toolbar?.delegate = self
// Make sure that tracking is enabled when the toolbar is completed
DispatchQueue.main.async {
self.trackSplitViewForFirstFlexibleToolbarItem()
}
}
}
extension MainWindowController: NSToolbarDelegate {
func toolbarWillAddItem(_ notification: Notification) {
// Make sure that tracking is evaluated only after the item was added
DispatchQueue.main.async {
self.trackSplitViewForFirstFlexibleToolbarItem()
}
}
func toolbarDidRemoveItem(_ notification: Notification) {
trackSplitViewForFirstFlexibleToolbarItem()
}
/// - Warning: This is a private Apple method and may break in the future.
func toolbarDidReorderItem(_ notification: Notification) {
trackSplitViewForFirstFlexibleToolbarItem()
}
/// - Warning: This method uses private Apple methods that may break in the future.
fileprivate func trackSplitViewForFirstFlexibleToolbarItem() {
guard var toolbarItems = self.window?.toolbar?.items, let splitView = (contentViewController as? NSSplitViewController)?.splitView else {
return
}
// Add tracking to the first flexible space and remove it from the group
if let firstFlexibleToolbarItem = toolbarItems.first, firstFlexibleToolbarItem.itemIdentifier == NSToolbarFlexibleSpaceItemIdentifier {
_ = firstFlexibleToolbarItem.perform(Selector(("setTrackedSplitView:")), with: splitView)
toolbarItems.removeFirst()
}
// Remove tracking from other flexible spaces
for flexibleToolbarItem in toolbarItems.filter({ [=10=].itemIdentifier == NSToolbarFlexibleSpaceItemIdentifier }) {
_ = flexibleToolbarItem.perform(Selector(("setTrackedSplitView:")), with: nil)
}
}
}
您可以使用 Apple 私有方法执行此操作,但 App Store 不允许这样做。
NSToolbarItem
上有一个私有方法 -setTrackedSplitView:
。它以 NSSplitView*
作为参数。您需要在要跟踪拆分视图的 flexible-space 工具栏项上调用它,并将它传递给它应该跟踪的拆分视图。为了防止 Apple 删除该方法,您应该在尝试使用之前检查 NSToolbarItem
是否响应该方法。
由于用户可以对工具栏进行自定义和重新排序,所以一般需要枚举window的工具栏项。对于标识符为 NSToolbarFlexibleSpaceItemIdentifier
的第一个,您设置它应该跟踪的拆分视图。对于所有其他灵活的 space 项目,您清除(设置为 nil
)要跟踪的拆分视图。当首次设置 window 并在工具栏委托的 -toolbarWillAddItem:
和 -toolbarDidRemoveItem:
方法中再次设置时,您需要这样做。还有另一个未记录的委托方法,-toolbarDidReorderItem:
,我发现它对更新工具栏很有用。
使用 macOS 11 或更高版本时,您可以将 NSTrackingSeparatorToolbarItem
项插入工具栏,这会将您的工具栏分成几个部分,与 NSSplitView
对象的分隔线对齐。
此示例将新的分隔符项添加到工具栏,该工具栏已包含其余按钮,在 Interface Builder 或代码中配置。目标拆分视图涉及 3 个拆分视图的标准配置,包括侧边栏面板。
class WindowController: NSWindowController, NSToolbarDelegate {
let mainPanelSeparatorIdentifier = NSToolbarItem.Identifier(rawValue: "MainPanel")
override func windowDidLoad() {
super.windowDidLoad()
self.window?.toolbar?.delegate = self
// Calling the inserts async gives more time to bind with the split viewer, and prevents crashes
DispatchQueue.main.async {
// The .sidebarTrackingSeparator is a built-in tracking separator which always aligns with the sidebar splitview
self.window?.toolbar?.insertItem(withItemIdentifier: .sidebarTrackingSeparator, at: 0)
// Example of a custom mainPanelSeparatorIdentifier
// Index at '3' means that there are 3 toolbar items at the left side
// of this separator, including the first tracking separator
self.window?.toolbar?.insertItem(withItemIdentifier: mainPanelSeparatorIdentifier at: 3)
}
}
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
if let splitView = (self.contentViewController as? NSSplitViewController)?.splitView {
// You must implement this for custom separator identifiers, to connect the separator with a split view divider
if itemIdentifier == mainPanelSeparatorIdentifier {
return NSTrackingSeparatorToolbarItem(identifier: itemIdentifier, splitView: splitView, dividerIndex: 1)
}
}
return nil
}
}
如果您想添加一个额外的分隔符,例如为检查器面板添加一个额外的分隔符,只需将一个额外的工具栏项标识符插入工具栏,然后将一个额外的 NSTrackingSeparatorToolbarItem
分配给 [=14= 中的另一个分隔符]委托功能。
Finder 和 Notes 有一个特殊的行为,我正在寻求重现。 NSToolbar 中的“flexible space”似乎考虑了拆分视图的尺寸。例如,第一组按钮在侧边栏的左侧与右侧对齐。第二组图标与第一列的右侧对齐。当我加宽侧边栏时,工具栏项目会随之移动。
可以重现吗?
解决方案
根据@KenThomases提供的解决方案,我实现如下:
final class MainWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
window?.toolbar?.delegate = self
// Make sure that tracking is enabled when the toolbar is completed
DispatchQueue.main.async {
self.trackSplitViewForFirstFlexibleToolbarItem()
}
}
}
extension MainWindowController: NSToolbarDelegate {
func toolbarWillAddItem(_ notification: Notification) {
// Make sure that tracking is evaluated only after the item was added
DispatchQueue.main.async {
self.trackSplitViewForFirstFlexibleToolbarItem()
}
}
func toolbarDidRemoveItem(_ notification: Notification) {
trackSplitViewForFirstFlexibleToolbarItem()
}
/// - Warning: This is a private Apple method and may break in the future.
func toolbarDidReorderItem(_ notification: Notification) {
trackSplitViewForFirstFlexibleToolbarItem()
}
/// - Warning: This method uses private Apple methods that may break in the future.
fileprivate func trackSplitViewForFirstFlexibleToolbarItem() {
guard var toolbarItems = self.window?.toolbar?.items, let splitView = (contentViewController as? NSSplitViewController)?.splitView else {
return
}
// Add tracking to the first flexible space and remove it from the group
if let firstFlexibleToolbarItem = toolbarItems.first, firstFlexibleToolbarItem.itemIdentifier == NSToolbarFlexibleSpaceItemIdentifier {
_ = firstFlexibleToolbarItem.perform(Selector(("setTrackedSplitView:")), with: splitView)
toolbarItems.removeFirst()
}
// Remove tracking from other flexible spaces
for flexibleToolbarItem in toolbarItems.filter({ [=10=].itemIdentifier == NSToolbarFlexibleSpaceItemIdentifier }) {
_ = flexibleToolbarItem.perform(Selector(("setTrackedSplitView:")), with: nil)
}
}
}
您可以使用 Apple 私有方法执行此操作,但 App Store 不允许这样做。
NSToolbarItem
上有一个私有方法 -setTrackedSplitView:
。它以 NSSplitView*
作为参数。您需要在要跟踪拆分视图的 flexible-space 工具栏项上调用它,并将它传递给它应该跟踪的拆分视图。为了防止 Apple 删除该方法,您应该在尝试使用之前检查 NSToolbarItem
是否响应该方法。
由于用户可以对工具栏进行自定义和重新排序,所以一般需要枚举window的工具栏项。对于标识符为 NSToolbarFlexibleSpaceItemIdentifier
的第一个,您设置它应该跟踪的拆分视图。对于所有其他灵活的 space 项目,您清除(设置为 nil
)要跟踪的拆分视图。当首次设置 window 并在工具栏委托的 -toolbarWillAddItem:
和 -toolbarDidRemoveItem:
方法中再次设置时,您需要这样做。还有另一个未记录的委托方法,-toolbarDidReorderItem:
,我发现它对更新工具栏很有用。
使用 macOS 11 或更高版本时,您可以将 NSTrackingSeparatorToolbarItem
项插入工具栏,这会将您的工具栏分成几个部分,与 NSSplitView
对象的分隔线对齐。
此示例将新的分隔符项添加到工具栏,该工具栏已包含其余按钮,在 Interface Builder 或代码中配置。目标拆分视图涉及 3 个拆分视图的标准配置,包括侧边栏面板。
class WindowController: NSWindowController, NSToolbarDelegate {
let mainPanelSeparatorIdentifier = NSToolbarItem.Identifier(rawValue: "MainPanel")
override func windowDidLoad() {
super.windowDidLoad()
self.window?.toolbar?.delegate = self
// Calling the inserts async gives more time to bind with the split viewer, and prevents crashes
DispatchQueue.main.async {
// The .sidebarTrackingSeparator is a built-in tracking separator which always aligns with the sidebar splitview
self.window?.toolbar?.insertItem(withItemIdentifier: .sidebarTrackingSeparator, at: 0)
// Example of a custom mainPanelSeparatorIdentifier
// Index at '3' means that there are 3 toolbar items at the left side
// of this separator, including the first tracking separator
self.window?.toolbar?.insertItem(withItemIdentifier: mainPanelSeparatorIdentifier at: 3)
}
}
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
if let splitView = (self.contentViewController as? NSSplitViewController)?.splitView {
// You must implement this for custom separator identifiers, to connect the separator with a split view divider
if itemIdentifier == mainPanelSeparatorIdentifier {
return NSTrackingSeparatorToolbarItem(identifier: itemIdentifier, splitView: splitView, dividerIndex: 1)
}
}
return nil
}
}
如果您想添加一个额外的分隔符,例如为检查器面板添加一个额外的分隔符,只需将一个额外的工具栏项标识符插入工具栏,然后将一个额外的 NSTrackingSeparatorToolbarItem
分配给 [=14= 中的另一个分隔符]委托功能。