General clarification on viewWillAppear? - iphone

I have a general question about viewWillAppear, I pushed newly a view consider view1, now viewWillAppear is called for that method, if view1 pushed another view (view2). Now if view2 is popped view1's viewWillAppear is called again, how can I come to know in which case its called i.e (is it because of newly pushing or popping of other view).
Hope my question is understandable :)
TIA

Frankly, I do not know how to determine that condition appropriately.
You could of course set some property of view1 in view2's viewWillDisappear method and re-set that in view1's viewWillAppear. Such like:
view1.m:
- (void) viewWillAppear ... {
...
if (self.wasPopped) {...}
self.wasPopped = NO;
...
}
view2.m:
- (void) viewWillDisappear {
view1.wasPopped = YES; //you would have to have a reference to view1 or fetch it from the navigation controller stack.
}
However, I strongly believe that this is not really the way you should go. Consider moving your code to the viewDidLoad method. viewDidLoad is called only once when the view(Controller) is created well before viewWillAppear is called for the first time.

Depending on your context, you want to use...
For viewWillAppear/viewDidAppear
isBeingPresented
isMovingToParentViewController
For viewWillDisappear/viewDidDisappear
isBeingDismissed
isMovingFromParentViewController

Just simply remember that whenever your view will appear on screen(it is going to be visible ) viewWillAppear method will be called.
In your case when view2 is popped again your view1 is going to appear on the screen that's why it's viewWillAppear is called again. Same way when you push new view controller, that new view controller will be displayed on the screen. Before displaying that view, that new view controller's viewWillAppear will be called.
I hope it makes sense.

You can use BOOL value and initialize it to FALSE.In viewWillAppear make that bool value TRUE and in viewWillAppear only check whether that bool value is TRUE or FALSE if it is true means the view is appearing for second time.

Related

UINavigationController - Run Code Before Popping View Controller

I have a stack of UIViewController subclasses. Each modifies a NSManagedObject model. Many of them also present their own modal view controllers.
I need to save changes to the NSManagedObjectContext when a user either 'pops' the view controller or pushes the next view controller.
Currently, I'm hiding the default back button and setting my own UIBarButtonItem with a target of self and a custom action.
This works okay, but ideally, I want to use the default back button and run code before the pop. Is there a way I can run my own code before the pop?
(I'd prefer not to put code into viewWillDisappear as persisting to disk can be expensive and this method can also be triggered by modals being displayed by view controller.) Can it be done?
You can do it in viewDidDisappear, after checking that self is either 1) the second last element in self.navigationController.viewControllers (the case where the next VC just got pushed) or 2) self.navigationController is nil (the self VC just got popped).
Yes.. Navigation controller has a delegate which indicates when a view controller popped or pushed.. You can use that to do your task...
Add following method in your code:
- (void) viewWillDisappear:(BOOL)animated{
//your code here
}
I use viewWillDissappear to make any changes persistant.
If required i Use viewWillAppear to recoginze any changes (reload the data) that may have taken place while other puhed view controlers did their work.
For pop check isMovingFromParent in viewWillDisappear
func viewWillDisappear(_ animation:Bool){
super.viewWillDisappear(animation);
if isMovingFromParent {
// your code here
}
}

UIViewController: viewWillAppear is called, viewDidAppear not

