I'm writing a Mac app using Storyboards for the first time. My app has a NSWindow Controller, which contains a NSViewController with an NSSplitView containing two further NSViewControllers.
The NSWindow controller contains an NSToolbar, with buttons that need to have actions performed within the NSViewControllers contained within the window.
The storyboard looks a little like this.
MainWindowController
+--------------------+
| ooo |
+--------------------+
| |
| |
| |
+--------------------+
|
|
\/
MainViewController
+--------------------+
| | |
| | |
| | |
| | |
| | |
+--------------------+
/ \
/ \
\/ \/
SubViewControllerA SubViewControllerB
+--------------------+ +--------------------+
| | | |
| | | |
| | | |
| | | |
| | | |
+--------------------+ +--------------------+
Previously, I've solved similar problems using NSNotification. In this scenario, a button in the toolbar for the Window Controller would post a notification and observers in the nested view controllers would act upon it. This time, I wonder if there might be a better way.
I have previously used Delegates for other things - but I wonder if it might be possible to have the Window Controller as a Delegate for my views. I think that I've managed to get most of the code right here:
MainWindowController.h
@protocol MainWindowDelegate <NSObject>
- (void)subviewVisibility;
@end
#pragma mark - SubViewControllerA
@interface SubViewControllerA : NSViewController
@end
#pragma mark - SubViewControllerB
@interface SubViewControllerB : NSViewController
@end
#pragma mark - MainViewController
@interface MainViewController : NSViewController <MainWindowDelegate> {
bool sidebarVisible;
}
@property (assign) IBOutlet NSSplitView* mainSplitView;
- (void)subviewVisibility;
@end
#pragma mark - MainWindowController
@interface MainWindowController : NSWindowController <NSToolbarDelegate>
@property (nonatomic, weak) id<MainWindowDelegate> delegate;
@property (assign) IBOutlet NSToolbar *toolbar;
@end
MainWindowController.m
#import "MainWindowController.h"
#pragma mark - SubViewControllerA
@implementation SubViewControllerA : NSViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
// Update the view, if already loaded.
}
@end
#pragma mark - SubViewControllerB
@implementation SubViewControllerB : NSViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
// Update the view, if already loaded.
}
@end
#pragma mark - MainViewController
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
sidebarVisible = NO;
// Do any additional setup after loading the view.
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
// Update the view, if already loaded.
}
- (void)subviewVisibility {
if (sidebarVisible) {
[self changeLeftPanelVisibility:1];
} else {
[self changeRightPanelVisibility:1];
}
sidebarVisible = !sidebarVisible;
}
- (void)changeLeftPanelVisibility:(bool)visible {
// Do stuff
}
- (void)changeRightPanelVisibility:(bool)visible {
// Do stuff
}
@end
#pragma mark - MainWindowController
@implementation MainWindowController
#pragma mark Toolbar Handlers
// - Code to set up the toolbar, removed for clarity
#pragma mark Action Handlers
- (IBAction)showSideBar:(id)sender {
[self.delegate subviewVisibility];
}
@end
If I run this code (and it compiles without warning or error, by the way), then I can set a breakpoint on showSideBar - and it breaks correctly (the code is all in the Toolbar handlers which I removed for clarity - that works fine). It doesn't call subview visibility in MainViewController.
I have used delegates before - but never for NSControllers. I expect that you've all noticed that I don't actually set the delegate anywhere - and I suspect that this is the problem. Ordinarily, I'd initialise my class and then its delegate - but in this case it'd look like this:
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
sidebarVisible = NO;
MainWindowController* lwController = MainWindowController.new;
lwController.delegate = self;
// Do any additional setup after loading the view.
}
.
.
.
Which clearly makes no sense. A subview can't initialise its parent!
Okay - so what about the InterfaceBuilder - is there a delegate in MainWindowController that I can hook up? Well no, not as far as I can see - and certainly not in a different controller to the MainWindowController.
So is this a failed experiment? (I think not, I've found tantalising hints online that it can be done, but nothing I can hang my hat on) Are Notifications the way forward? (which at least have the benefit that I know that I'm doing) Or is there a different, better way that I haven't thought of?
As you can see, I've been writing my code in ObjC because that's what I'm comfortable using - but I'd be delighted to see answers in Swift too (that might be more useful to the wider community). It's the thinking I'm after - I can write the code if I have your suggestions!