ViewController reference in AppDelegate using Storyboard - macos

I am writing a one view application based on storyboard.
I have added a toolbar, clicking on buttons messages are received into the AppDelegate.
I need to forward them to the ViewController but I am not able to get a reference to it in the AppDelegate.
How do I do that?

As simple as that:
- (void)applicationDidBecomeActive:(NSNotification *)aNotification {
if (_viewController==nil) {
_viewController = (ViewController *)[[NSApplication sharedApplication] mainWindow].contentViewController;
}
}

Related

The new UISplitViewController in iOS8 using objective c without storyboard

I try to implement adaptive UI in my app. By making UISplitViewController as the rootview controller, I can run the iPhone's code in iPad too.
I red Apple's documentation about UISplitViewController and some samples. All are using storyboards and the sample codes are available in swift only. I can not find a working version of code. So I started the code myself.
See my splitview controller class (BaseSplitViewController)
BaseSplitViewController.h:
#import <UIKit/UIKit.h>
#interface BaseSplitViewController : UISplitViewController <UISplitViewControllerDelegate>
#end
BaseSplitViewController.m:
#import "BaseSplitViewController.h"
#import "TabBarViewController.h"
#interface BaseSplitViewController ()
#property(nonatomic, strong) TabBarViewController *primaryTabBarVC;
#property(nonatomic, strong) UINavigationController *primaryNavigationController;
#property(nonatomic, strong) UINavigationController *secondaryNavigationController;
#end
#implementation BaseSplitViewController
- (instancetype)init
{
self = [super init];
if (self)
{
[self setViewControllers:#[self.primaryNavigationController, self.secondaryNavigationController]];
self.delegate = self;
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(cellTapped:) name:#"cellTapped" object:nil];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self assignPrimaryViewController];
}
- (void)assignPrimaryViewController
{
// Need to assign tab bar controller as primary view controller here
}
- (void)assignSecondaryViewController:(UIViewController *)vc
{
// Need to update the secondary controller each time the primary controller was tapped
}
- (UINavigationController *)primaryNavigationController
{
if (!_primaryNavigationController)
{
_primaryNavigationController = [[UINavigationController alloc] init];
}
return _primaryNavigationController;
}
- (UINavigationController *)secondaryNavigationController
{
if (!_secondaryNavigationController)
{
_secondaryNavigationController = [[UINavigationController alloc] init];
}
return _secondaryNavigationController;
}
- (UITabBarController *)primaryTabBarVC
{
if (!_primaryTabBarVC)
{
_primaryTabBarVC = [[TabBarViewController alloc] init];
}
return _primaryTabBarVC;
}
#end
Some points:
The above class "BaseSplitViewController" is the rootview controller of my app.
That is, self.window.rootViewController = [[BaseSplitViewController alloc] init];
From Apple's Documentation,
"When designing your split view interface, it is best to install
primary and secondary view controllers that do not change. A common
technique is to install navigation controllers in both positions and
then push and pop new content as needed. Having these types of anchor
view controllers makes it easier to focus on your content and let the
split view controller apply its default behavior to the overall
interface."
So, I created two navigation controllers (primary/secondary) and set them as split view controllers's primary & secondary views. setViewControllers: can be used for this.
My primary view here is, tab bar view. So, inside the assignPrimaryViewController: method, I should assign my TabBarViewController as split view controller's primary view.
Here, I found two ways.
1. [self.primaryNavigationController showViewController:self.primaryTabBarVC sender:nil];
2. [self.primaryNavigationController pushViewController:self.primaryTabBarVC animated:YES];
Here, I tried with [self showViewController:self.primaryTabBarVC sender:nil]; but my tab bar view was never shown. From my understanding, here "self" means the UISplitViewController. Calling showViewController: here makes the confusion to choose the navigation controller. Because we have two navigation controllers. So we need to clearly tell that navigation controller which needs to hold the primary controller.
Primary view controller part is over. Now the real problem starts. Consider my primary view controller is the tab bar which have tableview's in it. If I tap on the cell, I need to update the secondary view's content. This is the case in Regular mode. In compact mode, I expect when the user taps on the cell, it should push the detail view (secondary view) with back button.
I expect to put the below code within assignSecondaryViewController: vc: method
[self.secondaryNavigationController pushViewController:vc animated:NO];
[self.primaryNavigationController showDetailViewController:self.secondaryNavigationController sender:nil];
But it does not works.
Questions:
What should be placed inside assignPrimaryViewController & assignSecondaryViewController: methods to get my expected result?
And I really, yes really don't know how to implement UISplitViewController's following delegate methods.
primaryViewControllerForCollapsingSplitViewController:
splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
primaryViewControllerForExpandingSplitViewController:
splitViewController:separateSecondaryViewControllerFromPrimaryViewController:
Would be really helpful, if someone explains this new UISplitViewController's behavior.
Thanks

How to hide the initial window on start with OS X storyboards

I am creating an OS X status bar application, so I want the application to start hidden.
I have created a "storyboard" application, and the initial window always shows up, even if "Visible at launch" is unchecked (was unchecked by default).
Note: if I disable "Is initial controller" then the app correctly starts without any window, but my (now orphan) window seems to never be added to the storyboard:
var mainWindow = NSStoryboard(name: "Main", bundle: nil)?.instantiateControllerWithIdentifier("mainWindow")
The "mainWindow" controller is not found (even though I correctly set "Storyboard ID" on the Window Controller).
So I think it's better to leave "Is initial controller" but simply have the main window hidden at the start…
Uncheck the "Is Initial Controller" box on the storyboard, leaving your app without an initial controller. Your app will run, but will have no window.
This might be a bit of a hack, but you can do this
func applicationDidFinishLaunching(notification: NSNotification) {
// Insert code here to initialize your application
NSApplication.sharedApplication().windows.last!.close()
}
And then later on...
NSApplication.sharedApplication().windows.last!.makeKeyAndOrderFront(nil)
NSApplication.sharedApplication().activateIgnoringOtherApps(true)
Uncheck "Is Initial Controller", but then you need to set the storyboard and its associated NSWindowController manually.
The precise way of doing that is shown in this answer, which I'll quote here:
[...] in your AppDelegate, set up a property for the window controller:
#property NSWindowController *myController;
In your applicationDidFinishLaunching: method implementation, create a reference to the Storyboard. This way you get access your window controller from the storyboard. After that, the only thing left to do is to display the window by sending your window controller the showWindow: method.
#import "AppDelegate.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
#synthesize myController;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// get a reference to the storyboard
NSStoryboard *storyBoard = [NSStoryboard storyboardWithName:#"Main" bundle:nil];
// instantiate your window controller
myController = [storyBoard instantiateControllerWithIdentifier:#"secondWindowController"];
// show the window
[myController showWindow:self];
}
#end
The way to do this is just like you tried:
let storyboard = NSStoryboard(name: "Main", bundle: nil)
guard let mainWC = storyboard.instantiateControllerWithIdentifier("MainWindowController") as? MainWindowController else {
fatalError("Error getting main window controller")
}
// optionally store the reference here
self.mainWindowController = mainWC
mainWC.window?.makeKeyAndOrderFront(nil) // or use `.showWindow(self)`
The only thing you probably forgot was to uncheck "Release when closed".
This would immediately release the window and prevents the storyboard loading mechanism from finding it even if you had the right identifier.

Displaying a Cocoa Window as a Sheet in Xcode 4 (OSX 10.7.2) with ARC

I'm trying to get a Login Window to display as a sheet from my MainWindow, but whenever I try to implement the AppKit methods an error always pops up for various indistinguishable reasons.
None of the online guides out there are working, when i apply their code / adapted classes to my own project they never work.
Most of the guides are heavily outdated, including the Apple Documentation. And none of them seem to be compatible with Automatic Reference Counting. Or the Xcode 4 interfaces.
Would someone be able to detail for me in full a guide, for the simplest way of displaying a sheet following a button press on the MainWindow.
Feel free to ask for more information if you need it.
Tutorial for Xcode 4
Create new project and add the following to AppDelegate.h and AppDelegate.m.
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSPanel *theSheet;
}
#property (assign) IBOutlet NSWindow *window;
#end
AppDelegate.m
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window = _window;
- (IBAction) showTheSheet:(id)sender {
[NSApp beginSheet:theSheet
modalForWindow:(NSWindow *)_window
modalDelegate:self
didEndSelector:nil
contextInfo:nil];
}
-(IBAction)endTheSheet:(id)sender {
[NSApp endSheet:theSheet];
[theSheet orderOut:sender];
}
#end
Open the MainMenu.xib.
Use the existing NSWindow.
Make it visible using the following button:
Create one new NSPanel.
Add the appropriate NSButtons.
Connect Close to the App Delegate.
And select endTheSheet.
Connect Open to the App Delegate.
And select showTheSheet.
Connect the App Delegate to the new NSPanel.
And select theSheet.
Select the NSPanel and disable Visible At Launch.
(Essential step!)
Now hit run and enjoy the result:
Things have changed in SDK 10.10 - the calls are simpler to understand I think. A parent window is in charge of launching a child NSWindow as a sheet - and then you pass this child NSWindow to NSApp to run modally. Then do the opposite to unwrap.
Displaying sheet
To display the sheet instead of calling:
[NSApp beginSheet:theSheet
modalForWindow:(NSWindow *)_window
modalDelegate:self
didEndSelector:nil
contextInfo:nil];
You now call on the parent window:
(void)beginSheet:(NSWindow *)sheetWindow
completionHandler:(void (^)(NSModalResponse returnCode))handler
And then to run the sheet as in modal loop, you also have to call NSApp with:
- (NSInteger)runModalForWindow:(NSWindow *)aWindow
Closing Sheet
To close the sheet, call on the parent window:
- (void)endSheet:(NSWindow *)sheetWindow
Which causes the completionHandler from the above call to fire, - in which you can put a call to stop running the modal window by calling NSApp with:
- (void)stopModalWithCode:(NSInteger)returnCode
Full example
#implementation AppDelegate
#synthesize window = _window;
- (IBAction) showTheSheet:(id)sender {
[_window beginSheet: theSheet
completionHandler:^(NSModalResponse returnCode) {
[NSApp stopModalWithCode: returnCode];
}];
[NSApp runModalForWindow: theSheet];
}
-(IBAction)endTheSheet:(id)sender {
[_window endSheet: theSheet];
}
#end

How does an NSView subclass communicate with the controller?

I am brand spanking new to Cocoa programming, and am still kind of confused about how things wire together.
I need a pretty simple application that will fire off a single command (let's call it DoStuff) whenever any point on the window is clicked. After a bit of research it looks like subclassing NSView is the right way to go. My ClickerView.m file has this:
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"mouse down");
}
And I have added the View to the Window and have it stretching across the whole thing, and is properly writing to the log every time the window is clicked.
I also have my doStuff method on my controller (this could be refactored to its own class I suppose, but for now it works):
- (IBAction)doStuff:(id)sender {
// do stuff here
}
So, how do I get mouseDown in ClickerView to be able to call DoStuff in the controller? I have a strong .NET background and with that, I'd just have a custom event in the ClickerView that the Controller would consume; I just don't know how to do that in Cocoa.
edit based on Joshua Nozzi's advice
I added an IBOutlet to my View (and changed it to subclass NSControl):
#interface ClickerView : NSControl {
IBOutlet BoothController *controller;
}
#end
I wired my controller to it by clicking and dragging from the controller item in the Outlets panel on the View to the controller. My mouseDown method now looks like:
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"mouse down");
[controller start:self];
}
But the controller isn't instantiated, the debugger lists it as 0x0, and the message isn't sent.
You could either add it as an IBOutlet like Joshua said, or you could use the delegate pattern.
You would create a Protocol that describes your delegate's methods like
#protocol MyViewDelegate
- (void)doStuff:(NSEvent *)event;
#end
then you'd make your view controller conform to the MyViewDelegate protocol
#interface MyViewController: NSViewController <MyViewDelegate> {
// your other ivars etc would go here
}
#end
Then you need to provide the implementation of the doStuff: in the implementation of MyViewController:
- (void)doStuff:(NSEvent *)event
{
NSLog(#"Do stuff delegate was called");
}
then in your view you'd add a weak property for the delegate. The delegate should be weak, so that a retain loop doesn't form.
#interface MyView: NSView
#property (readwrite, weak) id<MyViewDelegate> delegate;
#end
and then in your view you'd have something like this
- (void)mouseDown:(NSEvent *)event
{
// Do whatever you need to do
// Check that the delegate has been set, and this it implements the doStuff: message
if (delegate && [delegate respondsToSelector:#selector(doStuff:)]) {
[delegate doStuff:event];
}
}
and finally :) whenever your view controller creates the view, you need to set the delegate
...
MyView *view = [viewController view];
[view setDelegate:viewController];
...
Now whenever your view is clicked, the delegate in your view controller should be called.
First, your view needs a reference to the controller. This can be a simple iVar set at runtime or an outlet (designated by IBOutlet) connected at design time.
Second, NSControl is a subclass of NSView, which provides the target/action mechanism machinery for free. Use that for target/action style controls. This provides a simple way of setting the reference to your controller (the target) and the method to call when fired (the action). Even if you don't use a cell, you can still use target/action easily (NSControl usually just forwards this stuff along to its instance of an NSCell subclass but doesn't have to).
you can also use a selector calling method,
define two properties in custom class:
#property id parent;
#property SEL selector;
set them in view controller:
graph.selector=#selector(onCalcRate:);
graph.parent=self;
and call as:
-(void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
[_parent performSelector:_selector withObject:self];
}

