2

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!

elitalon
  • 9,191
  • 10
  • 50
  • 86
  • How is filename defined? A declared property or a custom accessory? – logancautrell Oct 22 '11 at 20:45
  • `filename` is defined like `@property (strong, nonatomic) NSString *filename;` in `@interface` declaration of `WallpaperDetailsViewController` – elitalon Oct 22 '11 at 20:48
  • 2
    something is happening in your init code that is causing the view to get loaded. set a break point in view did load and see why it is getting called early. – logancautrell Oct 22 '11 at 21:11

4 Answers4

2

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

elitalon
  • 9,191
  • 10
  • 50
  • 86
  • Glad you figured it out. I guess my answer was kind of an aside, but it was clear that the solution lay in code other than what you had posted. – jscs Oct 22 '11 at 22:40
1

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.

jscs
  • 63,694
  • 13
  • 151
  • 195
  • Actually I thought that, but it doesn't work for me in this case. That's why I've hitting my head against a wall before asking here. – elitalon Oct 22 '11 at 21:34
  • The `UIImageView` is not created properly, because `viewDidLoad` is called just before setting `self.filename = aFilename`. – elitalon Oct 22 '11 at 21:41
  • Well, I guess that makes sense, since in this case `[super initWithNibName:@"..." bundle:nil];]` is exactly equivalent to calling that on `self`. – jscs Oct 22 '11 at 21:45
0

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.

Flyingdiver
  • 2,142
  • 13
  • 18
0

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!

Community
  • 1
  • 1
timthetoolman
  • 4,613
  • 1
  • 22
  • 22
  • 2
    There's no need for creating the image view in the view controller's intializer method. In fact, that could cause trouble should the VC receive a memory warning. Best to stick to the lazy-loading paradigm and only allocate it when needed, in `loadView:` or `viewDidLoad:` – jscs Oct 22 '11 at 21:35