Interview question: For example, I have 3 classes A,B & C. I navigate from A -> B -> C, while pushing viewDidLoad function calls automatically and during popping viewWillAppear get called. But would it be possible to call my viewDidLoad function while popping?
No, Its not possible.Only viewDidAppear and viewWillAppear will be called by itself.You can call it manually.
All the best.
The viewDidLoad method is called when the view just loaded. Then viewWillAppear is called by the navigation controller when it's about to display the view.
If you want some code to be executed when the view is about to be displayed, be it when it's being pushed or when the top one is being popped, it makes more sense to use the appropriate method viewWillAppear for that, instead of trying to call the viewDidLoad method at a time when it isn't appropriate.
Can't you just move whatever code you have in viewDidLoad to viewWillAppear?
If the question is just "how can we call viewDidLoad while popping?", then it's simple:
- (void) viewWillDisappear:(BOOL)animated
{
[self viewDidLoad];
}
Just as a side note, if you have a view controller stack like A->B, it's possible that viewDidLoad will be called automatically on A when popping B if, while B was on top, the navigation controller unloaded A's view (if the app received a memory warning, for example)
You should not call viewDidLoad manually, it's not designed to be handled this way. Use viewWillAppear, as other users already mentioned.
As for the question whether it may happen that viewDidLoad will be called upon popping from a higher view controller: I imagine that may happen when the device got a low memory warning and unloaded the view controllers further down in the navigation hierarchy. Then the OS has to reload the view, I haven't however tested this and it's possible that this will never happen. The OS only unloads views when they don't have a superview, I didn't check whether upon pushing, views down the hierarchy actually get removed from the hierarchy.
ViewDidLoad should be for view creation.
ViewWillAppear - for Data Interaction if Server Request is Asynchronous. like calling API or any functionality.or any functionality we like to call when view going to appear.
ViewDidAppear - for Data Interaction if Server Request is synchronous.
Related
i initialize tables, data etc in my main ViewController. For more settings, i want to call another Viewcontroller with:
DateChangeController *theController = [self.storyboard instantiateViewControllerWithIdentifier:#"dateChangeController"];
[self presentViewController:theController animated:YES completion:^{NSLog(#"View controller presented.");}];
And some clicks later i return with a segue (custom:)
NSLog(#"Scroll to Ticket");
[self.sourceViewController presentModalViewController:self.destinationViewController animated:YES];
My Problem:
After returning to my main ViewController, viewDidLoad is called (everytime).I read, that the ARC releasing my mainView after "going" to the other ViewController and calling the viewDidUnload Method, but i want to keep all my data and tables i initialize at the beginning..
Any solution? Thank you very much!
The problem is that you are doing this:
main view controller ->
date change controller ->
a *different* main view controller
In other words, although in your verbal description you use the words "returning to my main ViewController", you are not in fact returning; you are moving forward to yet another instance of this main view controller every time, piling up all these view controllers on top of one another.
The way to return to an existing view controller is not to make a segue but to return! The return from presentViewController is dismissViewController. You do not use a segue for that; you just call dismissViewController. (Okay, in iOS 6 you can in fact use a segue, but it is a very special and rather complicated kind of segue called an Unwind or Exit segue.)
If you do that, you'll be back at your old view controller, which was sitting there all along waiting for your return, and viewDidLoad will not be called.
So, this was a good question for you to ask, because the double call of viewDidLoad was a sign to you that your architecture was all wrong.
I think you're taking the wrong approach - viewDidLoad is supposed to be called when it is called - it's a notification to you that the view is being refreshed or initially loaded. What you want to do is move that table initialization code somewhere else, or, at least, set a Boolean variable so that it is only called once. Would it work to create an object that has your table data when viewDidLoad is first called, then to check it to see if it's already been called?
Please let me know at what times init and loadView method gets called.
To my knowledge init method gets called only once when view is initialized and loadView is called anytime view is loaded. So, even if you are pushing a new view in the view stack and then popping it then also the loadView of the poped up view should get called. But when I am running my code in debugging mode, both of these methods are getting called once, irrespective of how many times I am loading the same screen. Please let me know if I am missing something.
you are right at some points :)
The init method is being called when the ViewController object is instantiated. The loadView method gets called every time a ViewController should load its view into memory. This can happen before the view is displayed for the first time OR when it should be displayed for a second, third,... time but had been removed from memory before. (this might happen if your app runs out of memory.)
If you want to execute some code every time the view becomes visible, you should have a look at the methods viewWillAppear/viewWillDisappear/viewDidAppear/viewDidDisappear.
loadView is called when you access the view property of your view controller and it's nil.
If the view has been unloaded (viewDidUnload has been called for memory purpose) then loadView will be called again. If not it will not be called.
What you want is viewWillAppear: or viewDidAppear:.
I want to know the sequence in which default or predefined methods ( such as viewDidLoad applicationDidFinishLaunching etc) are called in program execution in a navigation based application.
See a life cycle for a view controller
init
loadView (if view wasn't specified with initWithNibName)
viewDidLoad
viewWillAppear
viewDidAppear
//here come some actions
viewWillDisappear
viewDidDisappear
viewDidUnload (in case of memory warnings)
Everything else depends on the sequence and methods (IB or through code) of instantiating of root view controllers.
I have a navigation controller. One of the views adds custom subviews in its viewDidAppear:. I notice that the first time I navigate to an instance of this view controller after launching the app, viewDidAppear: invokes twice. If I pop this view off the stack and navigate to it again, viewDidAppear: invokes only once per appearance. All subsequent appearances invoke viewDidAppear: once.
The problem for me is that the first time I get to this view I end up with twice the number of subviews. I work around this problem by introducing a flag variable or some such, but I'd like to understand what is happening and how come I get two invocations in these circumstances.
You should never rely on -viewWillAppear:/-viewDidAppear: being called appropriately balanced with the disappear variants. While the system view controllers will do the best they can to always bracket the calls properly, I don't know if they ever guarantee it, and certainly when using custom view controllers you can find situations where these can be called multiple times.
In short, your -viewWillAppear:/-viewDidAppear: methods should be idempotent, meaning if -viewDidAppear: is called twice in a row on your controller, it should behave properly. If you want to load custom views, you may want to do that in -viewDidLoad instead and then simply put the on-screen (if they aren't already) in -viewDidAppear:.
You could also put a breakpoint in your -viewDidAppear: method to see why it's being called twice the first time it shows up.
maybe you invoke viewDidAppear in viewDidLoad (or some other stuff is going on there), since it's invoked only once during loading the view from the memory. It would match, that it's invoked two times only the first time.
This was not an iOS 5 bug, but a hidden behavior of addChildViewController:. I should file a radar for lack of documentation, I think
https://github.com/defagos/CoconutKit/issues/4
If you have a line like this in your AppDelegate
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
make sure you DON'T have a "Main nib file base name" property in your plist set to "Window.xib" or whatever your custom window nib is named. If you do, remove that row from your plist and make sure you something like
yourRootVC = [[UIViewController alloc] init];
[window setRootViewController:yourRootVC];
in your AppDelegate after instantiating your window. In most cases, you could then safely delete the Window.xib as well.
You definitely should provide more info.
Is this the root view controller?
Maybe you initiate the navigation controller with this root view controller and then push it to the navigation controller once again?
Another solution that may have been your underlying cause: Be sure you are not presenting any new view controllers from within your viewWillAppear: method.
I was calling:
[appDel.window.rootViewController presentViewController:login animated:YES completion:nil];
from within viewWillAppear and seeing my originating VC's viewDidAppear: method called twice successively with the same stack trace as you mention. And no intermediary call to viewDidDisappear:
Moving presentViewController: to the originating VC's viewDidAppear: method cleared up the double-call issue, and so now the flow is:
Original viewDidAppear: called
Call presentViewController here
Original viewDidDisappear: called
New view is presented and no longer gives me a warning about "unbalanced VC display"
Fixed with help from this answer: https://stackoverflow.com/a/13315360/1143123 while trying to resolve "Unbalanced calls to begin/end appearance transitions for ..."
it's such an annoying problem, you'd think it runs once but then I now found out about this which is causing mayhem... This applies to all 3 (ViewDidAppear, ViewDidLoad, and ViewWillAppear), I am getting this when integrating with a payment terminal; once it finish calling the API, the window is being re-loaded when it's already on-screen and all it's memory is still there (not retained).
I resolved it by doing the following to all the routines mentioned above, below is a sample to one of them:
BOOL viewDidLoadProcessed = false;
-(void)viewDidLoad:(BOOL)animated
{
if (!viewDidLoadProcessed)
{
viewDidLoadProcessed = YES;
.
.
.
... do stuff here...
.
.
}
}
Repeat the above for all the other two, this prevents it from running twice. This never occurred before Steve Jobs died !!!
Kind Regards
Heider Sati
Adding [super viewDidAppear:animated]; worked for me:
//Called twice
- (void)viewDidAppear:(BOOL)animated{
}
//Called once
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}
I have a view controller managed in a UINavigationController. My view controller puts up a "loading" screen, and uses ASIHTTP to asynchronously load table data. Then it renders its table, and in each cell there's an image that it uses ASIHTTP to asynchronously load. When it lands each image, it uses [self.tableView reloadRowsAtIndexPaths] to reload that row, inside which the image is fed to the UIImageView in each row.
Works great. But if I back out of the view before it's done loading, it crashes.
Where it crashes is in my -tableView:numberOfRowsInSection method, and NSZombies tells me it dies because it's asking for the -count of an NSArray called self.offers that has been deallocated.
That method looks like this:
-(NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
return [self.offers count];
}
Wrapping that return in if (self.offers) made no difference.
My -dealloc releases and sets-to-nil every one of these properties, including both self.offers and self.tableView itself. I even tried setting up a BOOL disappearing property, hitting it with YES in -viewWillDisappear, and hanging conditional behavior off that, but it doesn't work because viewWillDisappear doesn't seem to get called! Far as I can tell we're not getting ANY method called when the navigation bar pops us off.
What do I do about this?
EDIT:
Thanks to #cduhn (who's bucking for a check!), I did a bunch more looking at this. The problem has been, my -dealloc just isn't getting called when I pop this viewcontroller (nor my -viewWillDisappear nor -viewDidUnload or anything else I could use to unhook the delegation structure that's at the root of this problem).
Then I realized: THIS viewController isn't the one on the NavController stack! What's at the top of the stack right here is a shell view, just a segmented controller and a big empty UIView. I toggle the contents of that UIView between two other UIViewController subclasses depending on the state of my segmented controller. So when my view with the table on it's PARENT view gets popped from the nav stack, this CHILD I'm working on doesn't seem to get any notice about it. Which is odd, because I'm definitely releaseing it.
I can call its -dealloc from my shell controller. I could call its -viewWillDisappear too, for that matter. Is that how I should be handling this? Probably I should put something into my shell controller's viewWillDisappear like:
[[self.mainView subviews] makeObjectsPerformSelector:#selector(viewWillDisappear)];
...so that message propagates down to my child views.
Am I on the right track here, you think??
(Oh man... and that also explains why actions from inside this child table view can't get to self.navigationController! I've been puzzled about that for weeks!)
Bugs like this, where a method gets called on an object after it's been deallocated, often happen when a method gets called on a delegate after that delegate has been deallocated. The recommended practice to avoid bugs like these is to set any delegate (or delegate-like) properties of an object to nil before you release that object in the delegate's dealloc method. I know that's a confusing sentence, so I'll explain it in the context of your bug.
You have an asynchronous image download that finishes after you've backed out of your table view controller. When this happens, you're calling reloadRowsAtIndexPaths:withRowAnimation:, which results in a call to tableView:numberOfRowsInSection: on the table view's dataSource. This call is failing because that dataSource no longer exists.
The problem is that the table view object still has your controller set as its dataSource and delegate properties, even after your controller has been deallocated. The solution is to simply set these properties to nil in your controller's dealloc, like this:
- (void)dealloc {
self.tableView.dataSource = nil;
self.tableView.delegate = nil;
self.tableView = nil; // Releases as a side-effect due to #property (retain)
[super dealloc];
}
Now when your table view tries to call tableView:numberOfRowsInSection: on its dataSource, it will send the message to the nil object, which swallows all messages silently in Objective C.
You should also do the same thing with your ASIHTTPRequest's delegate, by the way.
Any time you have an asynchronous operation that calls delegate methods upon completion, it's particularly important that you set that delegate to nil. When using UITableViewController under normal circumstances you can typically get away without setting the dataSource and delegate to nil, but in this case it was necessary because you're calling methods on the tableView after its controller has gone away.
As far as I can tell, the user cannot actually "back out" of a view while the UITableView is loading it's data. The methods are not run on a thread and block the main one, also blocking UI interaction. I cannot replicate your results. Even, scrolling the table view quickly and then pressing the back button.
I suggest that the stack popping is not the problem here.