【问题标题】:How to add a custom NSToolbarItem to an existing toolbar programmatically如何以编程方式将自定义 NSToolbarItem 添加到现有工具栏
【发布时间】:2019-04-27 07:17:32
【问题描述】:

我很难将自定义 NSToolbarItem 添加到我现有的工具栏。

NSToolbar 是在 NSWindowController 中创建的,然后我有一个以编程方式填充工具栏项的功能,代码为:

public func populateFileToolbarItem(_ toolbar: NSToolbar) -> Void{
    let itemId = NSToolbarItem.Identifier("FILE_OPEN")
    let index = toolbar.items.count
    var toolbarItem: NSToolbarItem
    toolbarItem = NSToolbarItem(itemIdentifier: itemId)
    toolbarItem.label = String("File")
    toolbarItem.paletteLabel = String("Open File")
    toolbarItem.toolTip = String("Open file to be handled")
    toolbarItem.tag = index
    toolbarItem.target = self
    toolbarItem.isEnabled = true
    toolbarItem.action = #selector(browseFile)
    toolbarItem.image = NSImage.init(named:NSImage.folderName)
    toolbar.insertItem(withItemIdentifier: itemId, at: index)
}

然后我调用这个函数将工具栏项添加到 windowController 中的现有工具栏

.......
  populateFileToolbarItem((self.window?.toolbar)!)
  self.window?.toolbar?.insertItem(withItemIdentifier: NSToolbarItem.Identifier.flexibleSpace, at: (self.window?.toolbar?.items.count)!)
  self.window?.toolbar?.insertItem(withItemIdentifier: NSToolbarItem.Identifier.print, at: (self.window?.toolbar?.items.count)!)
  print("after toolbaritems were inserted into toolbar. \(String(describing: self.window?.toolbar?.items.count))") 
......

控制台打印输出显示,工具栏只添加了两个工具栏项。

.......
after toolbaritems were inserted into toolbar. Optional(2)

并且工具栏中没有自定义项目显示。

哪位有经验的,请指教!

