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

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
}

Related

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

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.

Where can I declare custom initialization for custom table cells?

If I have a custom table cell that is used by:
tableView dequeueReusableCellWithIdentifier
Where inside of that custom cell can I set up custom initialization parameters? The auto-generated .m file for my custom table cell included:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
NSLog(#"INITWITHSTYLE!");
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
I added the NSLog call to see if that initWithStyle is ever called, but that NSLog is never reached, which means that that particular initWithStyle is also never called. So when a custom table cell is initialized using dequeueReusableCellWithIdentifier, which init method of that custom cell is actually used?
Your cell will get initialized once in -(void)awakeFromNib (even when it's from a Storyboard).
Then, there's no way to know where it will be dequeued, but it can receive a message when it's removed from screen and enqueued to the reusable cells pool: at this time - (void)prepareForReuse is called.
If you use storyboard you can use awakeFromNib, otherwise the initWithStyle:reuseIdentifier: should be called
- (void)awakeFromNib {
}
But you should remember that the cell will be reused so let's say if your cell is called first time awakeFromNib is called but later on the cell could be reused and this method won't be called again, it this scenario, if it doesn't work for you, you can use custom setter when you pass the data
//Extended
In comment you asked what method is called when the cell is reused.
You can use prepareForReuse, please read discussion (taken from Apple)
- (void)prepareForReuse
Discussion If a UITableViewCell object is reusable—that is, it has a
reuse identifier—this method is invoked just before the object is
returned from the UITableView method
dequeueReusableCellWithIdentifier:. For performance reasons, you
should only reset attributes of the cell that are not related to
content, for example, alpha, editing, and selection state. The table
view's delegate in tableView:cellForRowAtIndexPath: should always
reset all content when reusing a cell. If the cell object does not
have an associated reuse identifier, this method is not called. If you
override this method, you must be sure to invoke the superclass
implementation.

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
}
}

Cocoa - awakeFromNib is not called

I have an MainMenu.xib, and AppController is its file owner. I added -(void)awakeFromNib method which worked fine. Now, rounds of fixings down the road awakeFromNib stopped being called, and I can't figure out why. It owns the xib, so it should be called when it is unarchived. What's going on?
EDITED:
Well, I renamed awakeFromNib to something, and called that from init... that worked. Still confused as to why awakeFromNib is not. I also have a +(void) initialize method in there, could that be messing something up?
- (id)init {
self = [super init];
if (self) {
[self something];
}
return self;
}
-(void)something {
NSLog(#"yup");
}
Setting the class name of the File's Owner in the nib is only so you can tell Xcode what object's outlets and actions to show you so you can hook things up. It doesn't affect what object is actually the File's Owner when the app runs and the nib gets loaded.
The MainMenu nib's File's Owner is always the application object, no matter what class name you set for the FO in Xcode's inspector. Setting it to any class name but NSApplication[1] is wrong.
When you run your app, you should find error messages in the Console about any outlets or actions of the AppController that you tried to connect. They couldn't be connected because the application object doesn't have them.
Change the class name back in the nib editor, and create your AppController as a custom object in the MainMenu nib.
Well, I renamed awakeFromNib to something, and called that from init... that worked.
That means that init is getting called, which means you're calling it. That's a valid alternative to creating it in a nib, though you shouldn't override awakeFromNib if it's not in a nib or owning one.
Your choice: Continue creating the AppController using alloc and init, or remove that code and create it in the MainMenu nib instead.
[1]: Or, if you've subclassed NSApplication and changed the principal class of your app bundle to be that subclass, the name of that subclass.

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.