UIViewController viewDidLoad called before init method is complete - ios

I have a view controller that initializes two other view controllers. The view for one controller wasn't showing, and I tracked the problem to the instance being nil when it's added to the superview.
Here is the code. viewDidLoad is being called before the favoritesTableVC is initialized. I can see this by placing breakpoints in the initialization methods of the resultsTableVC and favoritesTableVC view controllers.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
resultsTableVC = [[[ResultsTableVC alloc] initWithController:self andTableView:nil] retain];
favoritesTableVC = [[[FavoritesTableVC alloc] initWithFrame:CGRectMake(0, 10, self.view.frame.size.width, defaultFavoritesTableHeight) andController:self] retain];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:resultsTableVC.view];
[resultsTableVC release];
[self.view addSubview:favoritesTableVC.view];
[favoritesTableVC release];
}
Here is the order the methods are being called:
allResults init
resultsTableVC init
allResults viewDidLoad
addSubview allResultsVC
addSubview favoritesResultsVC
favoritesResultsVC init
This is a single thread, so I don't understand how viewDidLoad can be called before init is complete.

-[ResultsTableVC initWithController:andTableView:] is probably referencing allResults.view.
This would force the allResults controller to load its view (which then of course causes viewDidLoad to fire). All of this happens synchronously, before you actually return from initWithController:andTableView:

I'm taking a guess, but could you try this :
favoritesTableVC = [[[FavoritesTableVC alloc] initWithFrame:CGRectMake(0, 10, SOME_HARD_CODED_INT, SOME_HARD_CODED_INT) andController:self] retain];
And see if you get the same result.
My guess is that self.view is pointing to nil at that time.
But that wouldn't explain why the init is call after... but no harm in trying.
(I haven't tested it)

Related

Why we are using the method -(id)init and what are the uses?

While referring a sample code i found this snippet can any explain why it is used.
- (id)init
{
self = [super init];
if (self) {
[[self view]setBackgroundColor:[UIColor redColor]];
}
return self;
}
and what is the difference between the following snippet.
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
}
init and viewDidLoad both are completely different.
viewDidLoad called, when the view is loaded into memory, this method called once during the life of the view controller object. It's a great place to do any view initialization.
init method is an initializer method. Cocoa has various types of intializer. To learn more, please check the link,
https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/Initialization/Initialization.html

Dealloc not getting called

