Getting values from a modal window launched by a different NSViewController

I am building a document based application that uses a split view controller that contains a custom NSViewController on the left and a custom SKView controller on the right. The left view controller has a button that when pressed creates a modal window that has text fields for entering values as well as 'OK' and 'Cancel' buttons.


All of that works in so far as the modal window popping up goes, but when the 'OK' button is pressed I want to access contents of the dialog's text fields from within my custom SKViewController. This is problematic, because the SKViewController is in a differnt scene from the dialog window or even the NSVIew contoller on the left of the split view. So there is no way to just connect to IBActions on the SKView controller or connect up IBOutlets to the dialogs text fields.


Approaches I have tried:

  1. Using NSNotificationCenter to send a message when the 'OK' button is pressed. This message is received by my SKView controller. This approach works, but has one big flaw - every open document SKViewController gets the message and tries to act on it. I could probably figure out some way to target the message with an identifer so only the proper SKViewController processes the message, but this seems like it's not the best practice.
  2. Sending an action to the first responder to call a function on my SKViewController. This seems like it's probably along the lines of the correct way to handle this, but it hasn't worked for me, probably because the the SKViewController is not in the responder chain when the dialog is modal.

A third approach I am considering is to set the NSView that launches the modal a delegate of the modal, so the modal can call an action on the NSView. The NSView will in turn have a delegate of the NSSplitPaneViewController, which will have an action to call the desired method on the SKViewController. So the message bounces up to the split view controller then back down to the SKViewController. But this level of coupling seems pretty bad.


So, what is the best approach to pass modal dialog results to an aritrary view?

Replies

I would prefer approach 1.


Why not make the splitViewController the sender ?


Then the SKViewController who receives notification could test the sender is also its parent SplitViewController.


Unless I am missing something here.

It's not really an arbitrary view controller on the right. If the button that triggers the modal dialog "belongs" to the left view controller (LVC), but the result of the dialog "belongs" to the right view controller (RVC), you may as well give the LVC a reference to the RVC.


Temporarily putting aside the question of how to do that, you have a couple of choices. You can have the LVC present the modal dialog, retrieve the data from the text fields when OK is pressed, then send a message to the RVC with that data. Or, when the button is pressed, LVC could send a message to the RVC to tell the RVC to present the dialog itself.


Either way, I suggest you don't think of other view controllers as getting the data from the text fields. It's really the dialog's responsibility to do that, and then to pass the data to whatever needs to process it. Why? Because it's generally a bad idea for one view controller to depend on the view/control structure of a view it doesn't own. That sort of linkage will tie you in knots, eventually.


So, how does the LVC get a reference to the RVC? Unfortunately, this is messier on macOS than it should be. I would recommend one of three ways:


1. Let the LVC assume it's in the left pane of a split view controller. It can walk up the view controller hierarchy (every view controller as a "parent" property) until it finds the split view controller, then get it's right pane and check that the pane's view controller is the right class to be the RVC. You should be able to do this in viewDidLoad().


2. Use a custom window controller with a property to hold a reference to the RVC. The RVC can set this reference property in the window controller to 'self', which means that the LVC can find the reference to the RVC when the button is pressed. The catch here is that the RVC can't find its window controller until its view is in the view hierarchy. That means you can't set the window controller property in viewDidLoad(), but you have to wait until viewWillAppear(). At that point, the window controller can be found as self.view.window.windowController (with optional unwrapping and type casting as needed.)


3. Same as #2 but use the document instead of a custom window controller. This is slightly less good (what happens if you change your app to support multiple windows per document?), but lets you avoid creating a custom window controller subclass. In this case, the way to get to the document is self.view.window.windowController.document.


#1 is probably the easiest way to do this, if you don't mind the assumption about the split view controller. #2 is a general technique you can apply to more complex situations.

How would the split view controller get the message when the "OK" button is pressed on the dialog? The dialog is in a different scene so I can't connect the action of the button to an IBAction on the split view controller.

I agree on the bit about not accessing the text fields of the dialog from the SKViewController - I mispoke about that. I actually have a computed property ont the dialog that constructs a dictionary with all the values and I access that. I think your method may be the cleanest, but I thinkit still suffers from one view controller having to know too much about the view controller hierarchy. If I ever move things around I would have to redo the logic that walks the hierarchy.


Update:


I think a slightly cleaner way is to use a custom NSSplitViewController since it has access to both VIewControllers on the left and right via "splitViewItems". It can set an "skViewController" property on the left view contoller, which can in turn set this property on the modal dialog's view controller which can then call a method on the SKViewController when the "OK" button is pressed.

Sounds perfectly fine to me!