UIModalTransitionStylePartialCurl with UITabBarController - iphone

This question has been asked a lot e.g. here but as far as I can see is yet to be answered in full.
I have a UITabBarController with a UINavigationController as the root vc for one of the tabs, which itself has a MKMapView as its root vc. The behaviour I want is for the map to partially curl upwards, while leaving the tab bar in place (similar to the Maps app).
So far all I have managed to get working is for the whole view to curl, which isn't as nice.
Solutions I have seen are to set the hidesBottomBarWhenPushed property to NO, which would make sense however this doesn't seem to work, (unless I am doing something wrong).
For clarity, my code is as follows:
MyVC *aView = [MyVC init];
aView.modalTransitionStyle = UIModalTransitionStylePartialCurl;
aView.hidesBottomBarWhenPushed = NO;
For the presenting part, I have tried the two alternatives below, neither of which seem to work:
[self presentModalViewController:updateStatus animated:YES];
[[self navigationController] presentModalViewController:updateStatus animated:YES];
Any help much appreciated.

I've scoured StackOverflow (and the Internet) for a solution to this problem. The question has been asked many times, but as you note, never sufficiently answered. Many solutions give an acceptable solution if it is unimportant whether, e.g., a lower toolbar curls up as well.
Others have provided a solution using UIView animations / CoreAnimation rather than UIModalTransitionStylePartialCurl as a modal transition style; this is at worst a solution not allowed in the App Store, and at best is not quite the same effect as one gets from UIModalTransitionStylePartialCurl (e.g. the shape of the curl is different).
None of these solutions have provided an answer that mimics Apple's solution in the Maps app (i.e., using UIModalTransitionStylePartialCurl but leaving an un-curled UIToolbar at the bottom of the screen).
I will continue in this tradition of incomplete answers, since you ask about a UITabBarController and my solution doesn't specifically address that case. It does, however, solve the problem I had, which was to get a half page curl with an un-curled toolbar at the bottom.
There must be a more elegant way to do this, but this is how I managed.
The rootViewController of my AppDelegate is a subclass of UIViewController, which I'll call TAContainerViewController. TAContainerViewController manages a) the actual contents of the screen (the "stuff to be curled"), TAContentViewController, and b) the contents "behind" the TAContentViewController (e.g. settings), which I'll call TAUnderCurlViewController.
My instance of TAContainerViewController had properties for a TAContentViewController and a TAUnderCurlViewController. The UIView that was my content was a subview of TAContentViewController's view property; likewise what the user sees under the curl is the view property of the TAUnderCurlViewController.
In the init method of TAContainerViewController I make sure to do the following:
_underCurlVC.modalTransitionStyle = UIModalTransitionStylePartialCurl;
And to curl the contents to reveal under the page, I set up an action that calls this code:
[self.contentVC presentModalViewController:self.underCurlVC animated:YES];`
where self is the TAContainerViewController, contentVC is an instance of TAContentViewController, and underCurlVC is an instance of TAUnderCurlViewController.
To dismiss the view, simply [self.contentVC dismissModalViewControllerAnimated:YES];.
Some strangeness seems to occur with the frame of contentVC when the modal view is dismissed, so I manually reset the frame when the modal view is dismissed.
I've posted a sample project with more details on Github. Hopefully someone can take this and turn it into a slightly more elegant solution, or expand it to work with a UINavigationController or UITabBarController. I think the trick is to pull the View Controllers out of the well-defined relationships in the Cocoa subclasses, so maybe subclassing those specialty View Controllers would do it.

Tim Arnold's response worked great for me, thanks!
One trap to watch out for: your modal page-curl transition will take over the whole screen if your content view controller is added as a child of the container view controller. You could just not add it as a child, but then none of the view lifecycle methods will get called on your content controller (e.g. viewDidLoad, viewWillAppear), which could be a problem.
Fortunately, there is a way around this. In your container controller:
Add your content controller as a child in viewDidLoad
Remove it as a child in viewDidAppear
Re-add it as a child in viewWillDisappear.
That way, your content controller gets its lifecycle methods called, while still being able to do a modal page-curl transition without taking up the whole screen.
Here is the entire code of a bare-bones solution:
#interface XXContainerController : UIViewController
#property (strong, nonatomic) UIViewController *contentController;
#property (nonatomic) BOOL curled;
#end
#implementation XXContainerController
#synthesize contentController = _contentController;
#synthesize curled = _curled;
- (void)viewDidLoad
{
[super viewDidLoad];
self.contentController = [self.storyboard
instantiateViewControllerWithIdentifier:#"SomeControllerInStoryboard"];
// Add content controller as child view controller.
// This way, it will receive all the view lifecycle events
[self addChildViewController:self.contentController];
self.contentController.view.frame = self.view.bounds;
[self.view addSubview:self.contentController.view];
[self.contentController didMoveToParentViewController:self];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Remove the content controller as child view controller.
// This way, the modal page curl transition will
// not take over the whole screen.
// NOTE: need to wait until content controller has appeared
// (which will happen later).
// Achieve this by running the code at the end of the animation loop
[UIView animateWithDuration:0 animations:nil completion:^(BOOL finished) {
[self.contentController removeFromParentViewController];
}];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Add the content controller as child view controller again
// so it receives the view lifecycle events
[self addChildViewController:self.contentController];
}
- (void)setCurled:(BOOL)curled
{
if (curled == _curled) return;
_curled = curled;
// Curl up the content view and show underneath controller's view
if (curled) {
// Note you can specify any modal transition in storyboard
// E.g. page curl, flip horizontal
[self.contentController
performSegueWithIdentifier:#"SomeModalSegueDefinedInStoryboard"
sender:self];
// Uncurl and show the content controller's view again
} else {
[self.contentController dismissModalViewControllerAnimated:YES];
// Have to do this, otherwise the content controller's view
// gets messed up for some reason
self.contentController.view.frame = self.view.bounds;
}
}
#end

Related

Removing a View from same view. Is it good practice.?

I have added following piece of code in a view.
- (IBAction)accept_clicked:(id)sender {
[self.view removeFromSuperview];
self.view = nil;
}
Once accept is clicked, I have removed the own view like this. It worked fine, anyhow is it fine to do like this or It should be removed from another view(parent)?
Don't do this (with self.view) - it doesn't looks good and you might face difficult to find problems. self.view is the main view associated with an UIViewController. So this view to be visible on the screen, you must have shown it somehow: either pushing it to a UINavigationController or presenting it modally with -presentViewController:animated:completion: (IOS5+) or - presentModalViewController:animated:. Showing a view by instantiating a view controller and adding its view to the current view controller's view is not a good practice also:
//Not good
MyViewController *mvc = [[MyViewController alloc] init];
[self.view addSubView:mvc.view];
In your particular case I suppose you are showing some terms and conditions (or something similar) and have an accept and deny button, your best solution would be to present your view controller from somewhere, implement a delegate method, so the presenting view controller can have the result and then in your -accept_clicked: method to use either [self dismissModalViewControllerAnimated:YES] or [self dismissViewControllerAnimated:completion:] (IOS5+),

Question about the mechanics of iPhone view controllers (i.e., explain why this crashes)

I am pretty new to iPhone programming, and was playing around with an app yesterday trying different scenarios with view controllers and nib files. So, I started a new app with a FirstViewController (FVC for short) and an FVC.xib.
I layed out a quick view in FVC.xib and ran the app - view displays, great.
I now wanted to have a second view I could add on top of the main view. So I went ahead and created SecondViewController.xib (SVC) but did not create the .m and .h files. I went about trying to load both these views from the same view controller, and here is where my question lies:
I created a button in FVC.xib and created an IBAction like this:
- (IBAction)loadSVC {
FirstViewController *viewController = [[FirstViewController alloc] initWithNibName:#"SecondViewController" bundle:[NSBundle mainBundle]];
secondView = viewcontroller.view;
[viewController release];
[self.view addSubView:secondView];
}
So this works great and adds the contents of SVC.xib, but when I try and remove that view from the superview, the app crashes:
[secondView removeFromSuperview];
If I actually create a view controller for SVC, use that to instantiate my view in FVC, and move the remove code to the SVC:
[self.view removeFromSuperview];
Everything works. My question - I kind of get why my first method crashes, but I was hoping someone could explain why and what goes on behind the scenes. I'm still a noob with object oriented programming, so what is actually happening in my first case where I create a new instance of FirstViewController and add its view to self.view? Why can't I release it (I assume because the original view is associated with FirstViewController, and when I create a new instance with the second xib it messes everything up) - I'd love a more technical explanation as to what is happening...
Thanks much!!
EDIT to add more info in response to Nick's reply below
Nick - so your answer did clear my thinking a bit in regards to the retain count, etc... I did another test app trying to get this working from a single view controller - think, for example, that I wanted to display an Alert or Welcome message to the user (I know in a real app there are different methods to accomplish this, but this is more of a learning experience) -- so I have my main view # MainViewController and layout my alert message in a xib called alert.xib -- so there is no logic behind the alert message, no reason for it to have a view controller that I can see, my end goal being loading/unloading this on top of my main view from the main view's view controller (or understanding why it is impossible)
I tried this using instance variables as you recommended:
In MainViewController.h:
#import <UIKit/UIKit.h>
UIViewController *secondController;
UIView *secondView;
#interface MainViewController : UIViewController {
}
#property(nonatomic, retain) UIViewController *secondController;
#property(nonatomic, retain) UIView *secondView;
- (IBAction)loadSecond;
- (IBAction)removeSecond;
#end
In MainViewController.m:
#import "MainViewController.h"
#implementation MainViewController
#synthesize secondController, secondView;
- (IBAction)loadSecond {
secondController = [[MainViewController alloc] initWithNibName:#"alert" bundle:[NSBundle mainBundle]];
secondView = secondController.view;
[self.view addSubview:secondView];
}
- (IBAction)removeSecond {
//I've tried a number of things here, like [secondView removeFromSuperview];, [self.secondView removeFromSuperview];, [secondController.view removeFromSuperview];
}
- (void)dealloc {
[secondController release];
[secondView release];
[super dealloc];
}
So - this works to load the alert view, but the removeSecond button does nothing (I did use NSLog to verify the removeSecond method is fired) - why?
Second, and most importantly - is this even possible, or is it horrible practice? Should every nib/view I am manipulating have their own view controller? Am I wrong to think I could just make a new instance of MainViewController and use it to display and remove this no-functionality, very temporary view? (And yes, I realize I could easily create this view programatically or accomplish the end goal in many different ways which would be easier, but I'm trying to really learn this stuff and I think figuring this out will help...
Thanks for the help!
You created a view controller
You accessed its view which caused controller to create the view and call the delegates (i.e. viewDidLoad)
Controller returns the view that you asked for
Now you add the view as a subview which increases its retain count
Controller is released and it releases the view, BUT since view's retain count was increased the view is still there
You try to remove the view, it is unloaded and delegates are to be called (e.g. viewDidUnload), however that messes up since the controller who created the view is released and that piece of memory is... smth else :)
That's why the first method doesn't work.
The second method is NOT correct either but it works because:
You remove controller's view from superview but since controller itself is not released (you didn't call [self release] or anything like that, not saying that you should :), just an example), then the view didn't reach 0 (zero) retain count and is still there - which means its subviews aren't removed
The proper way to do it is to save the reference to the controller as an instance variable (usually declare a synthesized property), and release it only when you are done with the view, making sure that the view is removed from superview before hand. The default templete for a View Based App shows how view controller should be managed
Hope this helps to understand why both methods behave differently
Based on your clarifications, you don't need secondView property or iVar. Also in your loadSecond instead of secontController = bla you need self.secondController = bla, otherwise you simply assign reference to the iVar instead of going through the setter.
Yes, it's possible to load subviews/other resources from a nib without having a dedicated controller
This is how you do it (one of the approaches):
UIView *result = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"MyNibName" owner:owner options:nil];
for ( id o in bundle ) {
if ( [o isKindOfClass:[UIView class]] ) {
result = (UIView *)o;
break;
}
}
Here the result will contain the first UIView in MyNibName. You can use other criteria to find out whether you got the view you wanted (tags, types...)

Same WebView on every view

Basically i have a WebView on SecondViewController and I wish for the WebView to be visible on every view like a tab bar and fully controllable on each view.
Please note the WebView will be on a webpage with a online slideshow so I cannot simply reload on each view
Also in the SecondViewController I have
- (void)webViewDidFinishLoad:(UIWebView *)YouTubePlayer {
I would suggest adding the webView on you window after the you add the tabbarcontroller.view just like:
[window addSubview:tabbarController.view];
[window addSubview:webview];
[window makeKeyAndVisible];
and initially make don't make it visible. You should handle all the webview related methods in the app delegate. Now whenever you don't need it you can hide it by calling the methods your wrote in app delegate from your view controllers.
Hope this helps.
I'd just set up a singleton UIWebView and add it to each view-controller-view when that view controller is about to become visible. Here's one way to do it:
//.h
#interface SharedWebView : UIWebView
{
}
+ (SharedWebView*) shared;
#end
//.m
SharedWebView* g_sharedWebView;
#implementation SharedWebView
+ (SharedWebView*) shared
{
if ( g_sharedWebView == nil )
{
g_sharedWebView = [[SharedWebView alloc] init];
// ... any other intialization you want to do
}
return g_sharedWebView;
}
#end
// in your view controller(s)
#property (readonly) UIWebView* webView
- (UIWebView*) webView
{
return [SharedWebView shared];
}
- (void) viewWillAppear: (BOOL) animated
{
[super viewWillAppear: animated];
[self.view addSubview: self.webView ];
self.webView.frame = CGRectMake(10, 10, 300, 300);
// want to re-set the delegate?
// self.webView.delegate = self;
}
The simplest approach is to just make all of your view controllers aware of this extra view (make the view available through a singleton PinnedViewController or whatever). During each view controller's -viewWillAppear, just do this:
[self addSubview:[[PinnedViewController sharedController] view]];
This will move the view to whoever is currently active (making a view your subview automatically removes you from your old hierarchy).
If that is cumbersome or otherwise unworkable, there are two other options. First, you can subclass UITabViewController (I assume that's what you're using here from your question), and inject your extra view (resizing the content view to make room). This is undocumented and unsupported, so take heed. But it's not incredibly difficult if you don't do too many other fancy tricks.
The other tricky solution is to create a second UIWindow that you float over the main UIWindow (or resize the main UIWindow to make room for it). This is only semi-documented and is also not really supported. But this approach can work if you're trying to put the extra view below the tabbar for instance.
But if your system is simple enough, I recommend just letting your view controllers all manage the pinned view manually. You'll save a lot of code spelunking that way, and you won't have to rely on any undocumented internal view hierarchies.
Sounds like you try to put views on top of this view but not modal. There was this blog entry I once saw that described how you would do something like this. I think it should apply also for your case: semi-modal-transparent-dialogs-on-the-iphone
In iOS UIViewControllers are expected to manage an entire "screen" worth of content so it's not normal to try to share a single view across many view controllers. Trying to have UIViewControllers whose views only manage part of their window is problematic and will result in unexpected behavior as UIKit will not send messages like -viewWillAppear to all view controllers with visible views. Instead you would normally create a single UIViewController whose view includes that web view and whatever other views compose your tab like interface.
Alternately you could have a hierarchy of many view controllers and add a single web view as a subview of all of them. You would then pull your web view delegate behavior out into some non-UIViewController controller class to manage the behavior of the web view.
You can have all your views take up a portion of the screen and have your UIWebView take up the rest. The logic for switching between the other views should remain the same.
For example, in your viewDidLoad method for your UIViewControllers, you could have something like:
self.view.frame = CGRectMake(0, 100, 320, 380);
And in your (say) AppDelegate, you would have your normal call to show the main UIViewController (in your case, it sounds like a UITabBarController?) and also have a call to add the UIWebView. Say, something like:
myWebView.view.frame = CGRectMake(0, 0, 320, 100);
The view controllers and UIWebView would be independent of each other.
You see this pattern, I believe, with apps that have iAds. I've done something just like this with one of my free apps.
Hope this helps!
I got the same effect by simply adding it to the navigationController itself(if you don't have one then just add it).
works great for me in one of my apps.
Could you not use two webviews on your application and simply change the uppermost webview with your more dynamic content?

How do I have a view controller run updating code when it is brought to the top of the stack of views?

I have a viewController (Planner) that loads two view controllers (InfoEditor and MonthlyPlan) when the application starts. MonthlyPlan is hidden behind InfoEditor (on load).
So my question is when I exchange InfoEditor for MonthlyPlan (MonthlyPlan gets brought to the top) how can I have data on the MonthlyPlan view be updated. An NSLog in viewDidLoad is being called when the application starts (which makes sense.) NSLogs in viewDidAppear and viewWillAppear aren't doing anything.
Any ideas?
Thanks!
-- Adding more details --
I'm creating the view hierarchy myself. A simple viewController that is just loading two other viewControllers. The two child viewControllers are loaded at the same time (on launch of application.) To exchange the two views I'm using this code:
[self.view exchangeSubviewAtIndex:1 withSubviewAtIndex:0];
The exchanging of the views is fine. The part that is missing is just some way of telling the subview, you're in front, update some properties.
There's a lack of details here. How are you "exchanging" the two views?
If you were using a UINavigationController as the container then viewWillAppear/viewDidAppear would be called whenever you push/pop a new viewController. These calls are made by the UINavigationController itself. If you ARE using a UINavigationController then make sure you have the prototypes correct for these functions.
- (void)viewWillAppear:(BOOL)animated
If you are trying to implement a view hierarchy yourself then you may need to make these calls yourself as part of activating/deactivating the views. From the SDK page of viewWillAppear;
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.
Update:
With the new details the problem is clear: This is a situation where you must send the disappear/appear messages yourself as suggested by the SDK. These functions are not called automagically when views are directly inserted/removed/changed, they are used by higher-level code (such as UINavigationController) that provides hierarchy support.
If you think about your example of using exchangeSubView then nothing is disappearing, one view just happens to cover the other wholly or partially depending on their regions and opacity.
I would suggest that if you wish to swap views then you really do remove/add as needed, and manually send the viewWillAppear / viewWillDisappear notifications to their controllers.
E.g.
// your top level view controller
-(void) switchActiveView:(UIViewController*)controller animated:(BOOL)animated
{
UIController* removedController = nil;
// tell the current controller it'll disappear and remove it
if (currentController)
{
[currentController viewWillDisapear:animated];
[currentController.view removeFromSuperView];
removedController = currentController;
}
// tell the new controller it'll appear and add its view
if (controller)
{
[controller viewWillAppear:animated];
[self.view addSubView:controller.view];
currentController = [controller retain];
}
// now tell them they did disappear/appear
[removedController viewDidDisappear: animated];
[currentController viewDidAppear: animated];
[removedController release];
}
I would just add an updataData method to each subview and call it at the same time you bring it to the front. You would need to add a variable to your root view controller to track the active subView:
[self.view exchangeSubviewAtIndex:1 withSubviewAtIndex:0];
if (subView1IsActive) [subView1Controller updateData];
else [subView2Controller updateData];

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.