I am trying to debug why my override of dealloc is not getting called on one of my view controllers.
I have a view controller that is setup by storyboarding. I have override all 3 init methods:
-(id) initWithCoder:(NSCoder *)aDecoder {
NSLog(#"TestViewController init with coder");
return [super initWithCoder:aDecoder];
}
-(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
NSLog(#"TestViewController init with nib name");
return [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
}
- (id) init {
NSLog(#"TestViewController init");
return [super init];
}
When I navigate to the said view in my app I see that 'initWithCoder' is called as expected. This is the only init method that I see called at all. It is called once when I navigate to my view.
However when I navigate away from my view controller dealloc does not get called. viewDidDisappear does get called. In view didDisappear I log the reference count:
-(void) viewDidDisappear:(BOOL)animated {
NSLog(#"TestViewController Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef) self));
[super viewDidDisappear:animated];
}
Reference count is 5.
I also log the reference count in viewDidLoad and it is 8.
So it jumps from 5 to 8, then back to 5.
I only see the init method called once. I have no idea where to even start looking....
Do you have an instance variable pointing to your viewController object? If you still have a pointer it will not decrement the retain count even if the view is not on the screen.
The unusual retain counts are normal. The only count that matters is 0. If it is 0 it will be deallocated. There is no guarantee that one pointer means there will be a retain count of 1.

Setting delegate to MKMapView

I am new to iOS programming. I have created ViewController with MKMapView element, and I wanted to set delegate [mapView setDelegate:self]
First I done it in method initWithNibName:bundle: like:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[[self map] setDelegate:self]];
UITabBarItem *item = [[UITabBarItem alloc] init];
[item setTitle:#"Map"];
[self setTabBarItem:item];
}
return self;
}
In this case MKMapView did not send me any messages, but when I placed setting delegate message to viewDidLoad method, it worked fine.
Could someone explain me why it was not working when setting delegate message was in initWithNibName:bundle?
Views do not get loaded in initWithNibName, it just initializes your viewcontroller class and load the xib file which contains your view details.
When viewcontroller calls viewDidLoad, you will have all your view objects allocated and initialized.
In your case, when you setDelegate in initWithNibname, you are calling it on a nil value, so nothing get set, but in viewDidLoad mapView is allocated and initialized, so it works fine.
For a deeper insight refer:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html
Beautiful explanation here: What is the process of a UIViewController birth (which method follows which)?
Looking to understand the iOS UIViewController lifecycle
http://thejoeconwayblog.wordpress.com/2012/10/04/view-controller-lifecycle-in-ios-6/
This line is your problem:
[self map]
In initWithNibName the map is not yet initialized and it returns nil.
In viewDidLoad the map is already initialized.

proper way to set badge value on uninstantiated view controller?

I have 2 view controllers in a tab bar controller. My 2nd Nav Controller wants to set a badge value.
This controller is not loaded when the app starts, so the badge does not show. If I go over to that tab, the badge is properly updated.
this snippet runs when the tab's View Controller loads/reloads/updates/etc...
[self.navigationController.tabBarItem setBadgeValue:[NSString stringWithFormat:#"%u",[self.photos count]]];
Is the correct way to do this: Override the Nav Controller with a custom class and put the badge value in at that level? It seems like that is where I should put this info, but I haven't found a definite answer.
When the TabBarController is loaded, all of it's contained initial viewControllers are initialised. But their views are not loaded until you navigate to the respective tab item. So you can't execute code at this point in any of the view-loading methods (viewDidLoad etc). However you can execute code by overriding one of the initialisation methods.
If using storyboards the process of unarchiving the viewController triggers this method when the NIB has loaded:
- (void) awakeFromNib
{
}
If not using Storyboards, this initialiser is called prior to NIB loading:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
In either case you can override these methods to execute some code...
- (void) awakeFromNib
[super awakeFromNib];
[self.navigationController.tabBarItem
setBadgeValue:[NSString stringWithFormat:#"badgeValue"]]];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self.navigationController.tabBarItem
setBadgeValue:[NSString stringWithFormat:#"badgeValue"]]];
}
return self;
}
However you will need to take care where you are getting your data from. At this point self.photos may be uninitialised for example. If the data for this is coming out of userdefaults, you should be able to read those in here and set your badge accordingly.

CALayer delegation causes zombie crash -- why?

I am new to Core Animation and having trouble implementing a CALayer object with the drawLayer method in a delegate.
I have narrowed the problem down to a very simple test. I have a main viewController named LBViewController that pushes a secondary viewController called Level2ViewController. In the level 2 controller, in viewWillAppear:, I create a CALayer object with it's delegate=self (i.e. the level 2 controller). Whether or not I actually implement the drawLayer:inContext: method I have the same problem -- when I return to the main viewController I get a zombie crash. In the profiler it appears that the object in trouble is the level 2 viewController object -- which is being dealloc'ed after it's popped.
I've tried using a subclassed CALayer object instead of the delegate and it works fine. If I comment out the delegate assignment it also runs fine. I would like to understand why delegation is causing this problem. Any advice is greatly appreciated.
Here's my code ---
Level2ViewController
#implementation Level2ViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CALayer *box1 = [[CALayer alloc] init];
box1.delegate = self; // problem disappears if I comment out this assignment
box1.backgroundColor = [UIColor redColor].CGColor;
box1.frame = CGRectMake(10,10,200,300);
[self.view.layer addSublayer:box1];
[box1 setNeedsDisplay];
}
// makes no difference whether or not this method is defined as long
// as box1.delegate == self
- (void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext
{
CGContextSaveGState(theContext);
CGContextSetStrokeColorWithColor(theContext, [UIColor blackColor].CGColor);
CGContextSetLineWidth(theContext, 3);
CGContextAddRect(theContext, CGRectMake(5, 5, 40, 40));
CGContextStrokePath(theContext);
CGContextRestoreGState(theContext);
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The method in LBViewController (the main controller) that pushes the level 2 view controller
- (IBAction)testAction:(id)sender {
Level2ViewController *controller = [[Level2ViewController alloc]
initWithNibName:#"Level2ViewController" bundle:nil];
controller.title = #"Level2";
// this push statement is where the profiler tells me the messaged zombie has been malloc'ed
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
You may want to set the layer's delegate to nil before the delegate object is released. So in your Leve2ViewController do this:
-(void)viewWillDisappear:(BOOL)animated
{
if (box1) {
box1.delegate = nil;
}
box1 = nil;
}
Obviously this requires, that box1 is turned into a field (so it is accessible in viewWillDisappear:)
Since you create box1in viewWillAppear: the code above uses viewWillDisappear:. Recently, when I ran into a similar problem, I had a separate delegate object in which I used init and dealloc.
Note: You call [super viewDidAppear:animated]; in viewWillAppear. Looks like a typo or copy/paste glitch :-)

Resources