I have MainVC than contains 2 tabs: FirstVC and SecondVC.
Then I tap on some of this taps I want to present below desired View/VC.
I am working on this project with Nib, so I have some confusion about it.
That object should I use here? View? How?
How I have MainVC with 2 View that hidden/shown based on tab.
In FirstVC I need to load tableView. In SecondVC - simpleView
So, can somebody give me some advices how to achieve this thing more cleverly?
You might want to use Container View Controllers, I blogged about that awhile back. (See http://www.notthepainter.com/container-view-controllers/)
Text pasted here for the future:
OS5 added something a lot of iOS developers have been needing, container view controllers. This lets you put a view controller inside another view controller. This is wonderful for countless reasons, but the one that draws me is reuse and abstraction.
I was working on a app which had 2 similar windows, they had a top part (body) and a footer. The top part was easy, they each had their own UIViewControllers. But when I went to add the footer to the second one I got to thinking DRY, I was repeating myself and that’s never a good idea. So I abstracted out a parent class and I went to put the footer code in and I got stuck, I wanted my footers to also be view controllers.
I remembered a talk I attended about new iOS5 features and container view controllers was mentioned. After a bit of googling around I had it.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// set up the footer contained view, this has nothing to do with table footers, this is below
// the tableview
//
FooterViewController *vc = [[FooterViewController alloc] initWithNibName:#"FooterViewController" bundle:nil];
vc.view.frame = self.footerFPOView.frame;
vc.delegate = self;
[self addChildViewController:vc];
[vc didMoveToParentViewController:self];
[self.view addSubview: vc.view];
}
Of course there are a few things to note. First, hooray, I’m loading my footer from a xib file. I’ve placed a UIVIew called footerFPOView in the outer view controller’s xib, this is a trick I use all the time. FPO stands for For Position Only and that lets me use interface builder for positioning. I communicate with a protocol, hence the delegate. And then I call addChildViewController to add it, and then I tell the new one that it has a new parent, and finally, add its view.
This is just a few lines of code yet the window should respond to both view controllers and respond to rotations.
I have a setup that is very similar to a UITabBarController but for various reasons I need to create my own custom container root controller.
I would like to create a custom transition between my two view controllers as specified in the picture (VC 1 & VC 2).
Is it correct to add my two view controllers as children to my master controller and do a view animation on the container views that live within the root view controller? That would mean that both of my view controllers are instantiated at once.
Or do I go about doing something like having VC 1 live as a child view controller on my root view controller while later instantiating VC 2 in my code when the transition takes place. This of course would mean that I would have VC 2 live in my storyboard but not connected to the root view controller and be instantiated through the Storyboard ID. And I would obviously be using the custom transition protocols that we received in iOS 7.
Or is there some other option?
Since they're both contained within one ViewController, you probably won't transition between them using present and dismiss.
I recently had a similar scenario where I finally went with using my own custom transitions from the ContainerViewController.
There are some things to think about when using ViewController containment. You should checkout this link on developer.apple, containing some good practices and examples on View Controller containment and how to animate in between ChildViewControllers.
When it comes to allocation, I'd say it's up to you. Unless these ViewControllers take a lot of memory, I'd probably go with instantiating the first one to be shown, and doing a lazy initialisation (initialise when needed) on the second one, and then retain them both in memory. After a transition has been made, make sure to remove the "unused" ViewController's view from the container and it should be all good.
Here's a simple example to fade from the firstVC's view to the secondVC's view:
[self addChildViewController:self.secondViewController];
[self.view insertSubview:self.secondViewController.view belowSubview:self.firstViewController.view];
[UIView animateWithDuration:0.4 animations:^{
self.firstViewController.view.alpha = 0;
} completion:^(BOOL finished) {
[self.firstViewController willMoveToParentViewController:nil];
[self.firstViewController.view removeFromSuperview];
[self.firstViewController removeFromParentViewController];
[self.secondViewController didMoveToParentViewController:self];
}];
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.
I have finding way to call pushViewController with scaling animation,
like facebook iPhone app main menu icon click animation.
(new viewController is popup from center, and it scales to original size.)
I searched several way to change animation of pushViewController.
First, I tried this:
viewController.view.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
[UIView animateWithDuration:0.5f animations:^{
viewController.view.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
}];
[self.navigationController pushViewController:viewController animated:NO];
but there is problem,
old viewController is disappeared when animation starts, there's only white background.
If I use CATransition to change animation,
I can show both old & new viewController both,
but there's no scaling animation, only move in, push in, fade in animations.
I want to show both new & old view controller like CATransition animations,
and need the way to implement my custom animation.
Here is my last suggestion, dirty way:
[self.view addSubview:viewController.view];
viewController.view.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
[UIView animateWithDuration:0.5f
delay:0.0f
options:UIViewAnimationCurveEaseInOut
animations:^{
viewController.view.alpha = 1.0f;
viewController.view.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
} completion:^(BOOL finished) {
[self.navigationController pushViewController:viewController animated:NO];
[viewController release];
}];
First, I added new viewController.view as subview, I can show animation with new & old view both.
When animation is ended, pushViewController later.
this way I can implement what I thought,
but I think it is dirty way, and there's remain problem:
Navigation bar items are not change immediately, It is changed after animation ends.
Is there any simple, clear way to implement this animation?
I think it is impossible to do that without change original implentation of pushViewController, should I do subclassing pushViewController?
Thanks to read, and your help.
In answer to the original question, I'm not sure if you consider this an improvement or not, but I think you might be able to include the navigator bar in the view that's being transformed (a) in IB, add a navigationBar to your view being transitioned to, (b) animate the hiding of the root navigation controller's navigation bar before you start your animation (so it slides off as your new view is sliding in), and (c) in your completion block of your animation, hide your new view's added navigation bar and unhide the root navigation controller's navigation bar. Maybe this renders something closer to what you intended, though probably different than you originally conceived. It's not perfect, though. The code, might look something like:
MyViewController *newController = [[MyViewController alloc] initWithNibName:#"MyView" bundle:nil];
newController.view.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
[self.navigationController setNavigationBarHidden:YES animated:YES];
[self.view addSubview:newController.view];
[UIView animateWithDuration:0.5f
delay:0.0f
options:UIViewAnimationCurveEaseInOut
animations:^{
newController.view.alpha = 1.0f;
newController.view.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
} completion:^(BOOL finished) {
[newController.view removeFromSuperview];
[self.navigationController pushViewController:newController animated:NO];
[newController.tempBar setHidden:YES];
[self.navigationController setNavigationBarHidden:NO animated:NO];
}];
It's adding a little more "dirt" to your "dirty way", but it might render the effect that you're looking for. You just want to make the temporary navigation bar in your new view to look as much like what the eventual root navigation controller's navigation bar will look like, and the user should be none the wiser. If this still doesn't quite achieve what you're looking for, you can just hide the root navigation controller's bar altogether, and always put your navigation on your views themselves, in which case the effect will be entirely seamless as you transition to your new view.
Alternatively, you could stay with your existing code and just set the title of the current view before you transition, that way it might look like the title change happened up front. But then you probably have to do stuff about resetting it when you return, setting the back button, etc., so it might not be worth it.
As an aside, and this is a little tangential to the original question (so I apologize in advance), but in response to the other suggestion that you shouldn't animate the view controller, but rather only views, I would like to voice a word of caution. Yes, technically that's right, but I'm nervous whether this will encourage people to adopt the bad practice of adding new views by (a) creating a new view controller; (b) animate the adding of that view controller's view to be a subview of the current view; but (c) not doing anything with that new view controller to add it to your view controller hierarchy (such as pushViewController or presentViewController).
If that's what was intended to the other answer, I have to say that I think this is a very bad idea. (If that's not what was intended, then my apologies, but I think it's easy for people to misconstrue the suggestion.) Bottom line, I have recently seen people do things like:
MyViewController *newController = [[MyViewController alloc] initWithNibName:#"MyView" bundle:nil];
// do some animation around adding the new view as a subview of your current view
// but neglect to ever invoke pushViewController or presentViewController
[self.view addSubview:newController.view];
This is not recommended for a couple of reasons. First, if it's an ARC project, your view controller will be discarded when newController falls out of scope. (There seem to be a spate of these sorts of questions being posted on SO resulting from exceptions being thrown as people transition to ARC.) Sure, you can fix this by making newController an ivar of your main controller so it won't fall out of scope, but be careful to remember to set it to nil when the main controller is eventually dealloc'ed or as you remove your subview, or else you can get a leak. And if you're in a non-ARC project, it's even easier to leak, so make sure to release the view controller when the subview is removed to prevent leaks.
Second, and probably more importantly, while the above code seems to work (or at least if you make the controller an ivar), but you end up with a disconnect between your hierarchy of your view controllers and that of your views. (In session 102 of WWDC 2011 about a different topic, view controller containment, it includes a lengthy, yet relevant, discussion of the importance of keeping view controller hierarchies and view hierarchies coordinated, e.g. rotation events may not be properly sent to your new view's controller because the controller isn't in the controller hierarchy, so iOS won't know to send them to your controller.) Bottom line, if you use another view controller's view to add as a subview of your current view, it is fragile, is susceptible to breaking on iOS upgrades, and things like rotation events will not be passed to your new view's view controller properly. If you do so, at least be aware that this is not good practice and make your own risk-assessment as to whether you want to do that. It's not necessary to bypass the proper view controller hierarchy, and I personally would like to dissuade people inclined to do so.
Bottom line, if you're transitioning to a view of a different view controller, you really want to stick to pushViewController or presentViewController, and do your animation around the new controller's view (and usually it's nowhere near as complicated as this animation ... generally it's incredibly simple and I've done all sorts of fade and flip animations with much greater ease ... it's the use of the transform that is making this complicated).
Alternative, if you want to animate a new UIView subview and not deal with view controllers at all, just make sure it's not another view controller's view, but rather, for example, a view you create programmatically that uses the same view controller as your originating view. And if you decide to use another view controller's view as a subview of your existing controller, just do so with the knowledge that it's a little fragile and some events may not be transmitted to your new view controller as you expect and it might not be as "future proof" as you may want.
I don't think that animation is done with a view controller. You can just keep the existing view controller. Create a new UIView that is supposed to zoom in. Add that new UIView to the existing view controller and apply animation to it.