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.
Related
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.
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.
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];
}
I created a custom view in interface builder with a few buttons in it. I created a class in code for it as the "Files owner" to connect the buttons to action methods.
How do I use this class then?
I cannot just do it like this...
StartScreen *ss = [[StartScreen alloc] initWithFrame: ...];
[self.window.contentView addSubView: ss];
...
because this only produces an empty view. (of course: the StartScreen class doesn't know anything about the nib file yet.)
What I want to do is something like:
StartScreen *ss = LoadCustomViewFromNib(#"StartScreen");
[self.window.contentView addSubView: ss];
or maybe i have to say something like
[self iWannaBeANibWithName: #"StartScreen"];
in the constructor of StartScreen?
pls help...
(btw I am developing for Mac OS X 10.6)
One option is to make StartScreen a subclass of NSViewController, maybe changing its name to StartScreenController. This is a potentially more modular solution in case you have IBActions in your nib file and/or you want to place view controlling code in its own class.
Declare StartScreenController as a subclass of NSViewController
Declare IBOutlets in StartScreenController if needed
Set the nib file’s owner class to be StartScreenController
Connect the file’s owner view outlet to the view object, and other outlets if needed
Then:
StartScreenController *ss = [[StartScreenController alloc] initWithNibName:#"nibname" bundle:nil];
[self.window.contentView addSubView:ss.view];
…
If you’re not using garbage collection, don’t forget to release ss when it’s not needed any longer.
The Nib loading functions are part of the NSBundle class. You can use it like this...
#implementation StartScreen
- (id) init {
if ((self = [super init])) {
if (![NSBundle loadNibNamed:#"StartScreen" owner:self])
// error
// continue initializing
}
return self;
}
See NSBundle Additions reference.
After always running monolithic blocks of code from within my AppController object, I've just learned how to modularize my code using separate model controller objects (so much neater and less confusing to work with :-))
My simple test app has a main AppController and two model objects (Model1 and Model2). I can successfully pass data from the AppController to the models, and the models themselves can run methods and process the passed data 'internally' as they were intended to do -- but I can't get them to communicate with a darned NSTextField in the UI. Here's the relevant parts of my code:
In AppController.m #import "AppController.h"
#implementation AppController
- (IBAction)passObjectsToModelController:(id)sender
{
NSString *stringToPass = #"Hello from Model2 :-)";
int numToPass=12345;
Model2 *ObjController2 = [[Model2 alloc]initWithStuff:stringToPass:numToPass];
[ObjController2 release];
}
#end
...in Model2.h
#import
#interface Model2 : NSObject
{
IBOutlet NSTextField *passedStringField;
}
- (id)initWithStuff:(NSString*)passedString :(int)passedNum;
#end
...and finally in Model2.m
#import "Model2.h"
#implementation Model2
- (id)initWithStuff:(NSString*)passedString :(int)passedNum
{
if(self = [super init])
{
NSLog(#"now inside 'Model2' controller...");
NSLog(#"the passed string reads: %#",passedString); //••• this works •••
NSLog(#"the passed number is:%d",passedNum); //••• this works •••
[passedStringField setStringValue:passedString]; //••• WTF!!... this DOESN'T work! •••
// do something internally with passedNum here...
}
return self;
}
#end
Both model objects have outlets to the common NSTextField and I've control-dragged from both objects to the field and connected them. My AppController doesn't know about the NSTextField (and I assume, doesn't even want to know). No IB connections have been made between the controller object and model objects.
NSLog tells me that the model objects are being created, and that the passed values are making it that far... but not from there into the text field in the GUI window. I'm not getting any compiler errors or warnings. Am I missing some kind of 'setTarget:' call perhaps?
Any help/ideas would be much appreciated. Thanks :-)
Aside from the lack of MVC that mihirsm mentions, the actual problem is that you're trying to access an outlet in an -init method.
When a object is initialized, outlets are not guaranteed to be connected.
If you want to set the value of an NSTextField declared as an outlet, you should implement -awakeFromNib, which is called when the nib has been loaded and all outlets are guaranteed to be live.
in Model1.h:
#interface Model1 : NSObject
{
IBOutlet NSTextField* passedStringField;
NSString* modelString;
}
- (id)initWithString:(NSString*)passedString number:(int)passedNum;
#end
in Model1.m:
#implementation Model1
- (id)initWithString:(NSString*)passedString number:(int)passedNum
{
if(self = [super init])
{
//copy the string to our ivar
modelString = [passedString copy];
}
return self;
}
//awakeFromNib is called when our outlet is live
- (void)awakeFromNib
{
[passedStringField setStringValue:modelString];
}
//don't forget to release the string, because we created it using -copy
- (void)dealloc
{
[modelString release];
}
#end
The Controller sits between the Model and the View. The Model should not communicate with the View.
It should be the job of the Controller to pass any incoming values from the View to the Model. The Model then processes the data and sends back to the Controller which then updates the View with the new data.
So, in your code you should only have one IBOutlet for the TexField declared in the AppController.
Given all this, I am not exactly sure why the TextField is not being updated. From the given code looks like it should. Maybe multople IBOutlets are causing some issue? Can you try with only one Model having the IBOutlet?