UIViewController: [super init] calls [self initWithNibName bundle:] - objective-c

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

Related

Equivalent Storyboard function for Nib

I'm trying to convert a project on macOS that uses Storyboards to instantiate a ViewController through a delegate, although I'm running into some difficulty trying to convert it to use a Nib instead.
Currently the storyboard version of the code uses an App Delegate which is associated with two View Controllers. When a button is clicked the front window animates and flips over revealing another (back) window. The code that instantiates the View Controller is:
mainWindow = [NSApplication sharedApplication].windows[0];
secondaryWindow = [[NSWindow alloc]init];
[secondaryWindow setFrame:mainWindow.frame display:false];
// the below is what I'm not sure of - how to reference nib instead of storyboard?
NSStoryboard *mainStoryboard = [NSStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
NSViewController *vc = [mainStoryboard instantiateControllerWithIdentifier:#"BackViewController"];
[secondaryWindow setContentViewController:vc];
I'm not sure the proper way to reference a nib instead of a storyboard in the example shown above. The project I'm trying to convert is located here. I'm really hoping someone can help, thank you!
This is pretty easy to do. Just make an NSViewController subclass (or an NSWindowController subclass if you want it to control a whole window) for each of the two views. For each view, override -init and have it call super's implementation of -initWithNibName:bundle: with the name of the view's nib file:
#implementation MyViewController
- (instancetype)init {
self = [super initWithNibName:#"MyViewController" bundle:nil];
if (self == nil) {
return nil;
}
return self;
}
Note that if you can require a sufficiently recent version of macOS (I think it's 10.11 and higher off the top of my head, but it's possible that I could be off by a version or so), you don't even have to do this much, because NSViewController will automatically look for a nib file with the same name as the class.
Anyway, now you should be able to instantiate a MyViewController and insert its view into your view hierarchy, and manipulate it the same way you'd manipulate any other view:
MyViewController *vc = [MyViewController new];
[someSuperview addSubview:vc.view];
If you want to do windows instead, you can make an NSWindowController subclass instead of NSViewController. NSWindowController is slightly more annoying to use, since its initializers that take nib names are all convenience initializers, whereas the designated initializer just takes an NSWindow. So if you're using, say, Swift, you can't do it the way I did it above with NSViewController. Objective-C, of course, generally lets you do whatever you want, so you actually can get away with just calling super's -initWithWindowNibName:owner:, and I won't tell anyone, wink wink, nudge nudge. However, to be stylistically correct, you probably should just call -initWithWindow: passing nil, and then override windowNibName and owner:
#implementation MyWindowController
- (instancetype)init {
self = [super initWithWindow:nil];
if (self == nil) {
return nil;
}
return self;
}
- (NSNibName)windowNibName {
return #"MyWindowController";
}
- (id)owner {
return self;
}
This should get you a window controller that you can just initialize with +new (or +alloc and -init if you prefer), then call its -window property and manipulate the window as normal.

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.

initWtihFrame: method for custom textfield not being called when created in a TableCellView

So I'm trying to create a custom NSTableView by subclassing NSTableCellView and a NSTextField inside of the cellview. I'm trying to write some init code in the initWithFrame: method for the NSTextField subclass (TableTextField), but it looks like the initWithFrame: method isn't being called at all, even when I create new rows for the NSTableView, which each contain an instance of the TableTextField. Here's the code in the TableTextField.m file:
#import "TableTextField.h"
#implementation TableTextField
- (id)initWithFrame:(NSRect)frame//This isn't even being called.
{
self = [super initWithFrame:frame];
if (self) {
self.ad = [[NSApplication sharedApplication] delegate];
}
return self;
}
- (void)mouseDown:(NSEvent*) theEvent{
NSLog(#"A");//TEST
self.ad = [[NSApplication sharedApplication] delegate];//This works. Why isn't it happening in init?
[self.ad.classViewController addHomeworkItem];
}
#end
I've been able to solve the problem by just putting the code i need in the mouseDown: method, which is really the only thing I need, but I feel like it's bad practice to redeclare self.ad every time i need to use it, rather than just declaring it once and accessing it for each use, and I can't seem to figure out why initWithFrame: isn't being called. I'm assuming it has to do with the way objects inside NSTableCellViews are initialized, but I haven't found a real explanation. Any suggestions on how to solve this, or explanations as to why it's happening would be welcome. Thanks!
Here's an image of how the NSTableView is set up (with 3 rows):
If you are using XIB files to layout the views, the initialiser method being called is initWithCoder:

The right way of setting up MapKit's delegate in a separate class

What is the proper way of setting up a separate delegate class for MapKit?
I have MapView class subclassing MKMapView and bare MapDelegate class conforming MKMapViewDelegate protocol having only one initializer method.
Here is the extract from MapView initialization method I use:
# MapView.m ...
#implementation MapView
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// [self setShowsUserLocation:YES];
[self setDelegate:[[MapDelegate alloc] initWithMapView:self]];
The only method MapDelegate class has is
# MapDelegate.m ...
- (id)initWithMapView:(MapView *)aMapView {
self = [super init];
self.mapView = aMapView;
return self;
}
Having [self setShowsUserLocation:YES]; commented, all works fine - I see the map. If I uncomment this line, my application begins to crash.
What my MapDelegate class is missing?
UPDATE 1: if I don't use a separate class MapDelegate and set just setDelegate:self - all works.
UPDATE 2: Now I understand, that the problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that I need MapDelegate class to live longer than it does now (delegate property has weak attribute). If I do the following:
#property (strong) id delegateContainer;
....
[self setDelegateContainer:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateContainer];
...it works! Is there a better way of retaining MapDelegate life cycle along with the one of MKMapView?
Thanks!
After waiting enough for any answers that could appear here and ensuring original problematic behavior twice more times, I am posting my own answer based on the second update from the question:
The problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that MapDelegate class should be able to be kept alive outside of the scope of question's initWithFrame method because delegate property has weak attribute. The possible solution is to create an instance variable serving as a container for a delegate class, for example:
#property (strong) id delegateClass;
....
[self setDelegateClass:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateClass];
This solves the original problem.
LATER UPDATE
Though it is possible to set MKMapView's delegate in a separate class, I now realize that such model should not be used:
Currently I always prefer to use my controllers (i.e. controller layer in MVC in general) as delegates for all of my View layer classes (map view, scroll view, text fields): controller level is the place where all the delegates of different views can meet - all situated in controller layer, they can easily interact with each other and share their logic with the general logic of your controller.
On the other hand, if you setup your delegate in a separate class, you will need to take additional steps to connect your separate delegate with some controller, so it could interact with a rest part of your logic - this work have always led me to adding additional and messy pieces of code.
Shortly: do not use separate classes for delegates (at least view classes delegates provided by Apple), use some common places like controllers (fx for views like UIScrollView, MKMapView, UITableView or models like NSURLConnection).
I think viewDidLoad would be a better place to set up the map view. It's just a guess, but perhaps the crash is due to the view not being loaded yet.
Of course subclassing MKMapView isn't recommended at all. You would generally put your map as a subview, and set the main view to be the delegate. From the docs:
Although you should not subclass the MKMapView class itself, you can get information about the map view’s behavior by providing a delegate object.
Finally, if you really want to have a separate delegate class, you don't need to set its mapView, as all delegate methods pass the map as an argument.

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
}