0

I'm fairly new to macOS programming and wanted to implement a custom class for a NSToolbarItem. Each time the item is pressed, I want to change a property of the class. Here is an example code:

class CustomToolbarButton: NSToolbarItem
{
   override init(itemIdentifier: String)
   {
      super.init(itemIdentifier: itemIdentifier)
      super.target = self
      super.action = #selector(reactToPress)
   }

    func reactToPress(sender: NSToolbarItem)
    {
      toggled_property = !toggled_property
      print("Item pressed")
    }

    private(set) var toggled_property = true;
}

This class is inserted in a toolbar in the storyboard. I've made sure to change the class-specifier in the identity-inspector to CustomToolbarButton. However, the action never seems to be triggered, as "Item pressed" never appears in the console output.

I've also tried to declare the "reactToPress" function in the following ways:

func reactToPress()
@objc func reactToPress()
@objc func reactToPress(sender: NSToolbarItem)

but still no success.

KO70
  • 189
  • 2
  • 15

2 Answers2

2

You need a non-weak reference for the item's target. try this:

// Define this in your class.
static let itemTarget = CustomToolbarButton(itemIdentifier: "myButton")

// and use it when setting the target in the constructor.
self.target = CustomToolbarButton.itemTarget

From docs.swift.org "A weak reference is a reference that does not keep a strong hold on the instance it refers to, and so does not stop ARC from disposing of the referenced instance."

If all references to the instance are weak, the item is created and released automatically. You need at least one non-weak reference to keep hold of the instance.

Recommended Reading: https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

Also, like you stated, the @objc directive is needed in the action function. Here are some helpful links.

'#selector' refers to a method that is not exposed to Objective-C '#selector' refers to a method that is not exposed to Objective-C

See Selector Expression https://docs.swift.org/swift-book/ReferenceManual/Expressions.html

Lastly here is a working example on Xcode 10.3 and Swift 5:

//  Created by Juan Miguel Pallares Numa on 9/12/19.
//  Copyright © 2019 Juan Miguel Pallares Numa. All rights reserved.

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    // Strong reference.
    var myWindowController = WindowController()

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        myWindowController.makeWindowKeyAndOrderFront()
    }
}

import Cocoa

class ToolbarController: NSObject, NSToolbarDelegate {

    let identifiers = [NSToolbarItem.Identifier("myIdentifier")]
    let toolbar = NSToolbar()
    let toolbarItem: NSToolbarItem

    override init() {
        toolbarItem = NSToolbarItem(itemIdentifier: identifiers[0])
        super.init()
        toolbar.delegate = self

        toolbarItem.label = "Print My Console Message"
        toolbarItem.target = self
        toolbarItem.action = #selector(
            ToolbarController.toolbarAction(_:))
        toolbarItem.image = NSImage(named: NSImage.applicationIconName)!
    }

    @objc func toolbarAction(_ sender: Any?) {
        print("Hello world")
    }

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

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

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

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

        return toolbarItem
    }
}

import Cocoa

class WindowController: NSWindowController {

    var toolbarController = ToolbarController()

    convenience init() {
        let window = NSWindow(
            contentViewController: ViewController())

        self.init(window: window)

        window.toolbar = toolbarController.toolbar
    }

    func makeWindowKeyAndOrderFront() {
        window?.makeKeyAndOrderFront(nil)
    }
}

import Cocoa

class ViewController: NSViewController {

    override func loadView() {
        view = NSView(frame: NSRect(x: 0, y: 0, width: 400, height: 300))
    }
}
Juanmi
  • 71
  • 1
  • 1
  • 7
0

Try the Objective-C compliant syntax

super.action = #selector(reactToPress(_:))

and

func reactToPress(_ sender: NSToolbarItem)

In Swift 4 you explicitly have to add the @objc attribute

@objc func reactToPress(_ sender: NSToolbarItem)
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thx for the suggestion. Unfortunately no result. I don't know if it is important but I use Swift 4 – KO70 Aug 09 '17 at 18:07
  • I updated the answer. But the Swift 4 most likely the `init(itemIdentifier` method does not work with a `String` parameter. – vadian Aug 09 '17 at 18:12
  • I added a console output to the init method and it gets triggered. However even when I add the @objc, the action function is not called. – KO70 Aug 09 '17 at 18:14
  • Actually the `init` method has been changed to `init(itemIdentifier: NSToolbarItem.Identifier)` in Swift 4 – vadian Aug 09 '17 at 18:16
  • Oh my god, I'm sorry ... I'm actually using swift 3.1 – KO70 Aug 09 '17 at 18:18
  • Why do you assign `target` and `action` to super? Use `self.action` and `self.target` – vadian Aug 09 '17 at 18:23
  • Tried it with `self.action` and `self.target` but still no success. Thank you for your endurance. – KO70 Aug 09 '17 at 18:26