3

在 iOS 中,工具栏可以添加到任何视图中。然而,在 macOS 中,似乎只能向窗口添加工具栏。

我正在开发一个带有带有工具栏的拆分视图控制器的应用程序,但工具栏的项目仅对右视图控制器的上下文有意义

例如,假设我有某种文本编辑器,其中左窗格显示所有文档(如在 Notes 应用程序中),右窗格显示可以编辑的实际文本。格式按钮仅影响右窗格中的文本。因此,将工具栏放置在该右侧窗格中而不是将其拉伸到窗口的整个宽度上似乎非常直观。

带有拆分视图控制器的示例窗口

有没有办法做到这一点?
(或者是否有一个很好的用户体验原因说明这将是一个不好的做法?)

我注意到 Apple 如何在他们的 Notes 应用程序中解决这个问题:他们仍然使用全角工具栏,但将仅与右窗格相关的按钮项与该窗格的前沿对齐。

笔记应用截图

因此,如果无法在视图控制器中放置工具栏,如何将工具栏项与右视图控制器的前沿对齐,如上面的屏幕截图所示?


编辑:

根据TimTwoToes 的回答和Willeke在评论中链接的帖子,似乎可以使用自动布局来约束带有拆分视图的子视图的工具栏项。如果有固定的工具栏布局,此解决方案将起作用。但是,Apple 鼓励(有充分的理由)让用户自定义您的应用程序的工具栏。

因此,我无法将约束添加到工具栏中的固定项目。相反,一个可行的解决方案似乎是使用领先的灵活空间并相应地调整其大小。

4

2 回答 2

2

初始注释

事实证明这很棘手,因为需要考虑很多事情:

  • 自动布局似乎不适用于工具栏项目。(我读过一些帖子提到 Apple 已将此归类为错误。)

  • 通常,用户可以自定义应用程序的工具栏(添加和删除项目)。我们不应该剥夺用户的选择权。

因此,简单地使用拆分视图或布局指南约束特定工具栏项不是一种选择(因为该项目可能位于与预期不同的位置或根本不存在)。

经过数小时的“黑客攻击”,我终于找到了一种可靠的方法来实现不使用任何内部/未记录方法的所需行为。这是它的外观:

调整灵活视图大小的示例动画


如何

  1. 而不是标准NSToolbarFlexibleSpaceItem创建NSToolbarItem一个自定义视图。这将作为您灵活的、调整大小的空间。您可以在代码或 Interface Builder 中执行此操作:

    Interface Builder 中的工具栏自定义

  2. 为您的工具栏和灵活空间(在各自的内部NSWindowController)创建插座/属性:

    @IBOutlet weak var toolbar: NSToolbar!
    @IBOutlet weak var tabSpace: NSToolbarItem!
    
  3. 在同一个窗口控制器中创建一个方法来调整空间宽度:

    private func adjustTabSpaceWidth() {
        for item in toolbar.items {
            if item == tabSpace {
                guard
                    let origin = item.view?.frame.origin,
                    let originInWindowCoordinates = item.view?.convert(origin, to: nil),
                    let leftPane = splitViewController?.splitViewItems.first?.viewController.view
                    else {
                        return
                }
    
                let leftPaneWidth = leftPane.frame.size.width
                let tabWidth = max(leftPaneWidth - originInWindowCoordinates.x, MainWindowController.minTabSpaceWidth)
    
                item.set(width: tabWidth)
            }
        }
    

    }

  4. set(width:)在扩展中定义方法NSToolbarItem如下:

    private extension NSToolbarItem {
    
        func set(width: CGFloat) {
            minSize = .init(width: width, height: minSize.height)
            maxSize = .init(width: width, height: maxSize.height)
        }
    
    }
    
  5. 使您的窗口控制器符合NSSplitViewDelegate并将其分配给您的拆分视图的delegate属性。1NSSplitViewDelegate在您的窗口控制器中实现以下协议方法:

    override func splitViewDidResizeSubviews(_ notification: Notification) {
        adjustTabSpaceWidth()
    }
    

这将产生所需的调整大小行为。(用户仍然可以完全删除空间或重新定位它,但他总是可以将其添加回前面。)


1 注意:

如果您使用的是NSSplitViewController,系统会自动将该控制器分配给其拆分视图的delegate属性,您无法更改它。因此,您需要子类化NSSplitViewController、覆盖其splitViewDidResizeSubviews()方法并从那里通知窗口控制器。您可以使用以下代码来实现:

protocol SplitViewControllerDelegate: class {
    func splitViewControllerDidResize(_ splitViewController: SplitViewController)
}

class SplitViewController: NSSplitViewController {

    weak var delegate: SplitViewControllerDelegate?

    override func splitViewDidResizeSubviews(_ notification: Notification) {
        delegate?.splitViewControllerDidResize(self)
    }

}

不要忘记将您的窗口控制器分配为拆分视图控制器的委托:

override func windowDidLoad() {
    super.windowDidLoad()
    splitViewController?.delegate = self
}

并实现各自的委托方法:

extension MainWindowController: SplitViewControllerDelegate {

    func splitViewControllerDidResize(_ splitViewController: SplitViewController) {
        adjustTabSpaceWidth()
    }

}
于 2019-03-01T19:16:31.627 回答
1

没有实现“本地”工具栏的本地方法。您必须自己创建控件,但我相信制作起来很简单。

此处描述了使用自动布局对齐工具栏项。与 Mischa 描述的自定义工具栏项对齐。

macOS 的方式是使用 Toolbar 解决方案并使它们与上下文相关。在这种情况下,文本属性按钮将在右窗格获得焦点时启用,而在失去焦点时禁用。

于 2019-03-01T12:38:16.313 回答