MacOS 开发(十一) : NSToolbar 纯代码(迅雷顶部工具栏效果实现)

1,782 阅读2分钟

一、需求来源

mac 工具栏开发,非常常见的一种功能,但是中文资源异常的少见,或者是xib 形式,花了一天时间查看了大量的国外相关资料博客最终纯代码实现。 迅雷顶部工具栏

代码实现效果

二、使用示例

import Cocoa
import SwiftExpand

class NNTabViewController: NSWindowController {

//    var toolbar: NSToolbar?
    lazy var toolbar: NSToolbar = {
        let toolbar = NSToolbar(identifier:"ScreenNameToolbarIdentifier")
        toolbar.allowsUserCustomization = true
        toolbar.delegate = self
        return toolbar
    }()

    let toolbarItems: [[String: String]] = [
        ["title": "irrelevant :)", "icon": "", "identifier": "NavigationGroupToolbarItem"],
        ["title": "Share", "icon": NSImage.shareTemplateName, "identifier": "ShareToolbarItem"],
        ["title": "Add", "icon": NSImage.addTemplateName, "identifier": "AddToolbarItem"],
        ["title": "Departments",
         "icon": "NSPreferencesGeneral",
         "identifier": "DepartmentViewController"],
        ["title": "Accounts",
         "icon": "NSFontPanel",
         "identifier": "AccountViewController"],
        ["title": "Employees",
         "icon": "NSAdvanced",
         "identifier": "EmployeeViewController"],
        ["title": "", "icon": "", "identifier": "NNUserInfoView"],
    ]

    var toolbarTabsIdentifiers: [NSToolbarItem.Identifier] {
        var list = toolbarItems.compactMap { $0["identifier"] }.map{ NSToolbarItem.Identifier(rawValue: $0) }
        list.insert(NSToolbarItem.Identifier.flexibleSpace, at: 3)
        list.insert(NSToolbarItem.Identifier.flexibleSpace, at: list.count - 1)
//        list.append(NSToolbarItem.Identifier.flexibleSpace)
        return list

    }

    var currentVC: NSViewController!

    var currentIdentifier = ""

    // **MARK: -lifecycle**
    override init(window: NSWindow?) {
        super.init(window: window)

//        toolbar = NSToolbar(identifier:"ScreenNameToolbarIdentifier")
//        toolbar?.displayMode = .iconAndLabel
//        toolbar?.allowsUserCustomization = true
//        toolbar?.delegate = self
        window?.toolbar = toolbar
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func windowDidLoad() {
        super.windowDidLoad()
        // Do view setup here.
    }

    // **MARK: -funtions**
    @objc func handleSegmentSelected(_ sender: NSSegmentedControl){
        print("\(#function):\(sender.selectedSegment)")
    }

    @objc func handleBtnSelected(_ sender: NSButton){
        print("\(#function):\(sender)")
    }

   
    @objc func handleItemSelected(_ sender: NSToolbarItem){
        print("\(#function):\(sender.itemIdentifier.rawValue)_\(sender.label)")
        loadViewWithIdentifier(itemIdentifier: sender.itemIdentifier.rawValue, withAnimation: true)
    }

    @objc func handleViewSelected(_ sender: NNUserInfoView){
        print("\(#function):\(sender)")
    }

    func loadViewWithIdentifier(itemIdentifier: String, withAnimation shouldAnimate:Bool){
        let VCIdentifiers: [String] = ["DepartmentViewController", "AccountViewController", "EmployeeViewController"]
        if VCIdentifiers.contains(itemIdentifier) == false {
            return
        }

//        print("\(#function):\(itemIdentifier)")
        if (currentIdentifier == itemIdentifier){
            return
        }
        currentIdentifier = itemIdentifier
        guard let itemDict: [String : String] = toolbarItems.filter({ $0["identifier"] == itemIdentifier }).first
            else { return }

        if (itemIdentifier == "DepartmentViewController"){
            currentVC = DepartmentViewController()
        }

        else if (itemIdentifier == "AccountViewController"){
            currentVC = AccountViewController()
        }

        else if (itemIdentifier == "EmployeeViewController"){
            currentVC = EmployeeViewController()
        }

        guard let window = self.window else { return }
//        print(currentVC as Any)
        let newView = currentVC.view
        let windowRect = window.frame
        let currentViewRect = newView.frame
//        print(windowRect as Any)

        let originY = windowRect.origin.y + (windowRect.size.height - currentViewRect.size.height)
        let newFrame = NSMakeRect(windowRect.origin.x, originY, currentViewRect.size.width, currentViewRect.size.height)

        window.title = currentVC.title ?? itemDict["title"]!;
        window.contentView = newView
        window.setFrame(newFrame, display: true, animate: shouldAnimate)
    }

    lazy var popover: NSPopover = {
        let controller = ListViewController()
        controller.preferredContentSize = CGSize(width: kScreenWidth*0.15, height: kScreenHeight*0.25)
        let popView = NSPopover(vc: controller)
        return popView
    }()

    func showPopoverController(_ sender: NSView) {
        popover.show(sender, preferredEdge: .maxY)
    }
}

extension NNTabViewController: NSToolbarDelegate{

