UITableViewController init method not called - objective-c

I've created an iPad application with a UITableViewController in the UISplitViewController (and everything works :) Since I'd like the table to use UITableViewStyleGrouped, i added:
- (id)init
{
self = [super initWithStyle:UITableViewStyleGrouped];
if (self != nil) {
// Initialisation code
}
return self;
}
to the root view controller & included it in the .h, but it's never getting called. So, two questions, can I set UITableViewStyleGrouped for a table in a UISplitViewController? And, if so, how?

init won't be called, initWithStyle: or initWithFrame:style: might be called.
edit: waychick was right. - (id) initWithCoder:(NSCoder *) coder is called.

Same problem. In my case
- (id) initWithCoder:(NSCoder *) coder
is called

Related

Custom initialisation while using storyboard

Prior to storyboards, and working with .xib files, I used this piece of code to do screen adjustments during init.
- (id)initForNewItem:(BOOL)isNew {
self = [super initWithNibName:#"NAME" bundle:nil];
if (self) {
if (isNew) {
// Place some buttons only when isNew is true
}
}
return self;
}
Then I also implemented this to generate an exception when initWithNibName is directly called because I wanted to avoid that:
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
#throw [NSException exceptionWithName:#"Wrong initializer" reason:#"Use initForNewItem:" userInfo:nil];
return nil;
}
Then another viewcontroller could call the custom init and the screen would be set:
MyViewController *myViewController = [[MyViewController alloc] initForNewItem:YES]; // Or NO ofcourse depending on the situation.
Now I'm using storyboard and initWithNibName is never called. Instead only initWithCoder is called but this method can only be called by the storyboard right? So how would I do something similar while using storyboard?
You wouldn't do this with storyboards.
The way to do it is to use properties to give the correct values once the view controller has been initialised.
This is done in - (void)prepareForSegue:(UIStoryBoardSegue *)segue sender:(id)sender.
You can access segue.destinationViewController and then use this to put values in to.

NSViewController New vs. InitWithNibName issues

