NSWindowController: loadWindow loads window from nib but showWindow: does nothing - objective-c

I have an NSWindowController subclass called _PreferencesWindowController with the following implementation -
#synthesize window;
- (id)init {
self = [super initWithWindowNibName:#"PreferencesWindow"];
if (!self) return nil;
return self;
}
And I tried to show the window in _PreferencesWindowController by using the following code -
_preferencesWindowController = [[_PreferencesWindowController alloc] init];
[_preferencesWindowController showWindow:nil];
It does nothing, and I checked _preferencesWindowController.window is nil from the debugger.
However if I call loadView on _preferencesWindowController the window can be loaded and is visible; _preferencesWindowController.window is no longer nil-valued -
[_preferencesWindowController loadWindow];
I looked at Apple's documentation on NSWindowController it specifically says "you should never directly invoke loadWindow", instead showWindow: should be used. I'm wondering what I might have missed that resulted in the above-mentioned behaviour I have been seeing.

OK I solved this by looking at the NSWindowController header file.
The problem is in my header file for _PreferencesWindowController -
#interface _PreferencesWindowController : NSWindowController <NSToolbarDelegate> {
NSWindow *window;
}
#property (assign) IBOutlet NSWindow *window;
#end
By removing the #property declaration and changing NSWindow *window ivar to IBOutlet NSWindow *window, showWindow: method now works without a glitch.
The property declaration must have resulted in an undefined behaviour in showWindow: method in NSWindowController's implementation.

Related

Error when using NSWindowController

I just started a new Cocoa project after a long time... And I don't know why, but I always get an error when calling a xib by a NSWindowController. What I do is really very simple: I have a new project as a starting point and then I don't wantz to call the xib from Appdelegate, but from a subclass of NSWindowController. Then the output tells me that:
2014-11-12 09:58:18.519 SimpleTest[8554:378690] ApplePersistence=NO
2014-11-12 09:58:18.671 SimpleTest[8554:378690] Failed to connect (window) outlet from (NSApplication) to (NSWindow): missing setter or instance variable
Okay, how does it look in code? My Appdelegate looks like this:
#import "AppDelegate.h"
#import "MainWindowController.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (strong) MainWindowController *mainWindowController;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
_mainWindowController = [[MainWindowController alloc] initWithWindowNibName:#"MainMenu"];
[self.mainWindowController showWindow:self];
}
#end
Nothing special so far. The MainWindowController looks like this:
#import "MainWindowController.h"
#interface MainWindowController ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation MainWindowController
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self != nil)
{
//do something
}
return self;
}
#end
And again very simple... Additiponally I have some modifications in IB: File'Owner of MainMenu.xib becomes MainWindowController. Its 'window' outlet is connected to the Window of the application. The delegate of Window is connected to File's Owner. Well, that's it! But why do I receive this error? What am I doing wrong?
---EDIT---
this shows the connections in IB
The most important is using right selector to create new instance and having all wired up correctly.
Steps:
1. Add new xib with window or an empty one and add window to it
2.Select File owner and set it to NSWindowController or its subclass.
Select window and connect it to File owner
Make new NSWindowController instance
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSWindowController *windowController = [[NSWindowController alloc] initWithWindowNibName:#"Window"];
[NSApp runModalForWindow:[windowController window]];
}

