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.
Related
In my app, sometimes pushViewController fails for no reason and what happens is very weird. The navigationBar and navigationItem change but the ViewController is not pushed. Then I can tap nothing on the screen. I find that viewWillAppear is called but viewDidAppear isn't called. I push the home button of iPhone to enter background. After entering foreground again, the ViewController is pushed and viewDidAppear is called. I don't know why and when it happens.
normal viewDidAppear callstack
viewDidAppear after enterBackground callstack
If you can repro by:
Try using the left edge pop gesture when there are no view
controllers below it (i.e on root view controllers, your VC-Home
controller)
Try clicking on any UI elements after this.
Then Disable interactivePopGestureRecognizer when current viewController is the firstVC in navigation controller.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
reference:
iOS App Freezes on PushViewController
Is the viewcontroller which you pushed into the view hierarchy overwrite the viewWillAppear/viewDidAppear accidently without calling [super viewWillAppear/viewDidAppear:animated]?
You're probably accidentally calling [super viewDidLoad] inside your viewWillAppear method
For anyone having the same issue as me: Check all your custom views to see if you're not having an infinite loop of layoutSubviews. This is on of the things that happens in between a viewWillAppear and a viewDidAppear.
In my implementation I had a custom Tab bar controller and fore some reason viewDid Appear was empty, putting super calling solved it.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) //was missing this line
}
You know,if you overwrite navigationController.interactivePopGestureRecognizer.delegate and not restore it appropriately, this bug will appear,too
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
}
I'm implementing my own 'back' button. Where onClick, the following code is executed in the ViewController (VC) being dismissed:
Dismiss current VC (VC#1)
Pop current VC (VC#1) off my custom navigationStack
Get the last VC (VC#2) from the navigationStack, and present it using
presentViewController
What happens is the back works visually works - i.e. current VC disappears, previous VC appears. However, the viewDidLoad method is not called. So the screen isn't updated with data updates from viewDidLoad.
[self dismissCurrentViewController:self completion:^{
[TWStatus dismiss];
FHBaseViewController *vcToDisplay = [[FHDataManager sharedInstance] popNavigationStack];
[vcToDisplay.homeVC presentViewController:vcToDisplay animated:NO completion: ^{ }];
}];
Questions:
I was under the impression that viewDidLoad always gets called when presentViuewController is used??
I 'build' the screen using a method called ONLY from viewDidLoad in VC#2. How is iOS displaying the screen without coming into viewDidLoad?
btw, I'm not using storyboards. Any help is appreciated!
My guess is that viewWillAppear is being called but viewDidLoad is not, at least not when you expect it is. viewDidLoad should be called once, but depending on how you're managing the view controllers, viewDidLoad may not be triggered every time your view appears (which happens after loading).
The completion handler is called after the viewDidAppear: method is called on the presented view controller. from presentViewController doc
so put this in your code with a breakpoint on the call to super and verify it is getting called when this transition occurs.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
edit: since you verified that viewWillAppear is getting called, then I would say that it's coming down to how you are managing the view controller life cycle. Even with a standard UINavigationController, viewDidLoad is not called when a view is shown as a result of popping items on the navigation stack. I would move your logic to viewWillAppear if you are dead set on not using UINavigationController
When I make a back button pragmatically I use:
[self.navigationController popViewControllerAnimated:YES];
This will invoke the viewDidLoad method. Use that instead of your current code.
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)
I've a bunch of turn based games in my app, and I use the same animations to declare the starting player.
At the very end of viewDidLoad, I placed the code for declaration. It takes the screenshot of current view then blurs it a little, and labels appear to show the name of the starting player. The issue is sometimes it happens to fast that I got the screenshot of previous view and labels appear on the blurred screenshot of previous view.
My viewDidLoad looks like this:
-(void) viewDidLoad
{
[super viewDidLoad];
[self initializeThings];
[self layoutUI]; //In some of the games this part requires heavy processing,
//ie laying out a 2D array of buttons (20x20=400 of them)
[self showStartingPlayer];
}
I use the default transition style cover vertical in all VCs. I tried calling [self showStartingPlayer]; deferred by using performSelector with delay but different devices require different delay values so it is not a robust solution. Is there any other method I can use in viewcontroller lifecycle instead of viewDidLoad or any practical way of doing such a thing?
if you are using presentViewController: animated: completion:, i would take advantage of the completion block to notify the view controller that the transition is complete.
for example, you could add a public method called -(void)wasJustPresented to your view controller which calls the necessary UI layout.
Then, call this in your completion block. Ex:
[self presentViewController:newVC
animated:YES
completion:^(void){
[newVC wasJustPresented];
}];
This will ensure your view controller is notified right after it is done being presented.
viewDidLoad is called when the view of the view controller has been loaded, but it doesn't mean that it's actually visible on the screen.
You may use - (void) viewDidAppear: to do that.
You should try to call your method inside the viewDidAppear which is called as the view transition has finished.
-(void)viewDidAppear:animated
{
[super viewDidAppear:animated];
//put your call here
}