Correct way to setup target/action for NSMenuItem in Cocoa?

I'm having some real difficulty with some initial Cocoa programming I am carrying out.
Essentially, I have an NSStatusBar item with an NSMenu attached as the menu. The menu has a single NMMenuItem. In IB I have connected the NSMenuItem to an NSObject which itself is set to the ApplicationDelegate's class; I have then set the Received Actions to an IBAction method in the ApplicationDelegate. Everything is hooked up correctly I think, except when I run the program and click the menu item the IBAction method is not called. I really can't seem to work it out. Here is the relevant code.
Application Delegate h file:
#import <Cocoa/Cocoa.h>
#interface sssAppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSMenu *statusMenu;
NSStatusItem *statusItem;
}
- (IBAction)showPreferencePanel:(id)sender;
#end
Application Delegate m file:
#import "sssAppDelegate.h"
#implementation sssAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
-(void)awakeFromNib{
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain];
[statusItem setMenu:statusMenu];
[statusItem setTitle:#"Status"];
[statusItem setHighlightMode:YES];
}
- (IBAction)showPreferencePanel:(id)sender {
NSLog(#"Hello World!");
}
#end
As I say, in IB I have connected the NSMenu to statusMenu in the Application Delegate (thus the menu all shows up under the NSStatusBar), and I have connected an NSMenuItem within the NSMenu to the NSObject with the Application Delegate class, and hooked it up to call showPreferencePanel, but nothing happens when I run it!!!
I tried it programatically as well but still can't get the IBAction method to be called.
Edit: I would attach some screen grabs to show the setup in IB but I'm not yet allowed.
The main nib which contains the menu that is added to the NSStatusBar, it looks like this:
FO NSApplication
FR FirstResponder
Application NSApplication
Font Manager NSFontManager
Main Menu NSMenu
Menu Item (Preferences) NSMenuItem
Sss App Delegate sssAppDelegate
NSMenuItem:
Sent Actions - showPreferencePanel--->Sss App Delegate
Sss App Delegate:
Outlets - statusMenu--->Main Menu
Received Actions - showPreferencePanel:--->Main Item (Preferences)
Programmatically, have you tried :
[statusItem setTarget:someTarget];
[statusItem setAction:#selector(someSelector)];
It should work.

Resources