I am having a weird error with NSViewController where if I allocate a view using the viewcontroller's regular init message, the view created is not my view, but when using the default NIB name, it does work.
Specifically, this code works all the time. It creates the view defined in the nib file, and displays it in the parentView.
+ (void)createTransparentViewCenteredInView:(NSView*)parentView withText:(NSString*)text duration:(NSTimeInterval)duration {
TransparentAccessoryViewController* controller = [[TransparentAccessoryViewController alloc] initWithNibName:#"TransparentAccessoryViewController" bundle:nil];
NSLog(#"%#", [controller.view class]); // Returns "TransparentAccessoryView" -- CORRECT
[parentView addSubview:controller.view];
}
However, the following code works SOME of the time (which is weird in that it doesn't always fail). With some parentViews, it works perfectly fine, and with others, it doesn't. The parent views are just random custom NSViews.
+ (void)createTransparentViewCenteredInView:(NSView*)parentView withText:(NSString*)text duration:(NSTimeInterval)duration {
TransparentAccessoryViewController* controller = [TransparentAccessoryViewController new];
NSLog(#"%#", [controller.view class]); // Returns "NSSplitView" -- INCORRECT
[parentView addSubview:controller.view];
}
The errors that comes up are as follows (I have no idea why it is bringing up an NSTableView, as I don't have an NSTableView here at all. Also, it is weird that it complains about an NSTableView when the type it prints is an NSSplitView):
2013-04-07 21:33:12.384 Could not connect the action refresh: to
target of class TransparentAccessoryViewController
2013-04-07 21:33:12.384 Could not connect the action remove: to target
of class TransparentAccessoryViewController
2013-04-07 21:33:12.385 * Illegal NSTableView data source
(). Must implement
numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:
The NIB file defines a custom subclassed NSView, called TransparentAccessoryView, and hooks this up to the File Owner's view property, standard stuff (all I did was change the custom class name to TransparentAccessoryView). I added an NSLog's to see what was going on, and for some reason, in the second case, the view class type is incorrect and thinks it is an NSSplitView for some reason. The ViewController class is as follows:
#implementation TransparentAccessoryViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
}
return self;
}
- (void)awakeFromNib {
self.textField.stringValue = #"";
}
+ (void)createTransparentViewCenteredInView:(NSView*)parentView withText:(NSString*)text duration:(NSTimeInterval)duration {
TransparentAccessoryViewController* controller = [[TransparentAccessoryViewController alloc] initWithNibName:#"TransparentAccessoryViewController" bundle:nil];
NSLog(#"%#", [controller.view class]);
[parentView addSubview:controller.view];
}
#end
I thought that the default init message triggers the viewcontroller to load the NIB named after the viewcontroller, which seems to be the case some of the time as the second version of my code works in certain conditions.
Does anyone know why this behavior is occurring at all?
From the docs:
If you pass in a nil for nibNameOrNil then nibName will return nil and
loadView will throw an exception; in this case you must invoke
setView: before view is invoked, or override loadView.
Therefore, if you're initializing a NSViewController with -init, you should call -setView: to set the view controller's view, or override -loadView. In the latter case, you could certainly implement the UIViewController-like behavior that you're probably expecting -- if nibNameOrNil is nil, try to load a nib that has the same name as the class.
I think that when you call init on a NSViewController, you're assuming that the implementation of init for NSViewController searches for a nib with the same name as the view controller and uses it. However, this is undocumented API or at least I can't seem to find any documentation supporting that assumption. The link you posted on your comments doesn't cite any documentation either and even reiterates that this is undocumented and that Apple could change this implementation at any point.
I think to assure that your code works in future versions of the SDK (and since it is already creating undesired behavior), you should not rely on this assumption. To achieve the same outcome simply override init and initWithNibName:bundle: in such a way as explained by this post:
#implementation MyCustomViewController
// This is now the designated initializer
- (id)init
{
NSString *nibName = #"MyCustomViewController";
NSBundle *bundle = nil;
self = [super initWithNibName:nibName bundle:bundle];
if (self) {
...
}
return self;
}
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
// Disregard parameters - nib name is an implementation detail
return [self init];
}

Objective-C how to call a method after self is initialized inside the object file?

Is there any way to know when a custom object is finished with being initialized from inside the object's file? Or let me rephrase the question, why can't I call any method inside this method?
- (id)initWithCoder:(NSCoder *)coder {
//NSLog(#"initWithCoder inside CustomObject (subclass of UIView)");
self = [super initWithCoder:coder];
if (self) {
//... initialization here
[self visibleEmptyButton]; //why does this method never get called?
}
return self;
}
EDIT:
-(void)viewDidLoad{
NSLog(#"viewDidLoad inside CustomObject(subclass of UIView) is called"); //It never gets called
[self viewDidLoad];
//initialization here...
}
(If the class you are init-ing is a subclass of UIViewController) Changing and setting things in the screen should be done after the view is loaded. Try doing it in this method:
- (void)viewDidLoad {
[super viewDidLoad];
[self visibleEmptyButton];
//Do the additional view altering here
}
If this method doesn't exist yet you can just add it to the .m file (no need to add it to the .h file).
In lieu of you're edit you could simply move the call to the UIViewController:
- (void)viewDidLoad {
[super viewDidLoad];
[TheInstanceOfYourViewClass visibleEmptyButton];
}
Also, to avoid making a whole bunch of small subview related methods public it often makes sense to create one method to handle the initial visual states.

How to use NSWindowController to show a window in standard application?

I created a new blank standard application using Xcode template. Removed the window in MainMenu.xib and I created a new customized NSWindowController subclass with a xib.
They were named "WYSunFlowerWindowController.h" and "WYSunFlowerWindowController.m".
And I append then init function like below:
- (id)init
{
NSLog(#"init()");
return [super initWithWindowNibName:#"WYSunFlowerWindowController" owner:self];
}
And my WYAppDelegate.m file is like below:
static WYSunFlowerMainWindowController* windowController = nil;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
if (windowController == nil) {
windowController = [[WYSunFlowerMainWindowController alloc] init];
}
[[windowController window] makeKeyAndOrderFront:windowController];
}
And I have the problem, that the window can't show it self after I launch the app. Anyone can tell me why? Is anything wrong with my code?
I am a newbie in Objective-C and cocoa. So I think I maybe make a silly mistake that I can't figure it out by myself.
UPDATE:
Here is my project source. Pleas have a look and help me to figure out what is my mistake。
https://dl.dropbox.com/u/3193707/SunFlower.zip
In your init method, I think you have to set self to the super init first before you return self.
-(id)init
{
NSLog (#"init()");
self = [super initWithWindowNibName:#"WYSunFlowerWindowController" owners:self];
return self;
}
Edit:
Try replace makeKeyAndOrderFront: with [windowController showWindow:self]
Then if that still doesn't work, check your window controller xib, make sure the file owner is set to WYSunFlowerWindowController and that the IBOutlet Window (declared in NSWindowController) is connected to the window.
Edit 2:
Commenting out your #property and #synthesize window in your controller was the trick. Don't redeclare get and setters that were already predefined in a superclass.

How to write a custom initializer that prevents viewDidLoad being called?

I would like to build a custom init method for a UIViewController, but after digging around on the Internet and specifically in SO I am confused about designated initializers.
I have a subclass of an UIViewController with these two initializers:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if ( self ) {
}
return self;
}
- (id) initWithFilename:(NSString *)aFilename {
self = [self initWithNibName:#"WallpaperDetailsViewController" bundle:nil];
if ( self ) {
self.filename = aFilename;
}
return self;
}
Then I have a viewDidLoad method that customizes the view according to the filename property:
- (void)viewDidLoad {
[super viewDidLoad];
// Create a UIImageView to display the wallpaper
self.wallpaper = [[UIImageView alloc] initWithImage:[UIImage imageNamed:self.filename]];
// ...
}
In another UIViewController I make the following call:
WallpaperDetailsViewController *detailsViewController = [[WallpaperDetailsViewController alloc] initWithFilename:#"foobar.png"];
[[self navigationController] pushViewController:detailsViewController animated:YES];
The result is that viewDidLoad is being called as a consequence of [self initWithNibName:], which does not initialize the UIImageView because self.filename is null.
According to other SO questions and answers, that should be the expected behavior. I am not sure about this because of my own experience in other projects prior to iOS 5. My question is:
How can I ensure that viewDidLoad: is call after initWithFilename: and not between initWithFilename: and initWithNibNameOrNil:bundle:?
If that's not possible, how can I implement an initializer method that receives custom data to create and customize the view?
Thanks!
I have found the problem.
WallpaperDetailsViewController does not inherit directly from UIViewController, but from another custom UIViewController I have implemented.
And what was the problem? That I have initialized a subview in the parent's initWithNibName method, instead of following the lazy-load technique and doing it in viewDidLoad. When WallpaperDetailsViewController was calling its parent initializer it got messy and cause viewDidLoad not to behave properly.
The solution? I moved every subview initialization in the parent class to its viewDidLoad method, and keep my original implementation of WallpaperDetailsViewController intact. Now everything is working as expected
Thanks to #Josh Caswell and #logancautrell
You don't need that empty implementation of initWithNibName:bundle:. Furthermore, it looks like your class here is establishing its designated initializer to be initWithFilename: If that's true, initWithFilename: should be calling the superclass's D.I.:
- (id) initWithFilename:(NSString *)aFilename {
// Call super's designated initializer
self = [super initWithNibName:#"WallpaperDetailsViewController"
bundle:nil];
if ( self ) {
self.filename = aFilename;
}
return self;
}
The rule is that all initializers within a class should call the class's D.I., and the D.I. should itself call the superclass's D.I.
It's not completely clear from what you've posted why loadView: is being called before your initializer has completed. Logancautrell's comment suggesting setting breakpoints in the view loading methods is good.
Why don't you just use a custom setter for the filename property that initializes the UIImage every time the filename is set?
Or, alternately, set the UIImage from the filename property in viewWillAppear: instead of viewDidLoad.
First, it is not recommended that you use dot syntax within your initializer. See the following for some good discussion:
Objective-C Dot Syntax and Init
Second, what you could do is assign the image in your initializer as well. So you could do something along the lines of
- (id) initWithFilename:(NSString *)aFilename {
self = [self initWithNibName:#"WallpaperDetailsViewController" bundle:nil];
if ( self ) {
filename = [aFilename retain];
wallpaper = [[UIImageView alloc] initWithImage:[UIImage imageNamed:aFileName]];
}
return self;
}
This will allow you to get everything setup and in good shape before viewDidLoad is called.
Good Luck!