In a UIViewController subclass, I have the following methods:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// do something
myTextField.text = #"Default";
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// do something
[myTextField selectAll:self];
[myTextField becomeFirstResponder];
}
- (void)viewDidLoad {
[super viewDidLoad];
// do something
myTextField.delegate = self;
}
The NIB has been created using Interface Builder. The corresponding view controller object is pushed by the navigation controller through pushViewController.
The inteded behavior is to show a default text entry in a text field, to select the entire text and to set the text field as first responder. [Edit: I've noticed that selecting all and making first responder makes no sense as the selection would dissapear; still, I wonder why the methods behave as described next.]
However, while methods viewDidLoad and viewWillAppear are called, the method viewDidAppear is not called. Can anybody tell me why? Most questions I found on the web and here deal with both viewWillAppear/viewDidAppear are not working; I also understood that in subviews or programmatically created views these methods are not evoked; but this does not apply in case and also I wonder why one of these "lifecycle" methods is evoked and the other not.
Any idea? Thanks!
I had this issue happen to me: viewWillAppear was being called but viewDidAppear was not!
I finally figured out that this was because I had a tabBarController where I overloaded it's own viewDidAppear and forgot the [super viewDidAppear:animated];
It threw off every VC in every tab! adding that line back in fixed it for my other VC's.
Hope this helps someone!
There is one case when viewWillAppear will be called but viewDidAppear will not.
Suppose you have two viewControllers and you push from the first to the second controller. Then, using the swipe, you want to go back to the first one, both controllers are displayed at the same time and at that moment viewWillAppear will be called from the first controller.
This way, you do not finish the swipe and stay on the second controller and viewDidAppear will not be called from the first controller.
I had the same problem.
I had copy/pasted viewDidAppear to create viewWillAppear but had forgotten to change the super.viewDidAppear() call. This then seemed to stop viewDidAppear from being called.
It sounds like somewhere in your code you have missed or messed-up a call to the superclass.
The call to viewDidAppear: should always follow viewWillAppear: unless you are doing something custom, which you say you don't. I don't know why it doesn't work but here are a few ideas:
Could it be that you are doing something strange in one of the delegate methods for UITextFieldDelegate? It's unlikely that it would affect viewDidAppear: being called but it could be a culprit.
Have you loaded a lot of stuff into memory before pushing the view? I'm not sure what would happen if you got a memory warning between viewWillAppear: and viewDidAppear:.
Have you tried to do a Clean? Sometimes that can help.
In cases like these when it should work I usually create a new class and the introduce the functionality one at a the time to see if I can get it work that way. I tried your code in a new Navigation Based project where I added a new UIViewController with an outlet to the text field. Then I pasted the code from the question and it did work as expected.
This can be because you added a child view controller to your parent VC in viewDidLoad or viewWillAppear. The child's appearance prevents the call to viewDidAppear.
This is a crazy thing to do, and I only know because this was a bug in my code. I meant to add the child VC to this VC, not the parent VC.

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.

Invocation of ViewDidLoad after initWithNibName

It appears that ViewDidLoad() is sent to a ViewController only after its View is physically displayed (i.e. via NavigationController pushViewController), and not immediately after initWithNibName(). Is this a behavior I can rely on? I would like to get the chance to set the member variables of my view so that all the members are valid by the time ViewDidLoad() is invoked.
You can set up member variables and other such things in initWithNibName:bundle:.
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle {
if (self = [super initWithNibName:nibName bundle:nibBundle]) {
// set up ivars and other stuff here.
someIvar = someValue;
}
return self;
}
You are correct that viewDidLoad: is only sent when the view is physically displayed, i.e when it is added to some visible view (which may sometimes be never if the user does not reach that view). So it's useful to split the functionality and think about what you can do at init time and what happens at view load time.
As Marcelo Cantos notes in the comment, viewDidLoad: is generally a fine place to do all sorts of setup work, using the concept of "lazy loading," so that you defer the setup until as late time as possible.
viewDidLoad is called before a view controller is displayed for the first time, not immediately after initWithNibName. For example, if you have a tab bar controller, all of the child view controllers will be initd at launch, but viewDidLoad will only be called when you click on the appropriate tab the first time. It's generally a good idea to initialize memory-intensive items in viewDidLoad, so as to avoid using unnecessary memory.
I found that if I override initiWithNibName in the view controller, the viewDidLoad method is not called. I have to call it manually [self viewDidLoad]. But if I do not override initWithNibName: viewDidLoad is called. I am working with 4 view controllers in tab bar controller. the tab bar controller is loaded from another view.
Sorry to unearth an old thread, but this solved it for me...
-(void)viewDidLoad is only called after -(void)loadView has done its thing. In the docs for loadView:
The view controller calls this method when its view property is requested but is currently nil.
My view controller only has viewDidLoad called after its view is request by a UITabBarItem, meaning viewDidLoad is only called in the viewController once the tab bar button is pressed. I, like the OP, want viewDidLoad to be called directly after the nib is loaded, so it's contents (titles, etc) can be populated before the user clicks the tab button.
So, after calling "self = [super initWithNibName:#"nibName" bundle:nil];" in the view controller's custom initialiser, I immediately called '[self view]' afterwards. As the view is requested earlier than when it is requested by the UITabBarItem (which calls 'addSubview'), the view is initialised fully during initialisation, rather than when requested.
Hope this helps.

What happens to the root view in UINavigationViewController when you push on a new view?

Specifically, what am I supposed to do with the view that is now hidden after pushing on a new view controller?
In my situation I have a view with animations going on, that continue to execute after the view is off screen.
Is there some accepted convention?
Do I remove the View Controller and View from memory?
Does Cocoa Touch have a convenient method to "Pause" a view (and controller) and remove it from memory, and bring it back into existence when needed (after a pop)?
Do I have to archive it myself and then un-archive it?
Are there any examples you can point me to?
Thanks
Another possible solution is to implement two of the following methods:
– viewWillAppear:
– viewDidAppear:
– viewWillDisappear:
– viewDidDisappear:
You could potentially stop your animation in viewWillDisappear or viewDidDisappear and then restart it in viewWillAppear or viewDidAppear. You could also store any necessary state information about the animation before you stop it.
Under low memory conditions, your controller's 'view' property will automatically be set to nil, if it's not on screen. Then the view will automatically load again when it is needed - and viewDidLoad should get called at that time.
If your view controller is retaining any subviews of the top-level view, then you may want to override the view controller's setView: method, check if the view is being set to nil, and if so, release subviews that you were retaining. Otherwise, the top-level view may never get deallocated.
- (void)setView:(UIView *)view
{
[super setView:view];
if (view == nil)
{
// Release other views
// self.someView = nil;
}
}
Whenever you call pushViewController, the current viewcontroller is stored in an array by navigation controller (this can be accessed using the viewControllers property of navcontroller).
Think of it as a stack. As you call pushViewController, a new viewcontroller is added to the stack on top of the current viewcontroller. But your rootviewcontroller is still in memory. When you call popViewController, that viewcontroller is removed from the stack and released.
So if you want to stop your animation when the view disappears, use the viewWillDisappear method as suggested by Andy.