Nib's IBOutlets are not connected (nil) - xcode

I have a Custom class which is supposed to load a window from a nib file.
When I load the nib file everything looks fine except the IBOutlets are not connected i.e. nil. IBActions work fine and when they are called the IBOutlets are not nil anymore.
The class is added to the nib in the IB as an object and obviously everything wired up.
It's the file's Owner and delegate
When it loads the nib, the window appears only if its "visible at launch" is set.
It doesn't matter where I load the nib and try to access IBOutlets immediately or seconds later.
It must be something very trivial...
UPDATE2: I UPLOADED AN EVEN SIMPLER TRIAL PROJECT: Trial Project2
Expected behaviour: Window2 title changes to "Title has Changed x times" when loads. It only starts working once the button is pressed i.e. IBOutlets are not nil anymore.

The big change was subclassing NSWindowController to create MyClass. This way, you only attempt to manipulate the close button after the window has loaded. Your code was small enough that I thought it best to simply post the changes:
trialProjectAppDelegate.m
#import "trialProjectAppDelegate.h"
#implementation trialProjectAppDelegate
#synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
myclass = [[MyClass alloc] init];
// Note that I'm forcing the window to load here.
(void) [myclass window];
}
#end
MyClass.h
#import <Cocoa/Cocoa.h>
#interface MyClass : NSWindowController
{
IBOutlet NSButton *dismissButton;
}
- (IBAction)closeNaggingWindow:(id)sender;
- (void)disableDismissButton;
#end
MyClass.m
#import "MyClass.h"
#implementation MyClass
- (id)init
{
if ((self = [super initWithWindowNibName:#"Window"]) != nil)
{
}
return self;
}
- (void)disableDismissButton
{
[dismissButton setEnabled:NO];
[dismissButton setTitle:#"Closing enabled in 5 sec"];
[self performSelector:#selector(enableDismissButton:) withObject:nil afterDelay:5];
}
- (IBAction)enableDismissButton:(id)sender
{
[dismissButton setEnabled:YES];
[dismissButton setTitle:#"Close"];
}
- (IBAction)closeNaggingWindow:(id)sender
{
[[self window] close];
[self autorelease];
}
- (void)awakeFromNib
{
[self disableDismissButton];
}
#end
Finally, in your Window.xib file, discard the naggingWindow outlet and connect your window to the window outlet that NSWindowController provides.

I haven't worked with any of the OS X interface classes, so there may be some aspect of this that is not 100% precisely accurate, but basically what is happening is this:
You've wired your nib's NSWindow object to a MyClass object, which is also in your nib. So when you load that nib, here's what's happening:
A MyClass instance is created
An NSWindow instance is created, with several subviews. The NSWindow and the button are attached to the new MyClass instance.
Nothing is wired to the File's Owner pseudo-object (the MyClass instance you created in your app delegate)
Then -changeWindowTitle is called on your original MyClass instance, which has none of its outlets wired.
The solution is simple: remove the MyClass object from your nib file. Select the "File's Owner", and in the Identity Inspector (third icon from the left in the Utility pane) set "Class" to "MyClass". Now reconnect your outlets to the File's Owner object, which is your original MyClass instance. You should now see the behavior you expected.
As an aside, the right place to do things "as soon as the nib is loaded", like setting properties on your fresh IBOutlet objects, is in the method -windowDidLoad.

Related

How to create a reusable button

I'm new to Xcode and objective c. I want to create a button (probably a UIBarButtonItem, for a navigation bar) with a particular appearance, which I will use repeatedly in different views. I've searched at length but can't figure out how.
Would it be appropriate to subclass UIBarButtonItem? I tried to do that, but I was quickly in over my head. Once I create the .h and .m files as a subclass of UIBarButtonItem, do I then have to instantiate a UIBarButtonItem? Do those files not automatically create a button object for me (imported from the parent class), which I can refer to as self? It seems like it would be weird to instantiate a button within its own subclass.
One thing I want to do is add the line,
button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
but I'm at a loss as to how to create reusable button with that property.
Even if that is completely the wrong approach to creating a reusable custom button, I clearly need to improve my understanding of objects, so explanation of my misunderstandings would be much appreciated!
Please?
You can do this without subclassing - by making a category (a preferred way of doing things in Objective-C). With a category you can provide custom methods for an object without having to subclass it. You can't (easily) provide custom properties, but in your case this is not relevant.
Using a Category
This is how your category header file could look:
// UIButton+StyledButton.h
#import <UIKit/UIKit.h>
#interface UIButton (StyledButton)
- (void) styleButton;
#end
Then in the implementation file:
//
// UIButton+StyledButton.m
//
#import "UIButton+StyledButton.h"
#implementation UIButton (StyledButton)
- (void) styleButton {
//style your button properties here
self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
('self' refers to the button object, which also acquires the custom methods you write in the category.)
To use it, #import "UIButton+StyledButton.h" then you can do this sort of thing...
on viewDidLoad {
[super viewDidLoad];
UIButton* myButton = [[UIButton alloc] initWithFrame:myFrame];
[myButton styleButton];
}
Using a Subclass
The subclassed equivalent would look something like this:
The header file...
// MyCustomButton.h
#import <UIKit/UIKit.h>
#interface MyCustomButton : UIButton
- (id)initWithCoder:(NSCoder *)coder;
- (id)initWithFrame:(CGRect)frame;
#end
The implementation file...
// MyCustomButton.m
#import "MyCustomButton.h"
#implementation MyCustomButton
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self styleButton];
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self styleButton];
}
return self;
}
- (void) styleButton {
//style your button properties here
self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
You provide two init methods - initWithFrame is the method to call when alloc/initing the object in code; initWithCoder is the init method called by the system if loading the object from a storyboard or xib.
To create one of your custom buttons in code, you alloc/init as you would any other object:
MyCustomButton* button = [[MyCustomButton alloc] initWithFrame:buttonFrame];
You wouldn't also alloc/init the superclass instance, this is done by the initWithFrame: method in the subclass when it calls [super initWithFrame:frame]. self refers to your custom subclass instance, but that includes all of the (public) properties and methods from it's superclass - unless you have implemented overrides in the subclass.
To use your subclassed button in a storyboard/xib, just drag out a regular button then set it's type to your custom button class in the Identity Inspector. The initWithCoder method is called automatically when the button is loaded from the storyboard/xib into a view.
update
From your comments, you seem to harbour a few confusions still, so here are some highly compressed de-obfuscating notes...
Keep away from subclassing UINavigationController unless you really know what you are doing. It's rarely necessary.
The buttons on a navController's interface are properties of it's contained viewControllers. Look up the navigationItem property of UIViewController (similarly - in the case of a UIToolbar - the View Controller has a toolbarItems property). This allows Navigation Controllers to be context-aware.
The 'viewDidLoad' in my example is assumed to be in a regular UIViewController. My example is also a category on the regular UIBUtton which has no formal relationship with UIBarButtonItem.
Try getting a UIButton category to work with a regular ViewController first before experimenting with UIBarButtonItem (which does not inherit from UIButton).
UIBarbuttonItem has no initWithFrame, because the thing that organises the bar (UINavigationBar or UIToolbar) - in this case a Navigation Controller - is responsible for it's ultimate size and positioning. The viewController governs the relative order of barButtonItems, and whether they appear on the left or the right, and the content and (some aspects of) it's appearance, but the rest is up to the NavController.

init and awakeFromNib

I'd like understand why if i try to set value (I.e. setAlphaValue or setTitle) for an object (like a NSButton) in init method nothing happen, but if i call setter function in awakeFromNib it works correctly.
#interface appController : NSObject {
NSButton *btn;
}
#end;
#implementation appController
-(void)awakeFromNib {
//it works
[btn setTitle:#"My title"];
}
-(id)init {
self = [super init];
if(self){
//it doesn't works
[btn setTitle:#"My title"];
}
}
#end
Outlets are set after -init and before -awakeFromNib. If you want to access outlets, you need to do that in -awakeFromNib or another method that’s executed after the outlets are set (e.g. -[NSWindowController windowDidLoad]).
When a nib file is loaded:
Objects in the nib file are allocated/initialised, receiving either -init, -initWithFrame:, or -initWithCoder:
All connections are reestablished. This includes actions, outlets, and bindings.
-awakeFromNib is sent to interface objects, file’s owner, and proxy objects.
You can read more about the nib loading process in the Resource Programming Guide.
When in init, the view will not be set up properly, and the outlets aren't connected. That's why you use awakeFromNib: in this case - everything is set up and ready to be used.

displaying an image in cocoa

I am totally new at this, but here goes:
I want to press a button in my application, and have an image display (I am planning to read it off of a camera, but to start with, I will open a .TIF file.) However, within the interface builder, I can make buttons within an NSObject object, but the literature makes it sound like I need to make an NSView object to display a file. The problem is, when I do this, the NSObject object does not seem to talk to the NSView object. I am trying to do something like:
NSString *inFilePath;
inFilePath = #"/Volumes/Data/Temp/Smiles.tiff";
NSImage *TestImage = [[NSImage alloc] initWithContentsOfFile:inFilePath];
[MyView setImage:TestImage];
Here, MyView is the NSView object. I get warnings that MyView may not respond to setImage. I have tried to define an IBOutlet within the NSObject object, and although I can connect it within the interface builder, console gives me the error:
unrecognized selector sent to class 0x1e080
So, it's not clear what the next step is. Is there an easy way to get two different objects to "talk to" each other?
Thanks
You want an NSImageView object. In the Interface Builder library this is called an Image Well, but you can configure it so that it doesn't have a bezel.
NSImageView is a subclass of NSView that is optimised for displaying images.
In your header (.h) file, you should have something like this:
#interface MyController : NSObject
{
//this declares an outlet so you can hook up the
//image view in Interface Builder
IBOutlet NSImageView* imageView;
}
#end
And in your implementation (.m) file:
#implementation MyController
//called once the nib is loaded and all outlets are available
- (void)awakeFromNib
{
NSString *inFilePath = #"/Volumes/Data/Temp/Smiles.tiff";
NSImage *testImage = [[NSImage alloc] initWithContentsOfFile:inFilePath];
#if !__has_feature(objc_arc)
[testImage autorelease];
#endif
//imageView is your outlet
[imageView setImage:testImage];
}
#end
In Interface Builder you should hook up the imageView outlet of your class to point to the NSImageView you placed on your view.

Getting around IBActions limited scope

I have an NSCollectionView and the view is an NSBox with a label and an NSButton. I want a double click or a click of the NSButton to tell the controller to perform an action with the represented object of the NSCollectionViewItem. The Item View is has been subclassed, the code is as follows:
#import <Cocoa/Cocoa.h>
#import "WizardItem.h"
#interface WizardItemView : NSBox {
id delegate;
IBOutlet NSCollectionViewItem * viewItem;
WizardItem * wizardItem;
}
#property(readwrite,retain) WizardItem * wizardItem;
#property(readwrite,retain) id delegate;
-(IBAction)start:(id)sender;
#end
#import "WizardItemView.h"
#implementation WizardItemView
#synthesize wizardItem, delegate;
-(void)awakeFromNib {
[self bind:#"wizardItem" toObject:viewItem withKeyPath:#"representedObject" options:nil];
}
-(void)mouseDown:(NSEvent *)event {
[super mouseDown:event];
if([event clickCount] > 1) {
[delegate performAction:[wizardItem action]];
}
}
-(IBAction)start:(id)sender {
[delegate performAction:[wizardItem action]];
}
#end
The problem I've run into is that as an IBAction, the only things in the scope of -start are the things that have been bound in IB, so delegate and viewItem. This means that I cannot get at the represented object to send it to the delegate.
Is there a way around this limited scope or a better way or getting hold of the represented object?
Thanks.
Firstly, you almost never need to subclass views.
Bind doesn't do what you think - you want addObserver:forKeyPath:options:context: (You should try to understand what -bind is for tho ).
When you say "the key seems to be it being the "prototype" view for an NSCollectionViewItem" I think you are really confused…
Forget IBOutlet & IBAction - they don't mean anything if you are not Interface Builder. "Prototype" means nothing in Objective-c.
The two methods in the view do not have different scope in any way - there is no difference between them at all. They are both methods, equivalent in every way apart from their names (and of course the code they contain).
If wizardItem is null in -start but has a value in -mouseDown this is wholly to do with the timing that they are called. You either have an object that is going away too soon or isn't yet created at a point you think it is.
Are you familiar with NSZombie? You will find it very useful.

Which delegate method should I use to respond to clicks on an NSTextField?

I am trying to respond to a click within a textfield. When the click occurs, I am going to open a panel. My initial thought was to use a delegate method to respond to the click event - but I found that:
This method doesn't work:
(void)textDidBeginEditing:(NSNotification *)aNotification
This method does work, but only when I actually edit the text within the text field, not when I first click it. And - if I edit the text a second time, this method stops working:
(void)controlTextDidBeginEditing:(NSNotification *)aNotification
I could use as much detail as possible - or a code example, ideally. I know that an nstextfield inherits from NSControl, which has a mouseDown event. Is there a similar way to respond to the event with a textfield, also?
Since NSTextField inherits from the NSControl class, it also inherits the -(void)mouseDown:(NSEvent*) theEvent method.
I needed to have an NSTextField call a delegate function upon clicking it today, and thought this basic code might be useful. Note that NSTextField already has a delegate and that in SDK v10.6, the delegate already has a protocol associated with it. Note that if you don't care about protocols, compiler warnings, etc., you don't need the protocol and property declarations or the getter and setter.
MouseDownTextField.h:
#import <Appkit/Appkit.h>
#class MouseDownTextField;
#protocol MouseDownTextFieldDelegate <NSTextFieldDelegate>
-(void) mouseDownTextFieldClicked:(MouseDownTextField *)textField;
#end
#interface MouseDownTextField: NSTextField {
}
#property(assign) id<MouseDownTextFieldDelegate> delegate;
#end
MouseDownTextField.m:
#import "MouseDownTextField.h"
#implementation MouseDownTextField
-(void)mouseDown:(NSEvent *)event {
[self.delegate mouseDownTextFieldClicked:self];
}
-(void)setDelegate:(id<MouseDownTextFieldDelegate>)delegate {
[super setDelegate:delegate];
}
-(id)delegate {
return [super delegate];
}
AppDelegate.h:
#interface AppDelegate <MouseDownTextFieldDelegate>
...
#property IBOutlet MouseDownTextField *textField;
...
AppDelegate.m:
...
self.textField.delegate = self;
...
-(void)mouseDownTextFieldClicked:(MouseDownTextField *)textField {
NSLog(#"Clicked");
...
}
...
If you're building with 10.5 SDK, don't have the protocol inherit from NSTextFieldDelegate.

Resources