Calling NSObject superclass method with [self performSelector:#selector]?

I have a Subclass of NSObject in which I want to call IMMEDIATELY (a sort of -(void)viewDidLoad) a method (in this case to load a MkMapView): what's the better way to do this? I think I cant use viewDidLoad, so can I use performSelector?
SubClass.h
#interface Mysubclass : NSObject <MKMapViewDelegate> {
}
SubClass.m (1st alternative)
-(id)init{
self = [super init];
if ( self != nil ) {
// THE CODE TO INITIALIZE MKMAPVIEW
}
return self
}
OR
SubClass.m (2nd alternative)
-(id)init{
[self performSelector:#selector(myMethod)];
return self;
}
-myMethod{
// THE CODE TO INITIALIZE MKMAPVIEW
}
What's the better (or correct) alternative? Its possible to avoid -(id)init? Or everytime I add a subclass, to call a method I have to write it into -(id)init? Thank you!
There is no reason to use -performSelector: in this context. If you want to add a method that initializes the MKMapView when your object is created, call the method from within the if (self) block:
- (id)init
{
self = [super init];
if (self) {
[self setupMapView];
}
return self;
}
- (void)setupMapView
{
// THE CODE TO INITIALIZE MKMAPVIEW
}
It is a matter of personal preference/style whether to have a second method -setupMapView or to simply leave the code for setting up the MKMapView in the if (self) block of the -init method or to break the code off into a second method -setupMapView called from -init.
That being said, it sounds like other things may be off with your setup. Your MKMapView should [most likely] be within a UIViewController subclass (which will probably have an associated XIB), so you will be have access to -viewDidLoad. Note that your UIViewController subclass will serve as the delegate to your MKMapView.
Update 1
In your UIViewController subclass instance (I'll assume you called it ViewController, you should have an IBOutlet to an MKMapView object. Do this in ViewController.h either by (1) adding an instance variable
#interface ViewController : UIViewController
{
IBOutlet MKMapView *myMap;
}
#end
or by (2) adding a property
#interface ViewController : UIViewController
#property (nonatomic, strong, readwrite) IBOutlet MKMapView *myMap;
#end
Now open ViewController.xib in Interface Builder. You should have an MKMapView inside the view. If you don't already, add one from the Object Library. Right click on File's Owner. Locate the row with the item myMap. Drag from the circle on the right end of the row to the MKMapView in the visible view.
Your ViewController class now has an outlet to the MKMapView. You will be able to send messages to the MKMapView subview of your view controllers view after it has been loaded.
You should have a property or an instance variable for your SubClass instance so that it doesn't get destroyed as soon as -viewDidLoad returns. Do this again by either adding an instance variable to ViewController.h
#interface ViewController : UIViewController
{
IBOutlet MKMapView *myMap;
SubClass *istance;
}
#end
or by adding a property
#interface ViewController : UIViewController
#property (nonatomic, strong, readwrite) IBOutlet MKMapView *myMap;
#property (nonatomic, strong, readwrite) SubClass *istance;
#end
Now, in ViewController.m, you need to define -viewDidLoad so that self.istance is set as the delegate of self.myMap. In the comments, I had suggested creating your own initializer -initWithMapView:. If you plan on having SubClass do some extensive set-up of your MKMapView, that makes sense. If you just want SubClass to be the delegate of the MKMapView, there's no need for such a method.
Let's consider both cases:
(1) using a method -[SubClass initWithMapView:]:
In ViewController.m you'll have (within the #implementation of ViewController)
- (void)viewDidLoad
{
self.istance = [[SubClass alloc] initWithMapView:self.myMap];
}
In SubClass.h you'll have (within the #interface of SubClass)
- (id)initWithMapView:(MKMapView *)mapView;
#property (nonatomic, weak, readwrite) MKMapView *mapView;
In SubClass.m you'll have (within the #implementation of SubClass)
- (id)initWithMapView:(MKMapView *)mapView
{
self = [super init];
if (self) {
self.mapView = mapView;
self.mapView.delegate = self;
//more setup of mapView.
}
return self;
}
(2) using -[SubClass init]:
Instead, in ViewController.m you'll have
- (void)viewDidLoad
{
self.istance = [[SubClass alloc] init];
self.myMap.delegate = self.istance;
}

set Delegate to Subclass of NSImageView placed in Xib

I have an NSImageView subclass that I use for dragging and dropping Files onto in my app.
To do this I created a subclass of NSImageView and also dragged the NSImageView onto my XIB, I then set the class of the XIB NSImageView Object to my custom subclass.
Everything works well with the drag and drop and I know I have the correct file after the drag.
The problem comes in when I want to update a textfield on the MainViewController based on the file dragged in.
I created the following subclass and protocol
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
#protocol PDFDraggedIntoWell <NSObject>
#required
-(void)PDFDraggedIntoWellWithURL:(NSURL*) importedURL;
#end
#interface DragAndDropImageView : NSImageView
#property (strong) id <PDFDraggedIntoWell> delegate;
#end
Then in my implementation in the subclass I try to call the delegate method
-(void) finishedDragginInFileWithURL:(NSURL*) importedURL{
if( self.delegate != nil && [ self.delegate respondsToSelector: #selector(PDFDraggedIntoWellWithURL:)]) {
[self.delegate performSelector:#selector(PDFDraggedIntoWellWithURL:) withObject:importedURL];
}
}
The problem I run into is how do you assign the delegate. From the XIB NSImageView to my MainviewController I connect up an IBOutlet
#property (weak) IBOutlet DragAndDropImageView *draggedFileImageView;
And I have declared that my ViewController will receive the delegate
#interface MyMainUIViewController ()<PDFDraggedIntoWell>
with the appropriate method implemented
-(void) PDFDraggedIntoWellWithURL:(NSURL *)importedURL{ }
Now I have tried in various places to assign delegate to self (in viewDidLoad - which doesn’t get called since the view is being loaded in a XIB??) and also in
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
but all I get back is the delegate is still nil when debugging.
What am I doing wrong? Thanks for the help!!
If you are using xibx the initWithCoder method is called for initialization. Set your delegate there.
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
myView.delegate = self;
}
return self;
}
Alternatively set the delegate via interface builder by dragging wile holding ctrl from the File's Owner to your view. Like this:
I was able to add add the delegate to the awakefromnib(in mymainviewcontroller) method and things are working fine now. Thanks

Show NSWindow from separate nib (not modal)

How to do it? I simply want to load a window and show it in front of the main window.
NSWindowController* controller = [[NSWindowController alloc] initWithWindowNibName: #"MyWindow"];
NSWindow* myWindow = [controller window];
[myWindow makeKeyAndOrderFront: nil];
This code shows the window for one moment and then hides it. IMHO this is because I don't keep reference to the window (I use ARC). [NSApp runModalForWindow: myWindow]; works perfectly but I don't need to show it modally.
Yes, with ARC if you don't hold a reference to the window it will be torn down as soon you as you exit the routine you were in. You need to hold a strong reference to it in an ivar. [NSApp runModalForWindow: myWindow] is different because the NSApplication object holds a reference to the window as long as it is being run modally.
You should likely do something similar to the following, which creates a strong reference to the NSWindowController instance you create:
.h:
#class MDWindowController;
#interface MDAppDelegate : NSObject <NSApplicationDelegate> {
__weak IBOutlet NSWindow *window;
MDWindowController *windowController;
}
#property (weak) IBOutlet NSWindow *window;
#property (strong) MDWindowController *windowController;
- (IBAction)showSecondWindow:(id)sender;
#end
.m:
#import "MDAppDelegate.h"
#import "MDWindowController.h"
#implementation MDAppDelegate
#synthesize window;
#synthesize windowController;
- (IBAction)showSecondWindow:(id)sender {
if (windowController == nil) windowController =
[[MDWindowController alloc] init];
[windowController showWindow:nil];
}
#end
Note that rather than sending the makeKeyAndOrderFront: method directly to the NSWindowController's NSWindow, you can just use NSWindowController's built-in showWindow: method.
While the above code (and sample project below) use a custom subclass of NSWindowController, you also use a generic NSWindowController and create the instance using initWithWindowNibName: (just make sure the File's Owner of the nib file is set to NSWindowController rather than a custom subclass like MDWindowController).
Sample project:
http://www.markdouma.com/developer/MDWindowController.zip

Programmatically changing a UILabel from the App Controller in a Navigation Based iOS App

I'm having a lot of trouble with what seems like a very simple thing. I cannot update a UILabel programmatically from a Navigation-based iOS App. I don't want to use a button as this label is designed to report the status of an external system, and should update on launch. There is no need to make the user go though the extra step on touching the button if I don't have to.
The following is a somewhat exhaustive list of the steps I've taken. I'm sorry if some of this seems unnecessary, but in my experience even the smallest forgotten step can be the cause of the issue.
From a fresh Navigation-based App in Xcode here are the steps I'm taking:
Replace UITableView with a generic UIView class
Re-wire File's Owner's view outlet to the new UIView
Add a UILabel to the center of the UIView, make the text centered, and leave the default text.
Save and Exit Interface Builder
RootViewController.h
#import <UIKit>
#interface RootViewController : UIViewController {
UILabel *myLabel;
}
#property (nonatomic, retain) IBOutlet UILabel *myLabel;
#end
RootViewController.m
#import "RootViewController.h"
#implementation RootViewController
#synthesize myLabel;
...
Removed TableView stuff from RootViewController.m
Wire IBOutlet myLabel to the Label in RootViewController.xib
Save and Exit Interface Builder
tempNavAppAppDelegate.m
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
RootViewController *rootViewCont = navigationController.visibleViewController;
rootViewCont.myLabel.text = #"test";
NSLog(#"Label Text: %#", rootViewCont.myLabel.text);
return YES;
}
...
Build/Run
The Label shows as "Label" not "test". And the log reports:tempNavApp[94186:207] Label Text: (null)
I've tried a number of different ways to get this done, but any help would be appreciated.
The Journey
After discovering that my rootViewCont.myLabel was also nil, thanks to the help of mprudhom, I decided to test and see if I could assign myLabel.text a value in RootViewController.m's - (void)viewDidLoad method.
It worked, I was able to change the text directly from the RootViewController. But while this proved my View Controller wasn't broken, it did not solve my initial desire to change the UILabel from tempNavAppAppDelegate.m.
Elliot H. then suggested that navigationController.visibleViewController wasn't actually returning a view controller. I had tested for the value of rootViewCont and it came back as a RootViewController, but Elliot's suggestion got me thinking about the app's lifecycle and when the different parts of my code was actually loaded up.
So I started printing an NSLog at each step of the launch process (application:didFinishLaunchingWithOptions:, applicationDidBecomeActive:, viewDidLoad, viewDidAppear:), and discovered to my surprise that [self.window makeKeyAndVisible]; does not mean that the view will load before application:didFinishLaunchingWithOptions: is complete.
With that knowledge in hand I knew where the problem was. The solution (or at least my solution) seems to be NSNotificationCenter. I have now registered for notifications in tempNavAppAppDelegate and I am broadcasting a notification in RootViewController's viewDidAppear: method.
The Pertinent Code
RootViewController.h:
#interface RootViewController : UIViewController {
IBOutlet UILabel *myLabel;
}
#property (nonatomic, retain) UILabel *myLabel;
#end
RootViewController.m:
#implementation RootViewController
#synthesize myLabel;
- (void)viewDidLoad {
[super viewDidLoad];
NSParameterAssert(self.myLabel);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:#"viewDidAppear" object:self];
}
tempNavAppAppDelegate.h:
#interface tempNavAppAppDelegate : NSObject {
UIWindow *window;
UINavigationController *navigationController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
- (void)viewDidAppearNotification:(id)notification;
#end
tempNavAppAppDelegate.m:
#implementation tempNavAppAppDelegate
#synthesize window;
#synthesize navigationController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(viewDidAppearNotification:) name:#"viewDidAppear" object:nil];
return YES;
}
- (void)viewDidAppearNotification:(id)notification
{
NSString *noteClass = [NSString stringWithFormat:#"%#", [[notification object] class]];
if ([noteClass isEqualToString:#"RootViewController"]) {
RootViewController *noteObject = [notification object];
noteObject.myLabel.text = #"Success!";
}
}
If this code is printing nil:
rootViewCont.myLabel.text = #"test";
NSLog(#"Label Text: %#", rootViewCont.myLabel.text);
Then almost certainly it is because rootViewCont.myLabel itself is nil. Try logging the value of rootViewCont.myLabel as well and you'll see.
Are you sure you wired up the label to your UILabel IBOutput declaration in Interface Builder? That's most commonly the problem.
I personally always assert all my expected outlets in viewDidLoad so that I catch early on when the outlets have been (accidentally or not) been decoupled in Interface Builder. E.g.:
- (void)viewDidLoad {
[super viewDidLoad];
NSParameterAssert(rootViewCont.myLabel);
}
your interface should look like this
#import <UIKit>
#interface RootViewController : UIViewController {
// IBOutlet here...
IBOutlet UILabel *myLabel;
}
#property (nonatomic, retain) UILabel *myLabel;
#end
Is visibleViewController actually returning the view controller? My guess is since application:didFinishLaunchingWithOptions: hasn't returned yet, it's possible UINavigationController hasn't properly configured that property to return yet, even though you've added the navigation controller's subview to the view hierarchy, it's probably that visibleViewController isn't valid until after viewDidAppear: is called on the view controller in question.
Try having an IBOutlet to the RootViewController directly, or create it programmatically, and then assign the label text.
Just a general reminder: If an object is nil (in this case visibleViewController would be returning nil), and you send it a message, you won't crash, because messages to nil are valid and won't do anything. When you call the myLabel accessor on the rootViewCont object, if rootViewCont is nil, myLabel will return nil always.