UIViewController: detecting when view appears again - ios

I have a UIViewController class that contains a WKWebView and implements WKNavigationDelegate.
I would like to detect when a the view controller appears again. I understand the method loadView but, if I push a new view on the stack and then go back from that view to the previous view (my view controller) which method is called on the view controller?

The method that will be called is viewWillAppear:.

If you push to next view then viewDidLoad will be called first
Then viewWillAppear, viewDidAppear
If you pop to previous screen again (your UIViewController) then
viewWillAppear will be called first and after entire view appears
then viewDidAppear will be called..
viewDidAppear is useful in the cases where any method called at viewWillAppear after that you can Load the data at ViewDidAppear..

The ViewControllers viewDidLoad method is only called once when the view is created for the first time.
// viewDidLoad is called only once when the view is created for the first time
- (void) viewDidLoad
{
[super viewDidLoad];
// do your code here
}
You can also implement the below two methods in side your ViewController.m class
// viewWillAppear is called just before the view is about to be appeared
- (void) viewWillAppear
{
[super viewWillAppear];
// do your code here
}
// is called when the view has appeared
- (void) viewDidAppear
{
[super viewDidAppear];
// do your code here
}

Related

Is it necessary to call super in viewWillAppear?

I am trying to understand the scenario of the method calls to view did/will appear and disappear.
What I did is selecting the table cell (higlights in grey) , go to detail view and go back and deselect the selected row (remove the selected cell grey color).
Here are my methods:
-(void)viewDidAppear:(BOOL)animated {
DLog(#"%# did appear", self);
[super viewDidAppear:animated];
if (_isPushed) {
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
_isPushed=NO;
}
}
-(void)viewWillAppear:(BOOL)animated {
DLog(#"%# will appear", self);
[super viewWillAppear:animated]; //If I remove this super call , then it works fine and there is no delay in deselecting the table cell
}
-(void)viewWillDisappear:(BOOL)animated {
DLog(#"%# will disappear", self);
[super viewWillDisappear:animated];
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
_isPushed=YES;
}
So , when I put breakpoint the flow goes like this:
without super call:
while pushing to new VC:
current viewWillDisappear //makes sense
new viewWillAppear //makes sense
current viewDidAppear // doesnt make sense , y this should get called as the view is already appeared?
current viewWillDisappear // make sense
current viewDidDisappear //make sense
new viewDidAppear //make sense
while coming back from pushed VC:
current viewWillDisappear
new viewDidDisappear
current viewDidDisappear
new viewDidAppear
with super call:
while pushing to new VC:
current viewWillDisappear
new viewWillAppear
current viewDidAppear
current viewWillDisappear
current viewDidDisappear
new viewDidAppear
while going back from pushed VC:
current viewWillDisappear
new viewDidDisappear
current viewDidDisappear
new viewDidAppear
The flow is pretty much the same either I use super call or not.
But the problem I am facing is, when I use super call in viewWillAppear, there is a delay(around >1second) in deselcting the cell.
If I dont use the super call in viewWillAppear , there is no delay and the cell is deselcting (around <0.5 seconds)
I am not sure to use super call or not.
Can anyone please tell me why there is a delay in deselecting the cell?
Yes, the documentation states you must:
Discussion
This method is called before the receiver's view is about
to be added to a view hierarchy and before any animations are
configured for showing the view. You can override this method to
perform custom tasks associated with displaying the view. For example,
you might use this method to change the orientation or style of the
status bar to coordinate with the orientation or style of the view
being presented. If you override this method, you must call super at
some point in your implementation.
Generally yes, call super. I've seen weird things happen in nav controllers when I forget.
In this case, if you have a UITableViewController, try using its clearsSelectionOnViewWillAppear flag to clear the selection for you.
Yes It's necessary to write super.

Call a function when a view controller is on top

I'm wondering if there is a method that would call when a view controller is loaded to the screen.
As an example, the user presses a button and a second view controller loads. When the view controller appears, it runs a function automatically.
There are several methods you can use in a ViewController to run functions depending when you need them to run. In your case, you should use
-(void) viewDidAppear:(BOOL)animated
This will run once the viewController is appearing on the screen. Be sure to also call [super viewDidAppear:animated] inside the method.
Other methods that may come in handy:
-(void) viewDidLoad
-(void) viewWillAppear:(BOOL)animated
-(void) viewWillDisappear:(BOOL)animated
-(void) viewDidDisappear:(BOOL)animated

UIViewController viewWillAppear not called when adding as subView

I have a UIViewController that I am loading from inside another view controller and then adding its view to a UIScrollView.
self.statisticsController = [self.storyboard instantiateViewControllerWithIdentifier:#"StatisticsViewController"];
self.statisticsController.match = self.match;
[self.scrollView addSubview:self.statisticsController.view];
I've put breakpoints in the statistics view controller and viewDidLoad is being called but viewWillAppear isn't.
Is it because I'm not pushing it onto the hierarchy or something?
You should add statisticsController as a child view controller of the controller whose view you're adding it to.
self.statisticsController = [self.storyboard instantiateViewControllerWithIdentifier:#"StatisticsViewController"];
self.statisticsController.match = self.match;
[self.scrollView addSubview:self.statisticsController.view];
[self addChildViewController:self.statisticsController];
[self.statisticsController didMoveToParentViewController:self];
I'm not sure this will make viewDidAppear get called, but you can override didMoveToParentViewController: in the child controller, and that will be called, so you can put any code that you would have put in viewDidAppear in there.
I encounter -viewWillAppear: not called problem again. After googling, I came here. I did some tests, and find out that the calling order of -addSubview and -addChildViewController: is important.
Case 1. will trigger -viewWillAppear: of controller, but Case 2, it WON'T call -viewWillAppear:.
Case 1:
controller?.willMoveToParentViewController(self)
// Call addSubview first
self.scrollView.addSubview(controller!.view)
self.addChildViewController(controller!)
controller!.didMoveToParentViewController(self)
Case 2:
controller?.willMoveToParentViewController(self)
// Call adChildViewController first
self.addChildViewController(controller!)
self.scrollView.addSubview(controller!.view)
controller!.didMoveToParentViewController(self)
By default, appearance callbacks are automatically forwarded to children.
It's determined with shouldAutomaticallyForwardAppearanceMethods property. Check value of this propery, if it's NO and if your child viewController should appear right on container's appearance, you should notify child with following methods in container's controller life-cycle implementation:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
for (UIViewController *child in self.childViewControllers) {
[child beginAppearanceTransition:YES animated:animated];
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.child endAppearanceTransition];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
for (UIViewController *child in self.childViewControllers) {
[child beginAppearanceTransition:NO animated:animated];
}
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.child endAppearanceTransition];
}
Customizing Appearance and Rotation Callback Behavior
Fixed my problem! Hope it would be helpful.
As mentioned in another answer, the parent view controller might not call viewWillAppear etc. when shouldAutomaticallyForwardAppearanceMethods is set to false. UINavigationController and UITabBarController are known to do that. In this case, you need to call beginAppearanceTransition(_ isAppearing: Bool, animated: Bool) on the child view controller with isAppearing set to true when the view appears and vice versa.
You have to place these calls at appropriate places in your code, normally when you add and remove your child view controller.
Don't forget to call endAppearanceTransition on your child view controller when your custom transition has ended, otherwise viewDidAppear and viewDidDisappear are not called.
Per Apple (https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html), the correct order of API calls to add a child view controller is:
[self addChildViewController:childVC];
[self.view addSubview:childVC.view];
[childVC didMoveToParentViewController:self];
But I still had the problem where viewWillAppear in the child VC was not sporadically getting called. My issue was that there was a race condition that could cause the code above to get executed before viewDidAppear in the container view controller was called. Ensuring that viewDidAppear had already been called (or deferring the addition of the child VC until it was) solved it for me.
The previous answers are correct, but in case it helps someone - if you override loadView in the child view controller, then none of the other UIViewController methods get called.
Took me some time to realize why my code wasn't running properly, until I realized that I had accidentally overridden loadView instead of viewDidLoad.
Check if your parent VC is a UINavigationViewController (or any other container). In this case the shouldAutomaticallyForwardAppearanceMethods is False and the appearance methods are not called.
I can't understand your questions and your description.
My problem was similar to this only.
CustomTabBarController -> CustomUINavigationController -> RootViewcontroller
viewWillAppear of CustomUINavigationController and RootViewController are not getting called unless you switched to another tab and come back.
The solution is call super.viewWillAppear(animated: true)
override func viewWillAppear(_ animated: Bool) {
**super.viewWillAppear(true)**
}
I struggled for more than a day for this small mistake.
View appearance methods also will not get forwarded if your view controller hasn't loaded its view. This could happen if you override loadView in your child view controller, and the view is already added to the view hierarchy.
In that case, you could do
addChild(childVC)
childVC.loadViewIfNeeded()
childVC.didMove(toParent: self)

Pushing self.view to navigation controller by allocing, but when coming back same data is shown

I am pushing self view to self.navigationcontroller by allocating. I have a tableView on that view so I am changing the content of tableview. But when I am pressing back button (that is automatically created), I am not able to show previous content. Its showing updated content.
Please suggest.
You set code in viewWillAppear method
- (void)viewWillAppear:(BOOL)animated
{
//code set here
}
If you fill the tableView's data in viewWillAppear: or viewDidAppear:, it will reload even if you only press the back button of your top viewController. If you do not want to have your content changed, you are supposed to use initWithNibName: or viewDidLoad: methoads. They are called only at creation time of the view.
Based on the comments on #Kirti's post, You can check if your viewcontroller is being popped by following method, and take some necessary actions for you controller holding table.
-(void) viewWillDisappear:(BOOL)animated
{
if(![self.navigationController.viewControllers containsObject:self])
{
YourControllerWithTable *instance = [self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count - 1];
instance.loadOldContent = YES;
}
}
In viewWillAppear: of YourControllerWithTable, you can check:
-(void) viewWillAppear:(BOOL)animated
{
if(loadOldContent)
{
//Do your work here
}
}
You don't push UIView instances onto a UINavigationController instance, only instances of UIViewController.

Why does my tabbar controller execute code from a different view controller than the active one?

Firstly, I have set both viewcontrollers to be UITabBarController delegates. Both are part of a tab bar controller. I did this by putting the following code into each viewDidLoad:
self.tabBarController.delegate = self;
Then I added the following delegate method to CalculatorsViewController:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
[self presentCalculatorsView];
}
Where presentCalculators view simply reveals a subview within the same view controller.
I also added the following delegate method to the OptionsViewController:
-(void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
[self presentHomeScreen];
}
Again this method simply reveals another subview within the viewController.
The problem I am having is that the OptionsViewController presentHomeScreen method is only called if I do not visit the CalculatorsViewController. Once I do visit the CalculatorsViewController in the app and then return to OptionsViewController,
[self presentHomeScreen]
is never called. In fact, it appears that it still calls the method from the CalculatorsViewController. I tested it with an NSLog statement.
Any ideas why one method overrides the other? Or why the tab bar button executes code from another viewController, other than the one that is active?
EDIT* It is almost as if the one viewController 'steals' the delegate from the other.
By calling self.tabBarController.delegate = self; on each viewDidLoad method, you are basically telling the tab bar controller to use abandon the current delegate and use the current view controller as delegate.
Note that the viewDidLoad method is called only once under normal circumstances. (It may be called again when the view of your view controller is unloaded due to memory warning, for example, then you access the view of your view controller again, which calls loadView/awakeFromNib and viewDidLoad. I'm not entirely sure on this scenario though.) In your scenario:
Open OptionsViewController for the first time - tab bar controller's delegate is OptionsViewController
Open CalculatorsViewController for the first time - tab bar controller's delegate is now CalculatorsViewController
Go back to OptionsViewController - tab bar controller's delegate is still CalculatorsViewController, as the viewDidLoad is not called again
If you must change the delegate, you can do it instead in the viewWillAppear method.

Resources