viewDidAppear: not firing under certain conditions? - iphone

I have the following items in my app nib:
the usual: file's owner, first responder window, delegate
View Controller "a"
View "b"
UIScrollView "c"
some other stuff in "b"
In my AppDelegate applicationDidFinishLaunching, I do this:
[window makeKeyAndVisible]
[window addSubView:a.view];
create a view controller "d"
create a navigationController "e" with rootviewcontroller "d"
invoke [c addSubView:e.view]
Question/problem: when I do all of the above, viewDidAppear: is not firing for "d". (but viewDidLoad IS firing.) How do I find out why it is not firing, and fix it so that it would fire?
(Why I want to use viewDidAppear: the above involves some chained animations and viewDidAppear looks like a good place for a view controller to know when its view has been loaded and animated, so it can trigger subsequent animations.)

Usually when you're manually screwing with the view hierarchy you won't get -viewWillAppear:, -viewDidAppear, etc.; they're called by various SDK methods, like -pushViewController:animated:, -presentModalViewController:animated:, and by UITabBarController when a tab gets selected.
When you add a view to the hierarchy yourself, it may or may not be onscreen or going-to-be-onscreen; the -addSubview: method doesn't make any assumptions about your intentions. Just call 'em yourself as you add the view.

The first thing you should be aware of is that viewDidAppear is a method of UIViewController and not of UIView, it really has nothing to do with views.
The second thing is that there can only be one "active" UIViewController at a time.
When you add "a"'s view to the window it becomes the active UIViewController and only "a" will receive the viewDidAppear message while "e" won't actually be getting any UIViewContoller related methods (viewDidAppear, viewWillAppear etc.)
As #Noah mentioned when you use pushViewController you will receive these messages because the method causes the pushed view Controller to become the "active" UIViewController.
My suggestion for you is that if you create controllers for views that are subviews don't subclass UIViewController but rather NSObject, it will reduce your confusion level as you won't expect to get your UIViewController methods called which they won't anyway.

I had a similar issue when I set the delegate of my navigation controller. So in my UINavigationControllerDelegate methods, I did something like this:
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
//do something here
[viewController viewWillAppear:animated];
}
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[viewController viewDidAppear:animated];
}

Related

Present modal view controller from different view? ModalViewController being hidden by other views

How can I present a modalViewController from a different view controller?
I am currently displaying my modal view like this:
[self presentModalViewController:navController animated:YES];
But, I want to
[OtherViewController presentModalViewController:navController animated:YES];
Because part of it is being hidden by another viewController that is above it and manages the "self" view.
Well it should work, but maybe you are in a corner case.
Try adding a static method to your delegate like:
+ (void)presentModalViewController:(UIViewController *)viewController;
Where you do something like
[parentViewController presentModalViewController:viewController animated:YES];
And use it to present the modal view directly from the parent VC.
Anyway, if you still have a problem, give us some code :-) or your app archi.
It simply does not work b/c there is now such class method declared in a UIViewController class.
You are trying to call (note the + sign in front):
+ (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated
UIViewController class only provides implementation for an instance method (note the - sign in front):
- (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated
What should this tell you?
It tells you that you need to have a link to a live OtherViewController object to be able to invoke presentModalViewController on it, otherwise there is no View hierarchy the compiler can comply with.

iOS: How to Recognize that We Got Back from a Child UIViewController within the Parent UIViewController?

Let's say that I have 2 UIViewControllers on a stack within a UINavigationController. In the "parent" we call "[self.navigationController pushViewController:childViewController animated:YES];" upon some user action and in the "child" we call "[self.navigationController popViewControllerAnimated:YES];" upon some user action.
How can we recognize within the parent that we just got back?
Is there some "event" driven method that can recognize that this popViewControllerAnimated action was called from the child?
It seems like you're using this child controller as a modal in that it can be 'dismissed'. If that is the case, try to follow Apple's patterns that they use for UIAlertViews.
If that is the case, I'd do either of the following to implement a delegate pattern(delegate vs block is a huge debate that I will not get into here) so the owner(the one that pushes the child on) knows when its dismissed:
Create a protocol (ChildControllerDelegate), have one method in it childControllerWasDismissed:(ChildController *)
add a block property(make sure its a copy property, not retain) to the ChildController
You'll then want to call the delegate method or block on viewDidDisappear. If you want finer grain control, have a delegate method or block that corresponds viewWillDisappear / viewDidDisappear.
I'd successfully resolved this by setting navigationController?.delegate = self and then implementing this method to determine whether the current view controller is shown again after a pop.
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if viewController == self {
// we got back
} else {
// some other controller was pushed
}
}
There's a few way to hint at that. What you can do, is call the popViewControllerAnimated from the parent. You can do that by passing a block to the child controller that would then execute the said block and thus popping would be done by the parent controller.
You can also use the UINavigationController delegate to be notified when a UIViewController will be dismissed:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
This method will let you know which VC will be shown and you can check if the current (not yet popped) VC is the child you were looking for.
You can also do some trick with - (void)viewWillAppear: but this might require some hacks.
First read this, it will help you understand what is going on with view controllers.
Then implement viewWillAppear: and viewDidAppear: in your parent view controller to log a message.

Why isn't viewWillDisappear or viewDidAppear being called?

I have a UINavigationController with a UITableView as my main menu. User clicks on a cell and a new view is pushed on the stack. In one case I push another UITableView that needs a toolbar. So on that 2nd tableView's init I setup the self.toolbarItems property with the correct items. But then I need to call [self.navigationController setToolbarHidden:NO animated:YES]; So it makes sense to call this in the viewDidAppear or viewWillAppear method. But I put it in those methods and find out (Also via NSLog) that they never get called. The same goes for hiding it in viewWillDisappear or viewDidDisappear. Why don't these methods get called? Where should I be doing this hiding/showing of the toolbar then?
I have noticed behavior where if a parent controller (like UINavigationController or UITabBarController) never get's viewWill/DidAppear called on it, it won't call it on the child controllers either. So make sure that in the code where you create the parent controller, you call viewWillAppear, show it, then call viewDidAppear. Then it should make those calls on it's child controllers as is appropriate.
Double check the parent controller is having those methods called, and call them yourself if they are not.
Yes Its true
you can do this by first write this code in
- (void)viewDidLoad {
self.navigationController.delegate = self;
}
And then write the code which you want to write in viewWillAppear
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([viewController isKindOfClass:[self class]]) {
//write your code here
}
}
Although you solved your problem, in case someone comes along in the future another problem could have been that you forgot the animated: argument to either method - that is to say, the format of the method needs to look like:
- (void) viewWillAppear:(BOOL)animated
i noticed the same issue in iOS7. When i'm using both tab bar (2 buttons A, B) and navigation controller.
A has two views. One with tableview and second displays data according to the selection from the table view.
B has is the only view.
Button which is refer to another separate view D, placed in both tab bar views (A & B) and in both views of A.
Problem arises when i click the button from tab item B, viewWillAppear and viewDidLoad not called.
So i solved this issue by presentModalViewController:animated: and to come back i used dismissModalViewControllerAnimated:, just when i go to view D from tab item B.

