3

I'm making a simple OS X app that looks like that in Interface Builder:

Backstory

The MainView is a subclass of NSViewController, and the toolbar is a simple NSToolbar I built with the Interface Builder. By default, the NSToolbar items are enabled, but they have no purpose until Main Image View contains a NSImage, so I want my NSViewController (MainView) to enable the Toolbars button when it receives an image, and disable them when the "Clear" button is pressed.

The issue

To implement the toolbar actions, I followed this answer, so now I have a MainView.swift implementing the NSViewController with the toolbar IBActions in it (and it works), so my toolbar can send actions to my view. But I have no way for the toolbar to receive messages from the NSViewController, because they are not in the same object: my toolbar is in the Window, my MainImageView is in MainView's View.

Question

From what my search brought up, I should use a delegate, so I started making a simple protocol:

protocol MainToolbarDelegate {
     func setButtonsEnabled(to:Bool)
} 

However, when setting the var delegate: MainToolbarDelegate in my class MainView: NSViewController, I have the same issue: I cannot point to the toolbar because it is not in the same object, and there is no segue linking one to another. I tried var delegate: MainToolbarDelegate = self.view.window.toolbar but Xcode returns me an error.

So now, my question is: what am I missing? That's the first time I use a delegate so I might be doing something completely wrong. Do I need to subclass my Window so that I could use segues?

Sorry again if my questions are not accurate, I'm completely lost.

Community
  • 1
  • 1
LaX
  • 453
  • 5
  • 16
  • 1
    I think it will be much easier just subclass NSWindowController. Here is one of my answer which is similiar to your case... http://stackoverflow.com/questions/28916862/how-to-pass-data-from-nswindowcontroller-to-its-nsviewcontroller/28925238#28925238 – Prontto May 25 '15 at 15:42
  • I figured that out a few minutes before your comment, and it seems to work, thanks! – LaX May 25 '15 at 16:12

1 Answers1

2

Use Notifications!

Say you'd like to enable/disable a remove project button based on an action taken in the ViewControler. First you'll need to add some custom notifications (this is for Swift 3 on Xcode 8 beta 6, syntax and naming may vary for your version or the final version):

extension Notification.Name {
    static let rowSelected = Notification.Name("turnOn")
    static let rowDeselected = Notification.Name("turnOff")
}

Next, you'll need to add an outlet for the button you would like to work with, as well as observers and functions in your window controller to handle the actions:

class SummaryWindowController: NSWindowController {

    @IBOutlet weak var removeProjectButton: NSToolbarItem!

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        shouldCascadeWindows = true
    }

    override init(window: NSWindow?) {
        super.init(window: window)

    }

    override func windowDidLoad() {
        super.windowDidLoad()

        self.removeProjectButton.isEnabled = false

        NotificationCenter.default.addObserver(self, selector: #selector(SummaryWindowController.enableRemoveButton(_:)), name: .turnOn, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(SummaryWindowController.disableRemoveButton(_:)), name: .turnOff, object: nil)
    }

    func enableRemoveButton(_ notification: Notification) {
        self.removeProjectButton.isEnabled = true
    }

    func disableRemoveButton(_ notification: Notification) {
        self.removeProjectButton.isEnabled = false
    }
}

and then you need a View controller to use to send the actions. In this case, it would be two buttons in the view:

class SummaryViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    @IBAction func onButtonTapped(sender: NSButton) {
         NotificationCenter.default.post(Notification(name: .turnOn))
    }

    @IBAction func offButtonTapped(sender: NSButton) {
         NotificationCenter.default.post(Notification(name: .turnOff))
    }
}
Ryan Collins
  • 679
  • 1
  • 6
  • 13
  • 1
    Technically Apple recommends you implement the NSToolbarValidation protocol here: https://developer.apple.com/reference/appkit/1658294-nstoolbaritemvalidation, but I find that it lacks some granularity in controlling toolbar items, so I opt for notifications like above. – Ryan Collins Sep 15 '16 at 21:20
  • @RyanCollins - the link is dead. – Duck Aug 31 '19 at 14:00
  • @SpaceDog https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Toolbars/Tasks/ValidatingTBItems.html#//apple_ref/doc/uid/20000753 – Ryan Collins Sep 01 '19 at 15:03