【问题讨论】:

    标签: swift macos toolbar items


    【解决方案1】:

    要从工具栏中添加/删除项目,您需要工具栏委托:NSToolbarDelegate。

    这是我正在使用的实现模板(可能比你想要的更多)。

    创建各种类型工具栏项的样板代码:

    
    struct ToolbarIdentifiers {
        static let mainToolbar = NSToolbar.Identifier(stringLiteral: "MainToolbar")
        static let navGroupItem = NSToolbarItem.Identifier(rawValue: "NavGroupToolbarItem")
        static let shareItem = NSToolbarItem.Identifier(rawValue: "ShareToolBarItem")
        static let addItem = NSToolbarItem.Identifier(rawValue: "AddToolbarItem")
        static let statusItem = NSToolbarItem.Identifier(rawValue: "StatusToolbarItem")
        static let filterItem = NSToolbarItem.Identifier(rawValue: "FilterToolbarItem")
        static let sortItem = NSToolbarItem.Identifier(rawValue: "SortToolbarItem")
        static let cloudUploadItem = NSToolbarItem.Identifier(rawValue: "UploadToolbarItem")
        static let cloudDownloadItem = NSToolbarItem.Identifier(rawValue: "DownloadToolbarItem")
        static let leftButtonItem = NSToolbarItem.Identifier(rawValue: "leftButtonToolbarItem")
        static let rightButtonItem = NSToolbarItem.Identifier(rawValue: "rightButtonToolbarItem")
        static let hideShowItem = NSToolbarItem.Identifier(rawValue: "hideShowToolbarItem")
    }
    
    // Base toolbar item type, extended for segmented controls, buttons, etc.
    struct ToolbarItem {
        let identifier: NSToolbarItem.Identifier
        let label: String
        let paletteLabel: String
        let tag: ToolbarTag
        let image: NSImage?
        let width: CGFloat
        let height: CGFloat
        let action: Selector?
        weak var target: AnyObject?
        var menuItem: NSMenuItem? = nil // Needs to be plugged in after App has launched.
        let group: [ToolbarItem]
    
        init(_ identifier: NSToolbarItem.Identifier, label: String = "", tag: ToolbarTag = .separator, image: NSImage? = nil,
             width: CGFloat = 38.0, height: CGFloat = 28.0,
             action: Selector? = nil, target: AnyObject? = nil, group: [ToolbarItem] = [], paletteLabel: String = "") {
            self.identifier = identifier
            self.label = label
            self.paletteLabel = paletteLabel
            self.tag = tag
            self.width = width
            self.height = height
            self.image = image
            self.action = action
            self.target = target
            self.group = group
        }
    }
    // Image button -- creates NSToolbarItem
    extension ToolbarItem {
        func imageButton() -> NSToolbarItem {
            let item = NSToolbarItem(itemIdentifier: identifier)
            item.label = label
            item.paletteLabel = label
            item.menuFormRepresentation = menuItem // Need this for text-only to work
            item.tag = tag.rawValue
            let button = NSButton(image: image!, target: target, action: action)
            button.widthAnchor.constraint(equalToConstant: width).isActive = true
            button.heightAnchor.constraint(equalToConstant: height).isActive = true
            button.title = ""
            button.imageScaling = .scaleProportionallyDown
            button.bezelStyle = .texturedRounded
            button.tag = tag.rawValue
            button.focusRingType = .none
            item.view = button
            return item
        }
    }
    // Segmented control -- creates NSToolbarItemGroup containing multiple instances of NSToolbarItem
    extension ToolbarItem {
        func segmentedControl() -> NSToolbarItemGroup {
            let itemGroup = NSToolbarItemGroup(itemIdentifier: identifier)
            let control = NSSegmentedControl(frame: NSRect(x: 0, y: 0, width: width, height: height))
            control.segmentStyle = .texturedSquare
            control.trackingMode = .momentary
            control.segmentCount = group.count
            control.focusRingType = .none
            control.tag = tag.rawValue
    
            var items = [NSToolbarItem]()
            var iSeg = 0
            for segment in group {
                let item = NSToolbarItem(itemIdentifier: segment.identifier)
                items.append(item)
                item.label = segment.label
                item.tag = segment.tag.rawValue
                item.action = action
                item.target = target
                control.action = segment.action // button & container send to separate handlers
                control.target = segment.target
                control.setImage(segment.image, forSegment: iSeg)
                control.setImageScaling(.scaleProportionallyDown, forSegment: iSeg)
                control.setWidth(segment.width, forSegment: iSeg)
                control.setTag(segment.tag.rawValue, forSegment: iSeg)
                iSeg += 1
            }
            itemGroup.paletteLabel = paletteLabel
            itemGroup.subitems = items
            itemGroup.view = control
            return itemGroup
        }
    }
    // Text field -- creates NSToolbarItem containing NSTextField
    extension ToolbarItem {
        func textfieldItem() -> NSToolbarItem {
            let item = NSToolbarItem(itemIdentifier: identifier)
            item.label = ""
            item.paletteLabel = label
            item.tag = tag.rawValue
            let field = NSTextField(string: label)
            field.widthAnchor.constraint(equalToConstant: width).isActive = true
            field.heightAnchor.constraint(equalToConstant: height).isActive = true
            field.tag = tag.rawValue
            field.isSelectable = false
            item.view = field
            return item
        }
    }
    // Menu item -- creates an empty NSMenuItem so that user can click on the label
    // definitely a work-around till we implement the menus
    extension ToolbarItem {
        mutating func createMenuItem(_ action: Selector) {
            let item = NSMenuItem()
            item.action = action
            item.target = target
            item.title = label
            item.tag = tag.rawValue
            self.menuItem = item
        }
    }
    /*
     * Create specialized toolbar items with graphics, labels, actions, etc
     * Encapsulates implementation-specific details in code, because the table-driven version was hard to read.
     */
    struct InitializeToolbar {
    }
    extension InitializeToolbar {
        static func navGroupItem(_ action: Selector, segmentAction: Selector, target: AnyObject) -> ToolbarItem {
            var group = [ToolbarItem]()
            group.append(ToolbarItem(NSToolbarItem.Identifier(rawValue: "BackToolbarItem"), label: "Prev", tag: .navPrev,
                                     image: NSImage(named: NSImage.goBackTemplateName), action: segmentAction, target: target))
            group.append(ToolbarItem(NSToolbarItem.Identifier(rawValue: "FwdToolbarItem"), label: "Next", tag: .navNext,
                                     image: NSImage(named: NSImage.goForwardTemplateName), action: segmentAction, target: target))
            let item = ToolbarItem(ToolbarIdentifiers.navGroupItem, tag: .navGroup, width: 85, height: 28,
                                   action: action, target: target, group: group, paletteLabel: "Navigation")
            return item
        }
    }
    extension InitializeToolbar {
        static func hideShowItem(_ action: Selector, segmentAction: Selector, target: AnyObject) -> ToolbarItem {
            var group = [ToolbarItem]()
            group.append(ToolbarItem(NSToolbarItem.Identifier(rawValue: "HideLeftItem"), label: "", tag: .leftButton,
                                     image: NSImage(named: "leftButton"), action: segmentAction, target: target))
            group.append(ToolbarItem(NSToolbarItem.Identifier(rawValue: "HideRightItem"), label: "", tag: .rightButton,
                                     image: NSImage(named: "rightButton"), action: segmentAction, target: target))
            let item = ToolbarItem(ToolbarIdentifiers.hideShowItem, tag: .hideShow, width: 85, height: 28,
                                   action: action, target: target, group: group, paletteLabel: "Hide/Show")
            return item
        }
    }
    extension InitializeToolbar {
        static func addItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
            let item = ToolbarItem(ToolbarIdentifiers.addItem, label: "Add", tag: .add, image: NSImage(named: NSImage.addTemplateName), action: action, target: target)
            return item
        }
    }
    extension InitializeToolbar {
        static func shareItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
            let item = ToolbarItem(ToolbarIdentifiers.shareItem, label: "Share", tag: .share, image: NSImage(named: NSImage.shareTemplateName), action: action, target: target)
            return item
        }
    }
    extension InitializeToolbar {
        static func filterItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
            let item = ToolbarItem(ToolbarIdentifiers.filterItem, label: "Filter", tag: .filter, image: NSImage(named: "filter"), action: action, target: target)
            return item
        }
    }
    extension InitializeToolbar {
        static func sortItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
            let item = ToolbarItem(ToolbarIdentifiers.sortItem, label: "Sort", tag: .sort, image: NSImage(named: "sort"), action: action, target: target)
            return item
        }
    }
    extension InitializeToolbar {
        static func cloudDownloadItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
            let item = ToolbarItem(ToolbarIdentifiers.cloudDownloadItem, label: "Down", tag: .cloudDownload, image: NSImage(named: "cloudDownload"), action: action, target: target)
            return item
        }
    }
    extension InitializeToolbar {
        static func cloudUploadItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
            let item = ToolbarItem(ToolbarIdentifiers.cloudUploadItem, label: "Up", tag: .cloudUpload, image: NSImage(named: "cloudUpload"), action: action, target: target)
            return item
        }
    }
    extension InitializeToolbar {
        static func leftButtonItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
            let item = ToolbarItem(ToolbarIdentifiers.leftButtonItem, label: "", tag: .leftButton, image: NSImage(named: "leftButton"), action: action, target: target)
            return item
        }
    }
    extension InitializeToolbar {
        static func rightButtonItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
            let item = ToolbarItem(ToolbarIdentifiers.rightButtonItem, label: "", tag: .rightButton, image: NSImage(named: "rightButton"), action: action, target: target)
            return item
        }
    }
    
    extension InitializeToolbar {
        static func textItem() -> ToolbarItem {
            return ToolbarItem(ToolbarIdentifiers.statusItem, label: "Watch This Space", tag: .status, width: 300, height: 24)
        }
    }
    

    这里是工具栏类,它实现了初始化器和委托:

    /*
     * Initializer builds a specialized toolbar.
     */
    enum ToolbarTag: Int {
        case separator = 1
        case navGroup
        case navPrev
        case navNext
        case add
        case share
        case filter
        case sort
        case cloudDownload
        case cloudUpload
        case leftButton
        case rightButton
        case hideShow
        case status
    }
    class Toolbar: NSObject, NSToolbarDelegate, Actor {
        var actorDelegate: ActorDelegate?
        var identifier: NSUserInterfaceItemIdentifier?
        var toolbarItemList = [ToolbarItem]()
        var toolbarItemIdentifiers: [NSToolbarItem.Identifier] { return toolbarItemList.map({ $0.identifier }) }
        var toolbarDefaultItemList = [ToolbarItem]()
        var toolbarDefaultItemIdentifiers: [NSToolbarItem.Identifier] { return toolbarDefaultItemList.map({ $0.identifier }) }
    
        // Delegate toolbar actions
        @objc func controlSentAction(_ sender: Any) {
            guard let control = sender as? NSControl else { return }
            guard let tag = ToolbarTag(rawValue: control.tag) else { return }
            actorDelegate?.actor(self, initiator: control, tag: tag, obj: nil)
        }
        @objc func segmentedControlSentAction(_ sender: Any) {
            guard let segmented = sender as? NSSegmentedControl else { return }
            guard let tag = ToolbarTag(rawValue: segmented.tag(forSegment: segmented.selectedSegment)) else { return }
            actorDelegate?.actor(self, initiator: segmented, tag: tag, obj: nil)
        }
        // These don't get called at the moment
        @objc func toolbarItemSentAction(_ sender: Any) { ddt("toolbarItemSentAction") }
        @objc func menuSentAction(_ sender: Any) { ddt("menuSentAction") }
    
        // Toolbar initialize
        init(_ window: Window) {
            super.init()
            identifier = Identifier.View.toolbar
    
            let toolbar = NSToolbar(identifier: ToolbarIdentifiers.mainToolbar)
            toolbar.centeredItemIdentifier = ToolbarIdentifiers.statusItem
    
            // Build the initial toolbar
            // Text field
            toolbarItemList.append(ToolbarItem(.flexibleSpace))
            toolbarItemList.append(InitializeToolbar.textItem())
            toolbarItemList.append(ToolbarItem(.flexibleSpace))
            // Show/Hide
            toolbarItemList.append(InitializeToolbar.hideShowItem(#selector(toolbarItemSentAction), segmentAction: #selector(segmentedControlSentAction), target: self))
            // Save initial toolbar as default
            toolbarDefaultItemList = toolbarItemList
            // Also allow these, just to demo adding
            toolbarItemList.append(InitializeToolbar.cloudDownloadItem(#selector(controlSentAction), target: self))
            toolbarItemList.append(InitializeToolbar.sortItem(#selector(controlSentAction), target: self))
    
            toolbar.allowsUserCustomization = true
            toolbar.displayMode = .default
            toolbar.delegate = self
            window.toolbar = toolbar
        }
    
        deinit {
            ddt("deinit", caller: self)
        }
    }
    /*
     * Implement NSToolbarDelegate
     */
    extension Toolbar {
    
        // Build toolbar
        func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
            guard let item = toolbarItemList.firstIndex(where: { $0.identifier == itemIdentifier }) else { return nil }
            switch toolbarItemList[item].identifier {
            case ToolbarIdentifiers.navGroupItem, ToolbarIdentifiers.hideShowItem:
                return toolbarItemList[item].segmentedControl()
            case ToolbarIdentifiers.addItem, ToolbarIdentifiers.shareItem, ToolbarIdentifiers.sortItem, ToolbarIdentifiers.filterItem, ToolbarIdentifiers.cloudUploadItem, ToolbarIdentifiers.cloudDownloadItem,
                 ToolbarIdentifiers.leftButtonItem, ToolbarIdentifiers.rightButtonItem:
                return toolbarItemList[item].imageButton()
            case ToolbarIdentifiers.statusItem:
                return toolbarItemList[item].textfieldItem()
            default:
                return nil
            }
        } // end of toolbar
    
        func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
            return toolbarDefaultItemIdentifiers;
        }
    
        func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
            return toolbarItemIdentifiers
        }
    
        func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
            return []
        }
    
        func toolbarWillAddItem(_ notification: Notification) {
        }
    
        func toolbarDidRemoveItem(_ notification: Notification) {
        }
    } // End of extension
    

    初始工具栏:

    自定义下拉菜单,Cocoa 为您做的:

    添加云按钮后:

    希望这会有所帮助。

    添加以澄清 2019 年 4 月 28 日:

    我的 Toolbar 类不是 NSToolbar 子类。它的初始值设定项传递了对窗口的引用,因此最后它将窗口的工具栏设置为它创建的工具栏:

        init(_ window: Window) {
            super.init()
            identifier = Identifier.View.toolbar
    
    **** stuff removed for clarity ****
    
            let toolbar = NSToolbar(identifier: ToolbarIdentifiers.mainToolbar)
            toolbar.allowsUserCustomization = true
            toolbar.displayMode = .default
            toolbar.delegate = self
            window.toolbar = toolbar
        }
    
    

    也许这是混淆语义,但它创建工具栏并充当工具栏委托,正如您在扩展中看到的那样。

    “Actor”协议是我的协调框架的一部分,对于构建工具栏本身并不重要。我必须包含整个演示应用程序才能展示这一点,并且我假设您有自己的设计来将工具栏操作传递给您的控制器/模型。

    这个应用程序是 Xcode 10.2/Swift 5,虽然我不认为它使用任何新的 Swift 5 功能。

    【讨论】:

    • 感谢您的友好建议。我是 Swift 编程的新手,我有很多 Java 技能,但在 Swift/XCode 方面的经验非常有限。我会尝试您的解决方案并让您知道结果。再次感谢。
    • Swift 4.2 中没有“ToolbarTag”的东西,你必须使用 Swift 5。是吗?
    • 对不起。我在您的文档中找到了 ToolbarTag,但不幸的是,我在 Swift 中没有找到 Actor 和 ActorDelegate 模型,请问是哪里来的?
    • 所有工具栏项目必须通过 Swift 函数调用插入到工具栏中(因为工具栏项目将使用不同的动作函数调用更新,当不同的内容类型加载到内容区域时),我的问题是,插入工具栏项目不在这里。请注意。
    • 我尝试从 Toolbar 类中删除 Actor 和 ActorDelegate,然后我从 windowController 调用 Toolbar 为我的窗口创建工具栏,但我发现系统抛出“无法将 'Toolbar' 类型的值分配给类型' NSToolbar?'" 作为 Swift 4.2 NSWindow 请求一个 NSToolbar。
    【解决方案2】:

    How Toolbars Work

    要创建工具栏,您必须创建一个提供重要信息的委托:

    • 默认工具栏标识符列表。此列表用于恢复默认设置并构建初始工具栏。也可以使用 Interface Builder 库中的工具栏项来指定默认的工具栏项集。
    • 允许的项目标识符列表。如果工具栏是可定制的,则允许的项目列表用于构建定制调色板。
    • 给定项目标识符的工具栏项目。

    例如添加flexibleSpace、打印和自定义项:

    class MyWindowController: NSWindowController, NSToolbarDelegate {
    
        var toolbarIdentifier = NSToolbarItem.Identifier("FILE_OPEN")
    
        func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
            return [NSToolbarItem.Identifier.flexibleSpace, NSToolbarItem.Identifier.print, toolbarIdentifier]
        }
    
        func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
            return [NSToolbarItem.Identifier.flexibleSpace, NSToolbarItem.Identifier.print, toolbarIdentifier]
        }
    
        func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
            willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
            if itemIdentifier == toolbarIdentifier {
                let toolbarItem = NSToolbarItem(itemIdentifier: toolbarIdentifier)
                toolbarItem.label = String("File")
                toolbarItem.paletteLabel = String("Open File")
                toolbarItem.toolTip = String("Open file to be handled")
                toolbarItem.isEnabled = true
                toolbarItem.target = self
                toolbarItem.action = #selector(browseFile)
                toolbarItem.image = NSImage.init(named:NSImage.folderName)
                return toolbarItem
            }
            else {
                return NSToolbarItem(itemIdentifier: itemIdentifier)
            }
        }
    
    }
    

    还可以在 IB 中添加部分或全部标准和/或自定义项目。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多