【问题标题】:Interface builder segmented controls?界面生成器分段控件?
【发布时间】:2010-11-22 07:59:08
【问题描述】:

我正在构建一个工具栏,我希望它看起来至少有点像 Preview 的。我想创建上一个/下一个按钮。我认为它完成的方式是使用分段控件,每个控件中都有一个图像。问题是我不知道如何得到下面的标签。我可以将一个标签放在整个事物的中心,但标记分段控件,但我无法在控件的每个部分下获得下一个和上一个标签。

我在这里错过了什么?

谢谢。

【问题讨论】:

  • 有人知道为什么我不能在这里选择正确的答案吗?

标签: xcode macos interface-builder


【解决方案1】:

正如@frédéric-blanc 指出的那样:实现这一目标的唯一方法是以编程方式使用NSToolbarItemGroup

诀窍是将viewsubitems 数组分配给NSToolbarItemGroup

let group = NSToolbarItemGroup(itemIdentifier: NSToolbarItem.Identifier(rawValue: "NavigationGroupToolbarItem"))

let itemA = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "PrevToolbarItem"))
itemA.label = "Prev"
let itemB = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "NextToolbarItem"))
itemB.label = "Next"

let segmented = NSSegmentedControl(frame: NSRect(x: 0, y: 0, width: 85, height: 40))
segmented.segmentStyle = .texturedRounded
segmented.trackingMode = .momentary
segmented.segmentCount = 2
// Don't set a label: these would appear inside the button
segmented.setImage(NSImage(named: NSImage.goLeftTemplateName)!, forSegment: 0)
segmented.setWidth(40, forSegment: 0)
segmented.setImage(NSImage(named: NSImage.goRightTemplateName)!, forSegment: 1)
segmented.setWidth(40, forSegment: 1)

// `group.label` would overwrite segment labels
group.paletteLabel = "Navigation"
group.subitems = [itemA, itemB]
group.view = segmented

如果您想尝试一下,请查看下面的示例应用代码。

示例应用(针对 Swift 5.1 更新)

这就是您在新的 macOS/Cocoa 示例应用中测试它所需的全部内容:

  1. 在 Xcode 中,使用默认选项(Swift、storyboards)创建一个新的 macOS 项目。
  2. 添加一个名为WindowController.swift 的新文件并将此代码粘贴到其中。
  3. 在界面生成器中将窗口控制器的类设置为WindowController
import Cocoa

class WindowController: NSWindowController, NSToolbarDelegate {

    var _toolbar: NSToolbar!

    let toolbarItems: [[String: Any]] = [
        ["title" : "irrelevant :)", "icon": "NSPreferencesGeneral", "identifier": NSToolbarItem.Identifier(rawValue: "NavigationGroupToolbarItem")],
        ["title" : "Share", "icon": NSImage.shareTemplateName, "identifier": NSToolbarItem.Identifier(rawValue: "ShareToolbarItem")],
        ["title" : "Add", "icon": NSImage.addTemplateName, "identifier": NSToolbarItem.Identifier(rawValue: "AddToolbarItem")]
    ]

    var toolbarTabsIdentifiers: [NSToolbarItem.Identifier] {

        return toolbarItems.compactMap { $0["identifier"] as? NSToolbarItem.Identifier }

    }

    override func windowDidLoad() {

        _toolbar = NSToolbar(identifier: "TheToolbarIdentifier")
        _toolbar.allowsUserCustomization = true
        _toolbar.delegate = self
        window?.toolbar = _toolbar

    }

    func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {

        guard let infoDictionary: [String : Any] = toolbarItems.filter({ $0["identifier"] as? NSToolbarItem.Identifier == itemIdentifier }).first
            else { return nil }

        let toolbarItem: NSToolbarItem

        if itemIdentifier == NSToolbarItem.Identifier(rawValue: "NavigationGroupToolbarItem") {

            let group = NSToolbarItemGroup(itemIdentifier: itemIdentifier)

            let itemA = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "PrevToolbarItem"))
            itemA.label = "Prev"
            let itemB = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "NextToolbarItem"))
            itemB.label = "Next"

            let segmented = NSSegmentedControl(frame: NSRect(x: 0, y: 0, width: 85, height: 40))
            segmented.segmentStyle = .texturedRounded
            segmented.trackingMode = .momentary
            segmented.segmentCount = 2
            // Don't set a label: these would appear inside the button
            segmented.setImage(NSImage(named: NSImage.goLeftTemplateName)!, forSegment: 0)
            segmented.setWidth(40, forSegment: 0)
            segmented.setImage(NSImage(named: NSImage.goRightTemplateName)!, forSegment: 1)
            segmented.setWidth(40, forSegment: 1)

            // `group.label` would overwrite segment labels
            group.paletteLabel = "Navigation"
            group.subitems = [itemA, itemB]
            group.view = segmented

            toolbarItem = group
        } else {
            toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: itemIdentifier.rawValue))
            toolbarItem.label = (infoDictionary["title"] as? String)!

            let iconImage = NSImage(named: (infoDictionary["icon"] as? String)!)
            let button = NSButton(frame: NSRect(x: 0, y: 0, width: 40, height: 40))
            button.title = ""
            button.image = iconImage
            button.bezelStyle = .texturedRounded
            toolbarItem.view = button
        }

        return toolbarItem
    }

    func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {

        return self.toolbarTabsIdentifiers

    }

    func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {

        return self.toolbarDefaultItemIdentifiers(toolbar)

    }

    func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {

        return self.toolbarDefaultItemIdentifiers(toolbar)

    }

    func toolbarWillAddItem(_ notification: Notification) {

        print("toolbarWillAddItem", (notification.userInfo?["item"] as? NSToolbarItem)?.itemIdentifier ?? "")

    }

    func toolbarDidRemoveItem(_ notification: Notification) {

        print("toolbarDidRemoveItem", (notification.userInfo?["item"] as? NSToolbarItem)?.itemIdentifier ?? "")

    }

}

