Objective C : Custom class in identity inspector creates a new object of that class? - objective-c

In a storyboard when I add a new view (for example a TableView) I can select a class in the "Custom class" field in the identity inspector.
If I understand the rule of this class, I expect this class "answer" to messages sent to my tableview (i.e. this class is my table viewcontroller) and when I run my project it seems to do what I want.
My question is: To do this, I expected my Xcode automatically instantiates an object of my controller class and "link" this object to my GUI in storyboard.
However, I expected that if I override the init method of my controller class with
-(id) init
{
self=[super init];
NSLog(#"object controller created automatically");
return self;
}
I have the string in output when is created my controller object.
Instead, I have no output.
Why is this happenig and what is wrong with the code?

UIView set up by storyboard never called init.
Instead, you should use - (void)awakeFromNib in which your outlet has been ready to use.
- (void)awakeFromNib {
[super awakeFromNib];
NSLog(#"object controller created automatically");
}
From awakeFromNib documentation:
Objects that conform to the NSCoding protocol (including all subclasses of UIView and UIViewController) are initialized using their initWithCoder: method. All objects that do not conform to the NSCoding protocol are initialized using their init method.

If I understand you question you want a message to be printed whenever your viewController is initialised.
Why dont you write the code in the viewDidLoad?
Like:
In your YourControllerClass.m
-(void)viewDidLoad {
[super viewDidLoad];
NSLog(#"Controller created");
}
Now set the class of the controller in the storyboard to YourControllerClassand the message should be printed whenever your controller is created.
Cheers
P.s.: If you still need help or got a question, please write a comment.

Related

UIViewController: [super init] calls [self initWithNibName bundle:]

I have two init functions in my UIViewController subclass:
- (id)init
{
self = [super init];
if (self)
{
// Custom stuff
return self;
}
return nil;
}
and
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName: nibNameOrNil
bundle: nibBundleOrNil];
if (self)
{
// Custom stuff
}
return self;
}
I put the init function in to avoid the call to the initWithNibName:bundle: method. I am trying to experiment with taking the xib file out. Unfortunately, calling this init [[Myclass alloc] init] calls initWithNibName:bundle: through the call to [super init].
First, where in the documentation should I be reading so that I would have expected the call to the parent init method to call my own initWithNibName:bundle: method?
Second, how is this a good design choice on Apple's part. I am not seeing why this is desirable behavior? (It may be that I am just not getting the big picture here so please feel free to clue me in.)
Third, how do I get around it best. Do I just take the initWithNibName:bundle: out of my code? Is there never a case where I would like the option of using either a xib or a manual instantiation of the class.
Usually I have to initialise my view controllers with managed object context. I implement simple -(id)initWithContext: method in which I call super's initWithNibName:bundle: method. This way I can define my own xib name.
Not sure about the first part of you question (the reading thing, that is), but Apple's class templates for VC's show that they have their own initWithNibName:bundle method which calls on super with same parameters as they are given. Hence from your situation I'd say that exactly this is designated initialiser and it's not "safe" to call simple init method on super as it will invoke initWithNibName:bundle. I believe UIViewController's init looks like this:
- (id)init
{
self = [self initWithNibName:nibNameDerivedFromClass bundle:probablyNilOrMainBundle];
if (!self) return nil;
// some extra initialization
return self;
}
Since the super class doesn't have initWithNibName:bundle it has to call method on itself making it the designated initialiser. Since you have overridden it, ObjC's runtime replaces self in that method with your class.
If you want to exclude Interface Builder from the creation of your UIViewController's GUI, you have to override loadView and create the view yourself. Don't implement initWithNibName:bundle:.
- (void)loadView {
// Init a view. The frame will be automatically set by the view controller.
UIView *view = [[UIView alloc] initWithFrame:CGRectZero];
// Add additional views (buttons, sliders etc.) to your view here.
// Set the view controller's view to the new view.
self.view = view.
}
First, where in the documentation should I be reading so that I would
have expected the call to the parent init class to call my own
initWithNibName:bundle: method?
You don't, it's a design thing. init is not the base initialization method for all classes
Second, how is this a good design choice on Apple's part. I am not
seeing why this is desirable behavior? (It may be that I am just not
getting the big picture here so please feel free to clue me in.)
When init calls that, it sends nil name and bundle, and it defaults to an empty xib file. There's always a xib file, yours or not.
Third, how do I get around it best. Do I just take the
initWithNibName:bundle: out of my code? Is there never a case where I
would like the option of using either a xib or a manual instantiation
of the class.
You don't. You don't really need to have that code there if you are just calling super, merely forwarding a method.
You can customize your view and add subviews in the viewDidLoad method. In this method you can check whether the class was created using init or using initWithNibName:bundle: by examining property nibName. When using init, nibName will be nil.
- (void)viewDidLoad
{
[super viewDidLoad];
if (!self.nibName) {
// View was not loaded from nib - setup view
}
}

Delegating Outline View's Data Source To Separate Object

I want to be able to use a blue object box to delegate control over an NSOutlineView. The blue object box would be hooked up to my primary controller, so it'd just be a data source and control the content of the NSOutlineView.
The problem I'm having is that I have no control over the Channel Data Source. I'm simply calling a declared method with some test NSLog inside of it, and it doesn't get called. The outlet doesn't get instantiated.
Here's the connections of the blue object box (ChannelDataSource)
Here's the connections of File's Owner for my primary controller.
So you see, I want to do something like [dataSource callMyMethod]; with the final aim that I have control over the contents for the NSOutlineView.
Any ideas?
EDIT
The application is structured whereby my primarily app delegate looks like this:
#implementation MyAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
controller = [[MainController alloc] init];
[controller showWindow];
}
#end
Then in the MainController I have something along the following lines:
#implementation MainController
-(id)init {
self = [super init];
if (self) {
// loads of random stuff
[dataSource myMethod];
}
return self;
}
So "Channel Data Source" blue object box is dataSource. At this point in the application life cycle, it's null, which isn't what I was expecting. At the same time, it's still a bit of black magic to me. If you have a blue object box, at what point is it instantiated? Obviously this isn't hooked up correctly though.
EDIT EDIT
Further to my points above, and trying to fix the problem, is this actually a good way to go about it? I'm looking at this thinking it's not meeting a decent MVC architecture, because ultimately the blue object box's owning class is storing and managing the data. Is there a better way to go about managing what's in your NSOutlineView?
EDIT EDIT EDIT
So I have my app delegate, which is strangely a class all by itself that instantiates the main controller. Don't ask me why I did this, it was very early code. So my app delegate (root entry point) has this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
controller = [[MyController alloc] initWithWindowNibName:#"MainWindow"];
[controller showWindow:nil]; // this doesn't open the window
[controller loadWindow]; // this does open the window
}
And the declaration of the controller:
#interface MyController : NSWindowController
Which contains the following method declaration in it:
-(void)windowDidLoad {
[dataSource insertChannel:#"test" forServer:#"test2"];
}
I have a breakpoint in windowDidLoad and it definitely doesn't get called.
Ideas?
There's still a few things you didn't clarify, but I can do some guessing. First, I'm assuming that MainController is a subclass of NSWindowController. If so, you should be using initWithWindowNibName: instead of just init, otherwise how would the controller know what window to show when you address showWindow: to it? Second, even if you do that, and change your init method to initWithWindowNibNamed:, what your wrote won't work, because the init is too early in the process to see your outlet, datasource. If you just log dataSource it will come up null. A better place to put that code would be in windowDidLoad, as everything will have been set up by then (this will be called after showWindow:). So, in my little test project, this is what I did.
In the app delegate:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.cont = [[Controller alloc] initWithWindowNibName:#"Window"];
[self.cont showWindow:nil];
}
In the Controller.M I have this:
- (void)windowDidLoad {
NSLog(#"%#",self.dataSource);
[self.dataSource testMethod];
}
In IB, in the Window.xib file, I set the class of the file's owner to Controller, and the class of the blue cube to ChannelDataSource. EVerything was hooked up the same way you showed in your post.

why does the init-method of a custom nib-based table cell not get called

I have a nib-based table view cell which I created in Interface builder. I set the class of the table view cell to FooTableViewCell which extends from UITableViewCell.
In FooTableViewCell I override the init method like this:
-(id)init{
if ((self = [super init])){
// My init code here
}
return self;
}
I now expected that my gets called, when it is being instantiated. However the table view gets displayed but the method is never called.
I could work around this but I would like to fully understand it and for me it's not clear how an object can come to live without the init method being called.
When being unarchived initialization goes through a slightly different path.
Instead of -(id)init being called -(id)initWithCoder:(NSCoder*)aDecoder will be called. At this point outlets aren't hooked up, if you need access to the outlets you can override -(void)awakeFromNib which will be called after the object has been hooked up.
When object is being loaded from nib file then its -awakeFromNib method is called - you can put your initialisation code there:
- (void)awakeFromNib{
// Init code
}

Assign button from another class to a current class selector with cocoa

I instantiate a class and then I try to change the selector of the instantiated class button:
WebViewController *newtab = [[WebViewController alloc] initWithNibName:#"NavigatorNoBottom" bundle:nil];
[[newtab tabsButton]setAction:#selector(addtabs:)];
The button tabsbutton is an outlet of the WebViewController class which is directly linked in interface builder.
The method -(void)Addtabs:(id)sender is a method in my current class.
But it seems that this code does not work, my button is here but it does nothing when I click on it .
If you need more context don't hesitate. I know this is something maybe very simple but I just bug at it....
Note that -initWithNibName:bundle: does not load the nib file immediately. This means that right after sending it you cannot expect your outlets to be set.
In that line where you send -setAction:, your tabsButton outlet is probably nil.
If WebViewController inherits from NSViewController, you can place that line that sets the action in the -loadView method of your WebViewController class to make sure the nib file has been loaded and all outlets are set:
- (void)loadView {
[super loadView];
[tabsButton setAction:#selector(addtabs:)];
// or [self.tabsButton setAction:#selector(addtabs:)];
}
Alternatively, if you don’t want to do this inside the view controller, you can do the following in an arbitrary class:
WebViewController *newtab = [[WebViewController alloc] initWithNibName:#"NavigatorNoBottom"
bundle:nil];
[newtab view];
[[newtab tabsButton]setAction:#selector(addtabs:)];
By sending -view to the view controller, you force it to load the nib file, hence the tabsButton outlet will have been set right after that.
Note that you can set the action in any class since a selector is not tied to a class. Also note that, since you haven’t set a target, the action will follow the action chain.

Update UI from another Class Method - Cocoa

I would like to update the UI in my application from the AppDelegate, but whenever I call it as so:
Controller *object = [[Controller alloc] init];
[object methodHere];
It doesn't seem to update the UI. What am I doing wrong here? I have put in a NSLog to see if it was being called, and it is. Here is a sample project that shows the error.
Edit: Can someone just show me what to change to the project I provided. I just don't know what to type into my project so that I can change the value of a simple NSTextField from another class.
When you write [[Controller alloc] init], you are not accessing the Controller object that is in your nib. You are creating a new Controller object that is unconnected to anything else in your application.
Remember, every Controller object is not the same any more than every NSArray is the same. Just because you made one Controller in your nib that's connected to an NSTextField does not mean some random Controller that you just created shares that controller's connections.
What you need to do is give the delegate a reference to the Controller that's in the nib.
This is really simple, and Chuck's comments basically explain what you need to do, but I will lay out the code explicitly for you. In testAppDelegate.h:
#interface testAppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
// You can make an IBOutlet to any kind of object you
// want; it's just a way for you to get a reference
// in code to an object that has been alloc'd and
// init'd already by the xib mechanism.
IBOutlet Controller *controller;
}
Then go into your xib in InterfaceBuilder and hook up that outlet from your Test App Delegate object to your Controller object (these objects are already present in the xib).
In testAppDelegate.m:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// This is the key:
// _Don't_ alloc/init a new controller object. The
// objects in your xib are allocated and initialized
// by virtue of being in that file. You just need to
// give your AppDelegate a pointer to it, as above.
[controller setTextValue:#"hello"];
}
It's being called all right, but it's not connected to the interface. There should be a view controller of some sort defined in your appDelegate.h file, call the method on that object instead.
Update for more detail:
One way you could pull this off would be to simply save the Controller when you originally create it (and not release it until later.)
Simply put your own controller object into your .h file
Controller* myController;
And when you create the new view controller you want to flip to, simply set myController to reference that object, and later when you want to update the UI, simply call
[myController methodHere];
A bit clumsy, but it works. Just don't forget to release myController when you're done with that view.
The other idea I'd suggest looking into would be to alter the method you're passing to your delegate. That is, instead of having the method as
-(returnType)callDelegateToDoSomething;
put it in as
-(returnType)callDelegateToDoSomething:(id) sender;
You call the new method the same way, but your controller should automatically pass itself as an argument. Then, inside the method, simply use
[sender methodHere];
and it should hopefully work. (You may need to play around with it a little. I'm not an expert on delegates or the sender argument, but it's worth a shot.)