To call addChildViewController or not to call addChildViewController - ios

I'm fairly new to Objective-C and iOS, and I've taken on an application written by someone else. I noticed something that threw me, and I think my question varies slightly from the other questions people have asked about addChildViewController.
Well, the app works just fine. Rotations work fine. Everythings good.
So what's the problem?
Well there's a full screen view that holds a bunch of subviews in a side-by-side tiled type of layout. In other words, all of them have the same full screen parent view, and each are siblings to each other.
Those subviews are each an instance of a UIViewController.
I had expected to see those subviews set up like this...
DoohickeyController *doohickey = [[DoohickeyController alloc] initWithNibName:#"Doohickey" bundle:nil];
[self addChildViewController:doohickey];
[self.view addSubview:doohickey.view];
but instead they simply do this....
DoohickeyController *doohickey = [[DoohickeyController alloc] initWithNibName:#"Doohickey" bundle:nil];
[self.view addSubview:doohickey.view];
Is there a hard and fast rule about whether or not to call addChildViewController when the app appears to work just fine?
Is there any advantage/disadvantage to adding the call to addChildViewController?

One of the major design assumptions in UIKit is that the ViewController hierarchy will generally be in sync with the View hierarchy. Callbacks handling autorotation and size class transitions are passed down through the ViewController chain, and if you never create the parent-child relationship between ViewControllers this system breaks down.
I follow the pattern of (psudo code)
//given:
ViewController *a;
ViewController *b;
[a willMoveToViewController:b];
[b addChildViewController:a];
[a didMoveToViewController:b];
[b.view addSubview:a.view];

I think one of the reason may be the ownership of your childViewController.
If you alloc init it in a function, it is an auto parameter inside that function. And when the function returns, your childViewController will be automatically released, and become nil.
Meanwhile, the view (doohickey.view) of your childViewController, due to you addSubview to self.view, so it will kept by self (viewController). The view still exist in spite of its VC is already nil.
So addChildViewController let self (viewController) keep doohickey (subviewController), therefore the ownership of doohickey and doohickey.view will be unified, and will be released at the same time (after self being released).

Related

ARC ViewController gets deallocated

I have a view controller that displays a button. When I click the button, the corresponding selector needs to be called. However, with ARC, the application crashed with an EXC_BAD_ACCESS message.
-(IBAction)reseauPushed:(id)sender{
self.reseauVC = [[ReseauVCIpad alloc]initWithNibName:#"ReseauVCIpad" bundle:nil];
[self.viewCenter addSubview:self.reseauVC.view];
}
with
#property (strong, nonatomic) ReseauVCIpad *reseauVC;
and the crash log :
-[ReseauVCIpad performSelector:withObject:withObject:]: message sent to deallocated instance
I use ARC.
The button action :
-(IBAction)helloPushed:(id)sender{
NSLog("hello);
}
This ReseauVCIpad view controller is obviously getting deallocated some how. Either you accidentally are setting self.reseauVC to nil somewhere, or, more likely, the parent view controller, itself, is somehow getting deallocated. Is there any chance you did this addSubview technique for any of the preceding view controllers? And if not, how did you instantiate the root view controller?
To diagnose where the problem is, I'd suggest you add dealloc methods to all of your various view controllers so that you can confirm if any are getting deallocated prematurely. (Either set breakpoints or put in NSLog statements.) I'd wager you're seeing the parent of ReseauVCIpad getting deallocated, which is, in turn, allowing ReseauVCIpad itself to be deallocated.
By the way, as others have pointed out, the typical answer to this problem is to make sure you're doing the appropriate containment calls (show below), or if this child view controller takes up the whole screen, you should just be pushing to it or modally presenting it. Clearly, you have a strong reference to ReseauVCIpad, so the lack of containment calls isn't the source of the problem with ReseauVCIpad itself (though I wonder if you are doing this addSubview trick without containment calls with one or more of ReseauVCIpad's parent view controllers).
But you still should be doing these containment calls (or do a proper modal/push transition), regardless, to ensure your view controller hierarchy stays in sync with your view hierarchy (see WWDC 2011 video Implementing UIViewController Containment for lengthy discussion of why this is important). The appropriate containment calls for adding a subview with its own controller is, at a minimum, as follows:
- (IBAction)reseauPushed:(id)sender
{
self.reseauVC = [[ReseauVCIpad alloc]initWithNibName:#"ReseauVCIpad" bundle:nil];
[self addChildViewController:self.reseauVC];
[self.viewCenter addSubview:self.reseauVC.view];
[self.reseauVC didMoveToParentViewController:self];
}
For a more detailed description, see the video I referenced above, or see the Creating Custom Container View Controllers section of the View Controller Programming Guide for iOS.
And when you want to remove it, you should do the appropriate containment calls there, too:
- (void)removeReseau
{
[self.reseauVC willMoveToParentViewController:nil];
[self.reseauVC.view removeFromSuperview];
[self.reseauVC removeFromParentViewController];
self.reseau = nil;
}
This generally solves this issue (where the child view controller was deallocated). It won't solve your reseauVC problem (because you already have strong reference), but (a) you should do this wherever you do addSubview with a view controller, regardless; and (b) I show you the pattern in case you're doing addSubview elsewhere without maintaining a strong reference.
Several things. You should not add one view controller's content view to another view controller unless you set up a parent/child view controller relationship. This was added in iOS 5, and expanded in iOS 6 (and probably further expanded in iOS 7...) Look at methods like addChildViewController:, removeFromParentViewController, isMovingToParentViewController, and didMoveToParentViewController:
The easiest way to set up a parent/child view controller relationship is to use storyboards (which requires iOS 5) and an embed segue. (Which I believe was added in iOS 6). That takes care of all the housekeeping for setting up parent/child view controller relationships for you.
Your second view controller is being saved to a strong property, so I'm not clear on why it's being deallocated. I'm also not clear where the call to performSelector:withObject:withObject: is coming from. What source line is crashing, and are you using performSelector:withObject:withObject: in your code anywhere?

Dangers of Unconventional UIViewController Usage

I'm going through some old code and trying to detect some hard to find bugs. I came across an unusual usage of a UIViewController where the controller is allocated, stored in a property, and its view is added as a subview instead of presenting the entire controller.
Let me start off by saying I know this is kind of hacky and abnormal. That said, what are the dangers in the following implementation? Are there any unexpected side effects that this could cause? What if MyOtherViewController unloads its view and recreates it due to receiving a memory warning at some point, could that cause any strange behavior?
#interface MyViewController()
#property (nonatomic, strong) MyOtherViewController *otherVC;
#end
#implementation MyViewController
- (void)viewDidLoad
{
self.otherVC = [[MyOtherViewController alloc] init];
[self.view addSubview:self.otherVC.view];
}
#end
What you are doing is creating custom view controller containers. This is not a bad thing, but you aren't doing it the way you're supposed to. There is a section in UIViewController's class reference that explains exactly how to accomplish what you're trying to do.
Take a look at Displaying a View Controllers Contents Programatically
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/UsingViewControllersinYourApplication/UsingViewControllersinYourApplication.html#//apple_ref/doc/uid/TP40007457-CH6-SW8
Note this: Important: Never install the view controller’s view into a view hierarchy directly.
I just tracked down a nasty BAD EXEC crash in a project I moved to (see nasty bug below).
I can say that using a UIViewController is very bad because:
You have to make sure the controller is not deallocated. The view won't because it is in view hierarchy with a superview, but the controller has no object to retain it. If it was added to the window as a rootController, to a tab controller, a navigation controller or presented by another controller (normal usage) it would be ok.
It won't receive orientation changes and messages that you would expect to get called besides viewDidLoad.
Nasty bugs. For instance in iOS 5 if this controller is not deallocated before you dismiss a modal controller you'll have a BAD EXEC crash that will drive you crazy. It seems the animation methods from the SDK expect your view controller to be present during the dismiss modal animation.

Issues updating iOS ContainerView interface objects

I'm trying to update the interface contents of a ContainerView on iOS (UIViewController embedded in a UIView) from the UIViewController that it's being displayed in. However, the ContainerView just won't update its content.
The ContainerView and the ViewController are associated with different classes. I can pass data between the two View Controllers by using a few methods like these:
- (void)displayStringInContainer:(NSString *)string
The string gets successfully passed to the ContainerView from the ViewController, however when I try to display that string in an interface element - nothing happens (even though the code is getting called):
self.buttonName.titleLabel.text = string;
I've even tried calling setNeedsDisplay on the button, but nothing happens. Note that this is happening with all interface items.
Here's how I call the method on the ContainerView from my ViewController:
ContainerViewController *cvc = [[ContainerViewController alloc] init];
[cvc displayStringInContainer:#"Text"];
I've done quite a bit of searching, but haven't found anything (also tried to look on the Apple Dev Site, but it's been down for the past three days :P). Does anyone know how to update the content of a ContainerViewController from another ViewController? Why isn't this working? I've been scratching my head on this for a while now.
Alloc init'ing cvc is not the right way to get your reference -- that's a new instance, not the same instance as the one embedded in your view. You can access that instance in code from the parent controller with self.childViewControllers[0] (assuming you have only one container view). You can also get the reference by implementing prepareForSegue and use segue.destinationController (that will be your embedded controller).
What you seem to be missing in your understanding, is that the controller you get when you use a container view in the storyboard is a child view controller. It's the same as if you had called [self addChildViewController:whatever] in code and then added the child's view as a subview of your view.

Segues and viewDidLoad/viewDidUnload method

I am making simple storyboard application which has 2 UIViewControllers and I am switching between them by using modal segue. Each UIViewController has one UIButton which is used to perform segue to another UIViewController. In viewDidLoad method I animate appearance of that UIButton on each UIViewController. I am using Cross Dissolve Modal segue.
When I press UIButton on 1st UIViewController I navigate to second UIViewController and animation is executed and 2nd UIViewController is shown. After I press UIButton on 2nd UIView Controller, first UIViewController is shown and it's animation is executed. Everything looks fine and viewDidLoad methods are called for each UIViewController when ever I navigate to it. And that's great.
I tried now to change Modal segue type from Cross Dissolve to other two by default offered in XCode Interface Builder. I changed to Cover Vertical, and everything worked just fine, without changes. But when I tried Flip Horizontal Modal segue, I saw a problem.
When performing Flip Horizontal Modal segue, my UIButton on both UIViewControllers is shown, but animation isn't executed. I tried debugging and I am sure that animation commands are being executed, but animation isn't shown.
So that's my first question: Does anyone know is there any difference between these types of Modal segues which may cause my animation not showing up?
Other questions are related to basic theory of segues and memory management. When I perform segue and navigate to some UIViewController, viewDidLoad method is called every time. So, does that mean I created new object instance each time viewDidLoad method was executed?
I also notice that viewDidUnload method is never called. So, if answer to previous question is affirmative (each viewDidLoad execution creates new object instance), does that mean that my UIViewController object instances are never being unloaded and deleted? Or ARC is doing garbage collection behind the scenes?
If someone could explain how things works with storyboard segues and memory management/object lifecycle and why viewDidUnload method is never being called, I'd be very grateful.
[edit #1: Trying to unload UIViewController after performing modal segue]
[update #1: This shouldn't be done, viewDidUnload will be called automatically]
I am making segue in IBAction attached to UIButton click. I have written this peace of code to perform modal segue.
#try
{
[self performSegueWithIdentifier:segueToPerform sender:self];
}
#catch (NSException *exception)
{
NSLog(#"Exception: %#", exception);
}
#finally
{
[self viewDidUnload];
}
I have manually called viewDidUnload in #finally block and I have checked weather viewDidUnload is called in runtime and yes - it is called.
Does this mean I managed to unload my UIViewController object I created when navigating to it with modal segue from another UIViewController and remove it from memory?
Is this method regular as a replacement for:
[self dismissViewControllerAnimated:YES completion:nil];
because this above line returns me to UIViewController from which I navigated to current UIViewController, but that doesn't fit my needs, because I need to perform new segues from current UIViewController to other UIViewControllers (beside returning back to UIViewController from which I navigated to current one)?
[edit #2: Finish]
At the end I changed implementation model and loaded new UIViews under single UIViewController after I created separate XIB files for those UIViews. I have marked answer from #dasblinkenlight as the right one since it contains lots of useful informations and discussion on that answer gives good answers to some doubts about using modal segues.
I do not know the answer to the first part of your question, but once you learn the answer to the second part, I am sure that you would go with a different solution anyway.
viewDidLoad method is called every time. So, does that mean I created new object instance each time viewDidLoad method was executed?
Absolutely. "Modal" segue causes the new view to obscure the old one completely until the new view is closed. If you go back and forth many times, your code will accumulate a whole "stack" of views underneath the current one.
I also notice that viewDidUnload method is never called. So, if answer to previous question is affirmative (each viewDidLoad execution creates new object instance), does that mean that my UIViewController object instances are never being unloaded and deleted?
This is correct, all the view controllers that you create are still there, ready for you to close the views on top of it.
Or ARC is doing garbage collection behind the scenes?
ARC is not a garbage collector, it is a reference counting mechanism with a little automation from the compiler. The objects are still there.
You should change your code to call
[self dismissModalViewControllerAnimated:YES];
in the second controller, rather than using a modal segue that brings you back to the first one.
I hit a similar issue where I had a complex VC navigation web, and the resulting use of segues (no UINavigationController) was sucking up too much memory.
You may want to take a look at this question/answer to see my final solution.
The conversation here between uerceg and dasblinkenlight helped me in finding this solution.
Segues and clearing historical ViewControllers from memory

Adding UIViewController.view to another view causes orientation problems

Short version:
I'm alloc/init/retaining a new UIViewController in one UIViewControllers viewDidLoad method, adding the new View to self.view. This usually works, but it seems to mess up orientation change handling of my iPad app.
Longer version:
I'm building a fairly complex iPad application, involving a lot of views and viewcontrollers. After running into some difficulties adjusting to the device orientation, I made a simple XCode project to figure out what the problem is.
Firstly, I have read the Apple Docs on this subject (a small document called "Why won't my UIViewController rotate with the device?"), and while I do believe it has something to do with one of the reasons listed there, I'm not really sure how to fix it.
In my test project I have an appDelegate, a rootViewController, and a UISplitViewController with two custom viewControllers. I use a button on the rootViewController to switch to the splitViewController, and from there I can use a button to switch back to the rootViewController. So far everything is great, i.e. all views adjust to the device orientation.
However, in the right viewController of the splitViewController, I use the viewDidLoad method to initialize some other viewControllers, and add their views to its own view:
self.newViewController = [[UIViewController new] autorelease];
[newViewController.view setBackgroundColor:[UIColor yellowColor]];
[self.view addSubview:newViewController.view];
This is where things go wrong. Somehow, after adding this view, adjusting to device orientation is messy. On startup everything is fine, after I switch to the splitViewController everything is still fine, but as soon as I switch back to the rootViewController it's all over. I have tried (almost) everything regarding retaining and releasing the viewcontroller, but nothing seems to fix it.
As you can see from the code above, I have declared the newViewController as a property, but the same happens if I don't.
Shouldn't I be adding a ViewController's view to my own view at all? That would really mess up my project, as I have a lot of viewControllers doing all sorts of things.
Any help on this would be greatly appreciated...
I had the same problem, seams resolved by removing from the parent view the "autoresize subview" option in IB.
Select the view a then: Inspector -> Attributes (first tab) -> drawing.
What I think is happening is that your new viewcontroller is getting the autorotation calls and probably not handling them. Meanwhile your old viewcontrollers and its views won't get the autorotation calls and will be stuck in whatever orientation they were in.
I can't tell, but I think what you want to do is to make a UIView and add it to your old viewcontroller, instead of making a UIVIewController and adding its view.

Resources