【讨论】:

  • 这可以在界面生成器中构建吗?
  • 无法使用 Interface Builder 构建分组。但是您可以使用 IB 将 NSSegmentedControl 创建为 @IBOutlet 并在工具栏设置期间引用它。无法手动创建其中的一部分。
  • 谢谢!您的答案应该是公认的,因为它是唯一能解决问题的答案。
  • 感谢@stef 的更新并切换到NSWindowController
  • 不用担心。并感谢您提供原始答案!没有它,我永远不会明白这一点。有趣的花絮:在 Apple 的 Numbers 应用程序中,您可以在分段的工具栏按钮下看到各个居中的标签。这就是让我走上尝试重现这种效果的道路的原因。如果这是他们想要推广的 UI 设计模式,那么他们肯定不会在我们自己的应用程序中轻松重现。
【解决方案2】:

我很确定这是一个“黑客”......风格。但是对于它的标签,我确实认为它“只是”字符串“Previous Next”......(是的,带有一些空格字符作为分隔符:(!)

编辑: 正确答案可以在 cmets 中找到;即来自 Leopard,可以使用 NSToolbarItemGroup 类通过基于分段控件的工具栏项来实现这样的结果,如文档here 中的代码所示。

(很抱歉造成混乱,但由于我是 Stack Overflow 的新手,我不知道是删除我的 cmets 并编辑我的答案更好,还是将它们留作历史记录……)

【讨论】:

  • 不,我错了,因为在预览中单击并按住“上一个”工具栏按钮只会突出显示此分段控件的“上一个”部分。忘记我之前的回答,这不是一个基本的“黑客”,我还在调查:)!
  • 好的,所以使用 F-Script Anywhere (fscript.org),我设法检查了预览的“上一个/下一个”工具栏项的类层次结构;而且……它实际上是使用一些 private NSToolbarItemViewer 类构建的,每个按钮标签都通过(仍然是 private)_NSToolbarItemViewerLabelView 实例表示。这不会解决您的问题,但这就是 Preview 设法标记每个段的方式(而 IB 中可用的默认控件显然只允许一个标记整个控件...)。
  • 好的。好吧,我想这告诉我失败是一种选择……我并不感到惊讶。查看捆绑包中的资源,我看到了单独的左胶囊和右胶囊图像(已选中和未选中),所以我担心他们使用两个单独的按钮进行了某种手动修改,而这两个按钮恰好非常接近彼此……该死。我希望有一个聪明的方法......
  • 哦,哦,我又错了;或者更确切地说,不完全正确;)!看,布赖恩:我想我已经找到了你要找的东西,而且还有代码!它位于 NSToolbarItemGroup 类参考文档的“概述”部分:developer.apple.com/documentation/Cocoa/Reference/…。在它的最后,有一个注释重新分段控件。 HTH…
  • 确实,从 Xcode Tools v3.1.3(迄今为止公开可用的 Apple devtools 的最新版本)开始,无法直观地实现这样的结果——因为没有 NSToolbarItemGroup 控件可以从“库”界面生成器的调色板。恐怕在将组添加到工具栏实例之前,您必须以编程方式分配、初始化和配置组……
【解决方案3】:

Brian,您可能没有看到我对我最初回答的第 n 条评论(原文如此)(因为当它们太多时,Stack Overflow 会将它们隐藏在可折叠部分中)。但是我的最后一条评论回答了您的最新问题(即您无法从 Interface Builder 中构建 NSToolbarItemGroup)。

现在 Snow Leopard 发布了,我可以告诉您,您仍然无法通过从这个较新的 IB 调色板中拖动一些控件来以图形方式解决您的问题。不,您唯一的解决方案是恕我直言,正如我在 8 月 27 日的评论中所引用的那样,“在将组添加到工具栏实例之前以编程方式分配、初始化和配置组。”

我希望你会看到这个新答案(因为我确实认为你根本没有注意到我隐藏的评论......)。祝您好运,希望您能够通过这些提示解决您的问题,所需的代码仍在 NSToolbarItemGroup 类参考的Overview 部分中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-09
    • 1970-01-01
    • 2014-05-20
    • 2011-03-26
    • 2012-04-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多