iOS: different addSubview behavior between iOS 4.3 and 5.0 - iphone

while coding in iOS 4.3 before, I found while add a view controller's view to another view with [superview addSubView:controller.view], the controller instance will not receive the -viewWillAppear/viewDidAppear message, than I found same issue in some thread in stack overflow. After that, I manually call -viewWillAppear/-viewDidAppear as needed.
but, after upgrade to iOS 5.0, some frisky UIView behavior happened. Finally I found that in iOS 5, the [superview addSubView:controller.view] , will send a -viewWillAppear/-viewDidAppear message to the controller instance automatically, plus my manually calls, there are two duplicated message each time the controller action its behavior.
and I also found a similar issue: iOS 5 : -viewWillAppear is not called after dismissing the modal in iPad
Now, the problem is, after search apple's documents, I didn't find any explicitly doc for diff about these issues. I even wonder if this is a guaranteed view life cycle behavior in iOS 5.0 .
Does anyone fix similar issues or find some guidelines about these difference. cause I want to run my app both in 4.x & 5.x iOS.

In iOS 4 you had to manually call -viewWillAppear, -viewWillDisappear, etc. when adding or removing a view from your view hierarchy. These are called automatically in iOS 5 if the view is being added or removed from the window hierarchy. Fortunately, iOS 5 has a method in UIViewController that you can override to revert the behaviour back to how it worked with iOS 4. Just add this to your UIViewController:
-(BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
return NO;
}
This is probably the easiest solution as long as you're supporting both iOS 4 and iOS 5. Once you drop support for iOS 4 you might consider modifying your code to use the newer approach when swapping views.
Edit 5 February 2012
Apparently this function requires the child view controller be added to the main view controller using the addChildViewController: method. This method doesn't exist in iOS4, so you need to do something like this:
if ([self respondsToSelector:#selector(addChildViewController:)] ) {
[self addChildViewController:childViewController];
}
Thanks to everyone who corrected me on this.

This may not be an answer what you want, but I had same kind of problem.
In my case, when I added a view controller's view to another view controller's view as a subview, the subview was received viewWillAppear only in iOS 5.0 not iOS 4.X.
So I added a nasty condition.
[self.view addSubview:self.viewController.view];
if ([[[UIDevice currentDevice] systemVersion] compare:#"5.0"] == NSOrderedAscending) {
[self.viewController viewWillAppear:animated];
}
From iOS 5.0, Apple provides a way to implement custom container view controllers like UINavigationController or UITabController. I think this change affects when viewWillAppear is called.
This problem may be solvable if we use -[UIViewController addChildViewController:].

The answers above a slightly incomplete.
Let's presume you have 2 view controllers, ControllerA, and ControllerB.
ControllerA.view is already added to the window(it is the parent), and you want to add ControllerB.view as a subview of ControllerA.
If you do not add ControllerB as a child of ControllerA first, the automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers will be ignored, and you will still be called by iOS5, meaning that you'll call your view controller callbacks twice.
Example in ControllerA:
- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
return NO;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.controllerB = [[ControllerB alloc] initWithNibName:#"ControllerB" bundle:nil];
[self.view addSubview:self.controllerB.view];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.controllerB viewWillAppear:animated];
}
In ControllerB NSLogging in viewWillAppear:
- (void)viewWillAppear:(BOOL)animated
{
NSLog("#ControllerB will appear");
}
This will result in iOS5 only displaying that NSLog message twice. i.e. You're automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers has been ignored.
In order to fix this, you need to add controllerB as a child of controller a.
Back in ControllerA's class:
- (void)viewDidLoad
{
[super viewDidLoad];
self.controllerB = [[ControllerB alloc] initWithNibName:#"ControllerB" bundle:nil];
if ([self respondsToSelector:#selector(addChildViewController:)])
[self addChildViewController:self.controllerB];
[self.view addSubview:self.controllerB.view];
}
This will now work as expected in both iOS4 and iOS5 without resorting to the horrible hack of checking iOS version strings, but instead checking on if the function we're after is available.
Hope this helps.

It is iOS5 behavior:
viewWillAppear, viewDidAppear, ... are executed automatically after addSubView: for iOS5.
So for iOS5 no need to execute manually those methods as need for iOS<5.0.
The fix may be:
if ([[UIDevice currentDevice].systemVersion doubleValue] < 5.0) {
...execute viewWillAppear or other
}

By this method u know which os u use and put condition if is less then 5.0 or other one
[[UIDevice currentDevice] systemVersion]

view{Will,Did}Appear, view{Will,Did}Disappear are functions on View Controllers and not views. These functions are called by SDK provided view controllers that are supposed to manage other view controllers e.g. UITabBarController, UINavigationBarController.
If you are managing sub-view controllers yourself, you have to call these explicitly (and in proper order - though you should have a very good reason to do this). A modal view not getting these calls upon dismissal of a modal view is simply because there is no one there to call it. Encapsulate the root view controller in a UINavigationController (and hide the navigation bar if you like) and then open a modal view controller. Upon its dismissal, or pop, viewWillAppear will get called.

After reviewing all the evidence, I think the best thing to do is NOT use viewDidAppear etc for views that are affected by this ios 4 / ios 5 bug. Instead make a custom class (like viewDidAppearCustom) and call it yourself. this way you can guarantee that apple won't change the sdk again and mess you up. There is a great blog covering this issue here:
http://gamesfromwithin.com/view-controller-notification-changes-on-ios5

Related

Autorotation in iOS6

I am updating my app for iOS 6 and having issues with the changes to autorotation. My app has a bunch of view controllers and all of them should only support the portrait layout except 1 which should support all 3 orientations except upside down.
If I add the application:supportedInterfaceOrientationsForWindow: method to the app delegate do I have to add conditions there to check if im displaying the one VC I want to be able to rotate?
The documentation states that if I implement supportedInterfaceOrientations on a VC it should override the app delegate method but this doesn't appear to be the case. I have an log statement in the method on the child VC and it is called once when the VC loads but its not called when I rotate the device, but the method in the app delegate is.
If I completely remove the method from the app delegate the orientation of my VC's seems to be completely dependent on my apps supported interface orientation settings. This of course seems to be due to the method supportedInterfaceOrientations being called once on creation of the VC but never when the device is rotated.
Does anyone have any ideas or suggestions? It would be much appreciated.
Replace
[window addSubview:viewController.view];
with
window.rootViewController = viewController;
You also need to override - (BOOL) shouldAutorotate and return "YES". This makes it so you declare what orientations your VC supports with "supportedInterfaceOrientations" and then on rotation it should call "shouldAutorotate". If you have any navigation controller or tabbar you may need to subclass those to do the same thing within them. I had this issue recently myself.
try this...
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
{
// here to implement landscope code
}
else
{
// here to implement setframePortrait
}
}

viewWillAppear being called twice in iOS5

I'm running all my apps to make sure it's not just one app, and in every app I have, when I run on the iOS5 simulator or device, the viewWillAppear method gets called twice on every view. I have a simple NSLog(#"1");, and this appears twice in my console every time. Is this just me, or is something going on? (It only gets called once in iOS4)
This is the code calling the view that calls viewWillAppear twice:
CloseDoorViewController *closeVC;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
closeVC = [[ CloseDoorViewController alloc] initWithNibName:#"CloseDoorViewIpad" bundle:nil];
} else {
closeVC = [[ CloseDoorViewController alloc] initWithNibName:#"CloseDoorViewController" bundle:nil];
}
[self.view addSubview:closeVC.view];
[self presentModalViewController:closeVC animated:NO];
It's the -addSubview: method.
When adding or removing view controller's view, someone must call 'View Event' methods such as -viewWillAppear:, etc. of the view controller.
Actually, it wasn't a recommended way to -addSubview:/-removeFromSuperView view controller's view by yourself before iOS 5, because it doesn't call 'View Event' methods (you can/should call it by yourself). Instead, it was recommended to use 'indirect' way to do that, such as -presentModalViewController: you use (it does call 'View Event' methods on your behalf).
On iOS 5, Apple has changed the behavior of -addSubview:/-removeFromSuperView methods to allow direct view management of view controller. So now, when you use those methods on viewController's view, 'View Event' methods will be called automatically.
So it was called twice.
See video "Implementing UIViewController Containment" on here also.
Because you are displaying the view twice.
First time by adding the view as a subview of the current view:
[self.view addSubview:closeVC.view];
Second time by pushing the view's controller on top of current view's controller:
[self presentModalViewController:closeVC animated:NO];
I'm not sure why in iOS4 the viewWillAppear was only called once, because iOS5 is correct to call it twice, given that you are displaying the view twice as explained above.
Just remove one of the lines and it would be fine (I'd recommend removing the addSubview and retain the presentModalViewController one).
If you want to restore the old (iOS 4) behavior in your view controller you should implement the following method:
- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
return NO;
}
Update: since you have edited your question to include a code sample, it is clear what the problem is. Lukman's answer above is correct/excellent.
Original answer before code was included:
I would try putting a breakpoint (or log statement) in the -init method of this class. If that is hit twice, then two view controllers are being created.
(note if you have not already overridden the -init method in this class, make sure you override the designated initializer which is -[UIViewController initWithNibName:bundle:])
http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIViewController_Class/Reference/Reference.html

Multiple ViewController different auto rotate requirements

I have a couple of views all managed by their own controllers, some of them nested.
I'd like to support rotation, some views are allowed to rotate to any orientation, some only to one of the portrait orientations (normal or upside down).
In my case, I need to implement -shouldAutorotateToInterfaceOrientation in my rootController to allow rotation for any of the subviews. The problem is, the rootController does not know if it should allow rotation because it needs to ask this to the subviews controller.
In my rootController's -shouldAutorotateToInterfaceOrientation I could do something like:
return [self.settingsController shouldAutorotateToInterfaceOrientation];
to provide the necessary logic of rotation but would this be the correct way to do this?
I did read apple's doc about rotation, but this is not really adressed.
For future reference I'll answer my own question.
My problem was that I had nested viewControllers and I displayed the view of a sub level viewController by calling something like:
self.view = _subLevelViewController.view;
or
[self.view addSubview:_subLevelViewController.view];
Apparently, nesting viewController like that is not what Apple intents for you to do.
You should stick with 1 "root viewController" and you should display other viewControllers using methods like:
[self presentModalViewController:_subLevelViewController animated:YES];
More info on the subject and a very good read:
http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/
I found this post and the referenced blog the most concise guidance on doing nested view controllers. It's worth updating:
// "self" is the root view controller.
if ([self respondsToSelector:#selector(presentViewController:animated:completion:)])
// The following is not available until iPhone 5.0:
[self presentViewController:self.subViewController animated:YES completion:NULL];
else
// For iOS 4.3 and earlier, use this (deprecated in 5.0):
[self presentModalViewController:self.subViewController animated:YES];
I've left it null here, but note that the new method allows you to send an inline function via the completion: param. Per the class ref, it will be called after subViewController's viewDidAppear: runs.

Getting "Using two-stage rotation animation" warning with UIImagePickerController

I wrote simple code to test UIImagePickerController:
#implementation ProfileEditViewController
- (void)viewDidLoad {
[super viewDidLoad];
photoTaker_ = [[UIImagePickerController alloc] init];
photoTaker_.delegate = self;
photoTaker_.sourceType = UIImagePickerControllerSourceTypeCamera;
photoTaker_.showsCameraControls = NO;
}
- (void)viewDidAppear: (BOOL)animated {
[self presentModalViewController: photoTaker_ animated: NO];
}
#end
And I'm getting strange warnings like the following:
2010-05-20 17:53:13.838 TestProj[2814:307] Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
2010-05-20 17:53:13.849 TestProj[2814:307] Using two-stage rotation animation is not supported when rotating more than one view controller or view controllers not the window delegate
Got any idea what this is about? Thanks a lot in advance!
This message will appear if you are presenting the UIImagePickerController within another UIViewController. Because it isn't pushed like a UINavigationController stack, there is confusion at the UIWindow level. I don't know if the warning is a problem, but to eliminate the warning you can do the following:
// self = a UIViewController
//
- (void) showCamera
{
cameraView = [[UIImagePickerController alloc] init];
[[[UIApplication sharedApplication] keyWindow] setRootViewController:cameraView];
[self presentModalViewController:cameraView animated:NO];
}
- (void) removeCamera
{
[[[UIApplication sharedApplication] keyWindow] setRootViewController:self];
[self dismissModalViewControllerAnimated:NO];
[cameraView release];
}
Perhaps you are adding the root UIViewController's view as a subview of the window instead of assigning the view controller to the window's rootController property?
IT ALL FALLS BACK ON THE UI
This warning can be implemented for several different objects: Pickers, keyboard, etc.
I have found that it is related to the UI taking two steps to complete a transition or other animation. Or for any instance where the UI is trying to finish one thing and is being asked to execute another before it has finished. (therefore it covers a wide range of possible triggers)
I have seen the warning appear on 4.0 & 4.2. In my case I was working with rotating the device and catching whether the keyboard was still up-(i.e. text field was still first responder). If so, the keyboard needed to stay up for between the views, but this presented other complications with other views.
Therefore, I implemented a BOOL tracker to keep track if keybaordIsPresent, and if so I was {textfield resignFirstResponder]; when detecting the orientation change and the reset the textfield to becomeFristResponder after the transition that was wrapped in an Animation Block. My BOOL tracker worked better, I still use the NSNotifications for the Keyboard, but there were overlaps of notifications during rotations because the keyboard was being dismissed without requesting such. The BOOL is set to NO on Load, and when the [textfield resignFirstResponder]; is implemented. *not when "-(void)keyboardWillhide is trigger by the NSNotifications, which gives me both working triggers that never conflict. The BOOL is set to YES, only when the user touches textfield which automatically triggers becomeFirstResponder.
I removed the warning by taking the [textfild resignFirstResponder]; out of the
-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//if (keyboardIsPresent == YES) {[self.entryTextField resignFirstResponder];}
}
and placing it back at the top of the code for the:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
if (keyboardIsPresent == YES) {
[self.entryTextField resignFirstResponder];
}
//Determine Which Orientation is Present:
if((fromInterfaceOrientation == UIInterfaceOrientationPortrait) || (fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)){
//LANDSCAPE VIEW:
[self configureLandscapeView];
}else {
//PORTRAIT VIEW:
[self configurePortraitView];
}
}
**Even though I had no code inside the -(void)willAnimatFirstHalfOfRotationToInterface:, the warning was still popping up. I think the warning was still popping up because the compiler still has to attempt the method while it is trying to execute the first animation and therefore gets the double animation call or overlap of resources. It doesn't know that there is no executable code with the method until after it runs through it. And by that time it already set aside resource in preparation for handling possible actions within the method.
**To ellimiate the warning I had to remove or nil out the code for the willAnimateFirstHalfOfRotation, so that the compiler does not have to even check to see if there is a possible 2nd animation or action that may need to be executed at the same time.
/*-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//if (keyboardIsPresent == YES) {[self.entryTextField resignFirstResponder];}}*/
After the transition is completed, within the original animation block I check to see if the "keyboardIsPresent" was YES prior to the rotation, and if so, I resign the First Responder once again. I use setAnimationDuration:0.3which comes out pretty clean and not jumpy.
Well, you are presenting UIImagePickerController modally inside viewDidAppear of ProfileEditViewController.
Think about this. That means when ProfileEditViewController view appears, the UIImagePickerController appears, say later you dismiss UIImagePickerController and it goes back to ProfileEditViewController, then viewDidAppear is called again and UIImagePickerController appears, say later you dismiss UIImagePickerController and it goes back to ProfileEditViewController, then viewDidAppear is called again and.... you get the idea.
That warning is rather cryptic though, not sure if that is what it's trying to tell you. I would suggest making a button somewhere on the ProfileEditViewController that calls presentModalViewController and also make sure you have a way to dismiss the UIImagePickerController (I've never used it not sure if it has one automatically).
You may be trying to present two modal view controllers at the same time, and they are fighting for animation resources.
1) There is rarely any UI reason to do this. You could instead just go directly to the second view controller (the image picker); and, after dismissing it, then present the first view or view controller.
2) If you do want two stacked view controllers or a view controller on top of a view, then set a timer in viewDidAppear to present the second view controller after the first one has finished it's animation. (You could display a dummy png image of a blank picker in the first one to prevent too much display flashing until the second view controller goes live.)
EDIT - Added a random code example:
I might try substituting this as an experiment:
- (void)foo {
[self presentModalViewController: photoTaker_ animated: NO];
}
- (void)viewDidAppear: (BOOL)animated {
NSTimer *bar = [ NSTimer scheduledTimerWithTimeInterval: (2.0f)
target: self
selector: #selector(foo)
userInfo: nil
repeats:NO ];
}
A shorter time delay may work as well.
I just had the same problem. In my case was a silly mistake that I'm putting here just in case anyone else falls into that same issue.
In my tabbed app I remove one of the original ViewControllers and added a new one with Storyboard to create a "Settings" section.
This new VC had to be a table view VC and even I designed, compiled and run it without a problem, when I changed the orientation of the app I kept getting this “Using two-stage rotation animation” error.
My problem was that I forgot to change in the original .h file interface "UIViewController" for "UITableViewController".
Once this was done I changed on the Storyboard identity badge the class from the general value to my SettingsViewController and that was the end of it.
I hope it can help someone else. It took me a while to get to bottom of this.
Cheers,
I think the warning here is about Core Animation performance. As a test, I loaded the image picker without any action sheet or other animations going on and the warnings are still there. I think these are warnings coming from the image picker class itself and not from any misuse of the API.

iPhone viewWillAppear not firing

I've read numerous posts about people having problems with viewWillAppear when you do not create your view hierarchy just right. My problem is I can't figure out what that means.
If I create a RootViewController and call addSubView on that controller, I would expect the added view(s) to be wired up for viewWillAppear events.
Does anyone have an example of a complex programmatic view hierarchy that successfully receives viewWillAppear events at every level?
Apple's Docs state:
Warning: If the view belonging to a view controller is added to a view hierarchy directly, the view controller will not receive this message. If you insert or add a view to the view hierarchy, and it has a view controller, you should send the associated view controller this message directly. Failing to send the view controller this message will prevent any associated animation from being displayed.
The problem is that they don't describe how to do this. What does "directly" mean? How do you "indirectly" add a view?
I am fairly new to Cocoa and iPhone so it would be nice if there were useful examples from Apple besides the basic Hello World crap.
If you use a navigation controller and set its delegate, then the view{Will,Did}{Appear,Disappear} methods are not invoked.
You need to use the navigation controller delegate methods instead:
navigationController:willShowViewController:animated:
navigationController:didShowViewController:animated:
I've run into this same problem. Just send a viewWillAppear message to your view controller before you add it as a subview. (There is one BOOL parameter which tells the view controller if it's being animated to appear or not.)
[myViewController viewWillAppear:NO];
Look at RootViewController.m in the Metronome example.
(I actually found Apple's example projects great. There's a LOT more than HelloWorld ;)
I finally found a solution for this THAT WORKS!
UINavigationControllerDelegate
I think the gist of it is to set your nav control's delegate to the viewcontroller it is in, and implement UINavigationControllerDelegate and it's two methods. Brilliant! I'm so excited i finally found a solution!
Thanks iOS 13.
ViewWillDisappear, ViewDidDisappear, ViewWillAppear and
ViewDidAppear won't get called on a presenting view controller on
iOS 13 which uses a new modal presentation that doesn't cover the
whole screen.
Credits are going to Arek Holko. He really saved my day.
I just had the same issue. In my application I have 2 navigation controllers and pushing the same view controller in each of them worked in one case and not in the other. I mean that when pushing the exact same view controller in the first UINavigationController, viewWillAppear was called but not when pushed in the second navigation controller.
Then I came across this post UINavigationController should call viewWillAppear/viewWillDisappear methods
And realized that my second navigation controller did redefine viewWillAppear. Screening the code showed that I was not calling
[super viewWillAppear:animated];
I added it and it worked !
The documentation says:
If you override this method, you must call super at some point in your implementation.
I've been using a navigation controller. When I want to either descend to another level of data or show my custom view I use the following:
[self.navigationController pushViewController:<view> animated:<BOOL>];
When I do this, I do get the viewWillAppear function to fire. I suppose this qualifies as "indirect" because I'm not calling the actual addSubView method myself. I don't know if this is 100% applicable to your application since I can't tell if you're using a navigation controller, but maybe it will provide a clue.
Firstly, the tab bar should be at the root level, ie, added to the window, as stated in the Apple documentation. This is key for correct behavior.
Secondly, you can use UITabBarDelegate / UINavigationBarDelegate to forward the notifications on manually, but I found that to get the whole hierarchy of view calls to work correctly, all I had to do was manually call
[tabBarController viewWillAppear:NO];
[tabBarController viewDidAppear:NO];
and
[navBarController viewWillAppear:NO];
[navBarController viewDidAppear:NO];
.. just ONCE before setting up the view controllers on the respective controller (right after allocation). From then on, it correctly called these methods on its child view controllers.
My hierarchy is like this:
window
UITabBarController (subclass of)
UIViewController (subclass of) // <-- manually calls [navController viewWill/DidAppear
UINavigationController (subclass of)
UIViewController (subclass of) // <-- still receives viewWill/Did..etc all the way down from a tab switch at the top of the chain without needing to use ANY delegate methods
Just calling the mentioned methods on the tab/nav controller the first time ensured that ALL the events were forwarded correctly. It stopped me needing to call them manually from the UINavigationBarDelegate / UITabBarControllerDelegate methods.
Sidenote:
Curiously, when it didn't work, the private method
- (void)transitionFromViewController:(UIViewController*)aFromViewController toViewController:(UIViewController*)aToViewController
.. which you can see from the callstack on a working implementation, usually calls the viewWill/Did.. methods but didn't until I performed the above (even though it was called).
I think it is VERY important that the UITabBarController is at window level though and the documents seem to back this up.
Hope that was clear(ish), happy to answer further questions.
As no answer is accepted and people (like I did) land here I give my variation. Though I am not sure that was the original problem. When the navigation controller is added as a subview to a another view you must call the viewWillAppear/Dissappear etc. methods yourself like this:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[subNavCntlr viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[subNavCntlr viewWillDisappear:animated];
}
Just to make the example complete. This code appears in my ViewController where I created and added the the navigation controller into a view that I placed on the view.
- (void)viewDidLoad {
// This is the root View Controller
rootTable *rootTableController = [[rootTable alloc]
initWithStyle:UITableViewStyleGrouped];
subNavCntlr = [[UINavigationController alloc]
initWithRootViewController:rootTableController];
[rootTableController release];
subNavCntlr.view.frame = subNavContainer.bounds;
[subNavContainer addSubview:subNavCntlr.view];
[super viewDidLoad];
}
the .h looks like this
#interface navTestViewController : UIViewController <UINavigationControllerDelegate> {
IBOutlet UIView *subNavContainer;
UINavigationController *subNavCntlr;
}
#end
In the nib file I have the view and below this view I have a label a image and the container (another view) where i put the controller in. Here is how it looks. I had to scramble some things as this was work for a client.
Views are added "directly" by calling [view addSubview:subview].
Views are added "indirectly" by methods such as tab bars or nav bars that swap subviews.
Any time you call [view addSubview:subviewController.view], you should then call [subviewController viewWillAppear:NO] (or YES as your case may be).
I had this problem when I implemented my own custom root-view management system for a subscreen in a game. Manually adding the call to viewWillAppear cured my problem.
Correct way to do this is using UIViewController containment api.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
UIViewController *viewController = ...;
[self addChildViewController:viewController];
[self.view addSubview:viewController.view];
[viewController didMoveToParentViewController:self];
}
I use this code for push and pop view controllers:
push:
[self.navigationController pushViewController:detaiViewController animated:YES];
[detailNewsViewController viewWillAppear:YES];
pop:
[[self.navigationController popViewControllerAnimated:YES] viewWillAppear:YES];
.. and it works fine for me.
A very common mistake is as follows.
You have one view, UIView* a, and another one, UIView* b.
You add b to a as a subview.
If you try to call viewWillAppear in b, it will never be fired, because it is a subview of a
iOS 13 bit my app in the butt here. If you've noticed behavior change as of iOS 13 just set the following before you push it:
yourVC.modalPresentationStyle = UIModalPresentationFullScreen;
You may also need to set it in your .storyboard in the Attributes inspector (set Presentation to Full Screen).
This will make your app behave as it did in prior versions of iOS.
I'm not 100% sure on this, but I think that adding a view to the view hierarchy directly means calling -addSubview: on the view controller's view (e.g., [viewController.view addSubview:anotherViewController.view]) instead of pushing a new view controller onto the navigation controller's stack.
I think that adding a subview doesn't necessarily mean that the view will appear, so there is not an automatic call to the class's method that it will
I think what they mean "directly" is by hooking things up just the same way as the xcode "Navigation Application" template does, which sets the UINavigationController as the sole subview of the application's UIWindow.
Using that template is the only way I've been able to get the Will/Did/Appear/Disappear methods called on the object ViewControllers upon push/pops of those controllers in the UINavigationController. None of the other solutions in the answers here worked for me, including implementing them in the RootController and passing them through to the (child) NavigationController. Those functions (will/did/appear/disappear) were only called in my RootController upon showing/hiding the top-level VCs, my "login" and navigationVCs, not the sub-VCs in the navigation controller, so I had no opportunity to "pass them through" to the Nav VC.
I ended up using the UINavigationController's delegate functionality to look for the particular transitions that required follow-up functionality in my app, and that works, but it requires a bit more work in order to get both the disappear and appear functionality "simulated".
Also it's a matter of principle to get it to work after banging my head against this problem for hours today. Any working code snippets using a custom RootController and a child navigation VC would be much appreciated.
In case this helps anyone. I had a similar problem where my ViewWillAppear is not firing on a UITableViewController. After a lot of playing around, I realized that the problem was that the UINavigationController that is controlling my UITableView is not on the root view. Once I fix that, it is now working like a champ.
I just had this problem myself and it took me 3 full hours (2 of which googling) to fix it.
What turned out to help was to simply delete the app from the device/simulator, clean and then run again.
Hope that helps
[self.navigationController setDelegate:self];
Set the delegate to the root view controller.
In my case problem was with custom transition animation.
When set modalPresentationStyle = .custom viewWillAppear not called
in custom transition animation class need call methods:
beginAppearanceTransition and endAppearanceTransition
For Swift. First create the protocol to call what you wanted to call in viewWillAppear
protocol MyViewWillAppearProtocol{func myViewWillAppear()}
Second, create the class
class ForceUpdateOnViewAppear: NSObject, UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool){
if let updatedCntllr: MyViewWillAppearProtocol = viewController as? MyViewWillAppearProtocol{
updatedCntllr.myViewWillAppear()
}
}
}
Third, make the instance of ForceUpdateOnViewAppear to be the member of the appropriate class that have the access to the Navigation Controller and exists as long as Navigation controller exists. It may be for example the root view controller of the navigation controller or the class that creates or present it. Then assign the instance of ForceUpdateOnViewAppear to the Navigation Controller delegate property as early as possible.
In my case that was just a weird bug on the ios 12.1 emulator. Disappeared after launching on real device.
I have created a class that solves this problem.
Just set it as a delegate of your navigation controller, and implement simple one or two methods in your view controller - that will get called when the view is about to be shown or has been shown via NavigationController
Here's the GIST showing the code
ViewWillAppear is an override method of UIViewController class so adding a subView will not call viewWillAppear, but when you present, push , pop, show , setFront Or popToRootViewController from a viewController then viewWillAppear for presented viewController will get called.
My issue was that viewWillAppear was not called when unwinding from a segue. The answer was to put a call to viewWillAppear(true) in the unwind segue in the View Controller that you segueing back to
#IBAction func unwind(for unwindSegue: UIStoryboardSegue, ViewController subsequentVC: Any) {
viewWillAppear(true)
}
I'm not sure this is the same problem that I solved.
In some occasions, method doesn't executed with normal way such as "[self methodOne]".
Try
- (void)viewWillAppear:(BOOL)animated
{
[self performSelector:#selector(methodOne)
withObject:nil afterDelay:0];
}
You should only have 1 UIViewController active at any time. Any subviews you want to manipulate should be exactly that - subVIEWS - i.e. UIView.
I use a simlple technique for managing my view hierarchy and have yet to run into a problem since I started doing things this way. There are 2 key points:
a single UIViewController should be used to manage "a screen's worth"
of your app
use UINavigationController for changing views
What do I mean by "a screen's worth"? It's a bit vague on purpose, but generally it's a feature or section of your app. If you've got a few screens with the same background image but different overlays/popups etc., that should be 1 view controller and several child views. You should never find yourself working with 2 view controllers. Note you can still instantiate a UIView in one view controller and add it as a subview of another view controller if you want certain areas of the screen to be shown in multiple view controllers.
As for UINavigationController - this is your best friend! Turn off the navigation bar and specify NO for animated, and you have an excellent way of switching screens on demand. You can push and pop view controllers if they're in a hierarchy, or you can prepare an array of view controllers (including an array containing a single VC) and set it to be the view stack using setViewControllers. This gives you total freedom to change VC's, while gaining all the advantages of working within Apple's expected model and getting all events etc. fired properly.
Here's what I do every time when I start an app:
start from a window-based app
add a UINavigationController as the window's rootViewController
add whatever I want my first UIViewController to be as the rootViewController of the nav
controller
(note starting from window-based is just a personal preference - I like to construct things myself so I know exactly how they are built. It should work fine with view-based template)
All events fire correctly and basically life is good. You can then spend all your time writing the important bits of your app and not messing about trying to manually hack view hierarchies into shape.