    func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
        guard let itemDict: [String : String] = toolbarItems.filter({ $0["identifier"] == itemIdentifier.rawValue }).first
            else { return nil }

        let toolbarItem: NSToolbarItem
        if itemIdentifier.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.goBackTemplateName)!, forSegment: 0)
//            segmented.setWidth(40, forSegment: 0)
//            segmented.setImage(NSImage(named: NSImage.goForwardTemplateName)!, forSegment: 1)
//            segmented.setWidth(40, forSegment: 1)

            let items: [Any] = [NSImage(named: NSImage.goBackTemplateName)!, NSImage(named: NSImage.goForwardTemplateName)!, ]
            let segmented = NSSegmentedControl.create(NSRect(x: 0, y: 0, width: items.count*40, height: 40), items: items)
            segmented.target = self
            segmented.action = #selector(handleSegmentSelected(_:))

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

            toolbarItem = group

        } else if ["ShareToolbarItem", "AddToolbarItem"].contains(itemIdentifier.rawValue) {
            let iconImage = NSImage(named: itemDict["icon"]!)
            let button = NSButton(frame: NSRect(x: 0, y: 0, width: 40, height: 40))
            button.title = ""
            button.image = iconImage
            button.bezelStyle = .texturedRounded
            button.target = self
//            button.action = #selector(handleBtnSelected(_:))
            button.action = Selector(("handleBtnSelected:"))

            toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier)
            toolbarItem.label = itemDict["title"]!
            toolbarItem.view = button
        } else if ["NNUserInfoView", ].contains(itemIdentifier.rawValue) {
            let userView = NNUserInfoView(frame: NSRect(x: 0, y: 0, width: 160, height: 50))
            userView.name = "SoaringHeart"
            userView.desc = "装逼或者被装逼。"
            userView.delegate = self
            toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier)
            toolbarItem.label = itemDict["title"]!
            toolbarItem.target = self
            toolbarItem.action = #selector(handleViewSelected(_:))
            toolbarItem.view = userView

        }  else {
            let iconImage = NSImage(named: itemDict["icon"]!)
            toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier)
            toolbarItem.label = itemDict["title"]!
            toolbarItem.image = iconImage
            toolbarItem.target = self
            toolbarItem.action = #selector(handleItemSelected(_:))
        }
        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 ?? "")
    }
}

extension NNTabViewController: NSToolbarItemValidation{
    func validateToolbarItem(_ item: NSToolbarItem) -> Bool {
        return true
    }
}

extension NNTabViewController: NNUserInfoViewDelegate{
    func userInfoView(_ userView: NNUserInfoView, state: NSResponder.MouseState, event: NSEvent) {
        switch state {
        case .entered:
            print("\(#function):进入区域")
            showPopoverController(userView)

        case .exited:
            print("\(#function):离开区域")

        case .down:
            print("\(#function):点击")

        case .up:
            print("\(#function):抬起")

        default:
            break
        }
    }
}

Github