viewWillAppear, viewDidAppear not being called, not firing

(This is both question and answer since it took quite a bit of digging to find the real answer.)
Symptom: viewWillAppear, viewDidAppear were not being called in my UIViewController.
Cause: Embedding a UINavigationController or UITabBarController (my case) in a UIViewController somehow interrupts with the calling of these methods.
Solution: Call them manually in the UIViewController that contains the aforementioned UINavigationController / UITabBarController.
For example (assuming projectNavigationController is your UINavigationController):
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[projectNavigationController viewWillAppear:animated];
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[projectNavigationController viewWillDisappear:animated];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[projectNavigationController viewDidAppear:animated];
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[projectNavigationController viewDidDisappear:animated];
}
In my case I had an inner UITabBarController and I called the methods accordingly and all was solved.
(Attribution on the solution: http://davidebenini.it/2009/01/03/viewwillappear-not-being-called-inside-a-uinavigationcontroller/)
I'm going to go ahead and disagree with #St3fan, and use UIKit as the counter-example.
However, the wisdom (or lack thereof), of embedding controllers in general should be guided by sane UI design principles.
The easiest counter-example is UINavigationControllers embedded in UITabBarControllers. These appear all over the place. Just off the top of my head, the iPod app on iPhone, and Contacts within the Phone app on iPhone.
I was curious enough to bother to check what they do with the views (add to the "super-controller" view or to the UIWindow. I was pretty sure I was going to find that the sub-controller views were descendants of the super-controller views in the view hierarchy, which is contrary to St3fan's recommendation.
I whipped up a very quick iPhone app hooking everything up in InterfaceBuilder to create a UITabBarController based app with two tabs, the first of which was a UINavigationController with a plain ole UIViewController as it's root view controller, and a 2nd tab with a plain old UIViewController just so I had a 2nd tab to click later.
Sprinkle in some NSLog statements to output the various UIView's for the controllers we see this:
tabBarController.view = <UILayoutContainerView: 0x5b0dc80; ...
navigationController.view = <UILayoutContainerView: 0x59469a0; ...
rootViewController.view = <UIView: 0x594bb70; ...
Superview: <UIViewControllerWrapperView: 0x594cc90; ...
Superview: <UINavigationTransitionView: 0x594a420; ...
Superview: <UILayoutContainerView: 0x59469a0; ... // navigationController.view
Superview: <UIViewControllerWrapperView: 0x594b430; ...
Superview: <UITransitionView: 0x5b0e110; ...
Superview: <UILayoutContainerView: 0x5b0dc80; ... // tabBarController.view
Superview: <UIWindow: 0x5942a30; ...
The lines prefixed with "Superview" were the output from walking up the rootViewController.view's superview chain until hitting nil.
Then of course a quick glance at the call stack in a couple of places where viewDidDisappear would get called on the root view controller.
First, the call stack when viewDidDisappear is called on the root controller as a result of a new controller being pushed on to the stack:
-[RootController viewDidDisappear:]
-[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:]
...
Second, the call stack when another tab is selected in the top-most UITabBarController:
-[RootController viewDidDisappear:]
-[UINavigationController viewDidDisappear:]
-[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:]
So in all cases, it seems that Apple decided that controllers should be calling the various viewDidAppear, etc methods on their embedded subcontrollers and that the view's should be embedded similarly. I think the OP hit this nail right on the head if we're to take UIKit design as a good lead to follow.
I just saw this same circumstance. Earlier the interface builder segue triggered by a table cell selection had stopped working and after some exasperation digging into the code, I just set it up manually, calling from the cell-selection override in the table view delegate.
Later I made some layout changes in the called view controller and saw that viewDidAppear wasn't being called, as described above. The debug output made reference to "nested push operations" or something, and since I had a big comment to myself in my manual push operation
#warning I SHOULD NOT HAVE TO DO THIS!!
I breakpointed the segue code and, sure enough, the IB segue was now working, and it was my manual operation in the table cell selection code that was messing up the delegate calls in the called view. I removed the manual code and everything is fine again.
It does seem odd that the cell select code gets called after the view is pushed. I have to do a protocol and delegate to get the indexpath of the selected cell in the caller.

How to automatically call a method after popping a view controller off the stack on the iPhone

I need to update the parent view on an iPhone after popping a child view off the navigation stack. How can I setup the parent view to be notified or receive an automatic method call when the child is popped off the stack and the parent becomes visible again?
The user enters data on the child page that I want to display on the parent page after the user is done and pops the view.
Thanks for you help!
I just resolved this self same problem - and the answers above are almost correct, they just forgot about setting the delegate.
I have a root view controller that displays the size of a list, calls a child view controller that may alter the size of a list, and must update the size upon return.
When I create my parent view (SettingsView below), and add it as the root view of a UINavigationController, I make sure to set the UINavigationController's delegate before I display the view - that's the key part:
SettingsView *sv = [[SettingsView alloc] initWithNibName:#"SettingsView" bundle:nil];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:sv];
[nc setDelegate:sv];
In the parent view, implement the UINavigationControllerDelegate protocol:
#interface SettingsView : UIViewController <UINavigationControllerDelegate>
and provide the willShowViewController method:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// Your code to update the parent view
}
This is called after the child view is dismissed, and before the parent view is redisplayed.
I had the need to do something like this as well. In the ViewController that owned my UINavigationController, I had to implement willShowViewController, like this:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
}
That method is called whenever the UINavigationController changes views. If I'm understanding your question correctly, I think this should do what you want.
I think there is some confusion here. UIViews are not pushed to and popped from the UINavigationController's stack. What is being pushed and popped is UIViewControllers, which in turn handle one or (more often) several views each.
Fortunately, the UIViewController has these methods:
-(void) viewWillAppear:(BOOL)animated;
-(void) viewDidAppear:(BOOL)animated;
-(void) viewWillDisappear:(BOOL)animated;
-(void) viewDidDisappear:(BOOL)animated;
These are called whenever the view is about to (dis)appear, or has just (dis)appeared. I works with tab bars, modal views and navigation controllers. (And it's a good idea to make use of these when you implement custom controllers.)
So in your case, if I understand correctly, you simply have to override the viewWillAppear: or viewDidAppear: method on what you call the "parent page" (which is presumably handled by a UIViewController) and put in code to update the appearance of the page to reflect the data just entered.
(If I remember correctly, you must make sure that the UINavigationController gets a viewWill/DidAppear: message when it is first displayed, in order for these messages to later be sent to its child controllers. If you set this up with a template or in IB you probably don't have to worry about it.)
Felixyz answer did the trick for me. Calling the view will appear method will run the code in it every time the view appears. Different from view did load, which runs its code only when the view is first loaded. So your parent view would not update itself if a child view altered the info displayed in the parent, and was then popped off the sack. But if the parents calls view will appear, the code gets ran every time the view shows back up.
Make sure to call the super method at the same time. Proper implementation would look like this:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(#"View Appearing");
}
If you need to notify one controller to another you may use delegation pattern as described here (see 2nd answer).
Unfortunately there is no automatic notification(AFAIK) for exact task as you described.
To meet your needs you may send message to delegate (i.e. to your parent controller) in viewWillDisappear function of your child controller.