What I want to do is present a viewController that comes from the bottom and has a custom size. Whatever I seem to do either looks naff (e.g its animated up, flashes then goes transparent), is fullscreen or blacks out the whole screen. I have read a few solutions with custom delegates handing the animation but there must be a simple way. Also a lot of the solutions are for iOS 6 <
HackViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"HackViewController"];
vc.delegate = self;
vc.modalPresentationStyle= UIModalPresentationCustom;
vc.view.frame = CGRectMake(0, 0, 800, 400);
vc.credits = CREDIT_PER_LEVEL * building.buildingLevels;
vc.view.backgroundColor = [UIColor clearColor];
[self presentViewController:vc animated:YES completion:^{
vc.view.backgroundColor = [UIColor clearColor];
}];
If you wanna add viewController (say VC2) as a modal over first viewController(say VC1), then in VC1, when you are presenting VC2,
[self presentViewController:VC2 animated:YES completion:nil];
In VC2 viewDidLoad
[self.view setBackgroundColor:[UIColor colorWithRed:211.0/255.0 green:211.0/255.0 blue:211.0/255.0 alpha:0.8]];
And, in AppDelegate under application didFinishLaunchingWithOptions:
[[self.window rootViewController] setModalPresentationStyle:UIModalPresentationCurrentContext];
You can create other views like imageView or label, etc in VC2
If you only want to support iOS7 and above, you can implement this using a custom modal presentation style (UIModalPresentationCustom) and implement the transitioningDelegate to return a transitioning controller. In the transitioning controller, you will be called when the presentation is to happen, and you can provide the target frame of the presented view controller. This has a big advantage of using the system presentation model, and the system is aware a controller has been presented.
If you need to support iOS6, you need to add the presented view controller's as a subview, the view controller as a child view controller of the presenting view controller, and manager the view hierarchy yourself. This is less than optimal because of manual view hierarchy control and child/parent view controllers.
You don't have to presentViewController:, you can do that with addSubview:
Keep doing what you are doing but add view of vc to view of presented view controller instead of presenting vc with presentViewController:
If you do that within an animation block, it would look nice.
Related
I have 3 viewcontrollers
let's say AVC, BVC and CVC
First I present BVC from AVC and then present CVC from BVC
So the hierarchy would be AVC->BVC->CVC.
When I dismiss CVC from BVC, viewWillAppear of AVC is triggered, how can I prevent viewWillAppear of AVC getting called?
Thanks
UPDATE
First present (in AVC)
BTViewController *BTVC = [self.storyboard instantiateViewControllerWithIdentifier:#"BTVCIns"];
__weak BTViewController *weakBTVC = BTVC;
BTVC.dismissAction = ^{[weakBuyTicketVC dismissViewControllerAnimated:YES completion:nil];};
[BTVC setModalPresentationStyle:UIModalPresentationOverCurrentContext];
[self presentViewController:BTVC animated:YES completion:nil];
Second present (in BVC)
PMViewController *PMVC = [self.storyboard instantiateViewControllerWithIdentifier:#"PMVCIns"];
__block PMViewController *blockPMVC = PMVC;
blockPMVC.dismissAction = ^{[blockPMVC dismissViewControllerAnimated:YES completion:nil];};
blockPMVC.delegate = self;
[self presentViewController:PMVC animated:YES completion:nil];
From the View Controller Programming Guide for iOS
When presenting a view controller using the UIModalPresentationFullScreen style, UIKit normally removes the views of the underlying view controller after the transition animations finish. You can prevent the removal of those views by specifying the UIModalPresentationOverCurrentContext style instead. You might use that style when the presented view controller has transparent areas that let underlying content show through.
Since you are using UIModalPresentationOverCurrentContext, iOS considers that BVC may have transparent areas which expose AVC, hence when BVC appears it is possible that AVC is also going to appear, so viewWillAppear is called.
I know there are lot of discussions around this warning and we should be presenting the view controller only after presenting view controller is completely presented and added in view hierarchy. All these rules are followed and I am still getting this warning when presenting second modal view controller.
This the complete sequence of actions happening here:
On app launch, initialized and added base view controller to window.
From base view controller, on tap on a button, modally presented first view controller.
From first view controller, on tap on a button, modally presented second view controller. Once dismissed, I need to show first view controller so I am not dismissing first VC and then presenting second VC from base VC.
Warning in console:
2015-03-11 13:15:43.467 MyApp[597:84839] Warning: Attempt to present <MyCustomNavigationViewController: 0x15d6a8920> on <MyFirstModalViewController: 0x15d67cc60> whose view is not in the window hierarchy!
Thoughts?
Below is my code [Not showing button handlers; took code from within the calling methods]:
// Present base view controller
self.window.backgroundColor = [UIColor whiteColor];
self.primaryViewController = [[MyParentViewController alloc] init];
self.navigationController = [[MyCustomNavigationViewController alloc] initWithRootViewController:self.primaryViewController];
[self.navigationController.navigationBar setBarStyle:UIBarStyleBlack];
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
// Present first view controller modally from base view controller
MyFirstModalViewController *firstModal = [[MyFirstModalViewController alloc] init];
MyCustomNavigationViewController *aNavigationController = [[MyCustomNavigationViewController alloc] initWithRootViewController:firstModal];
[aNavigationController.navigationBar setBarStyle:UIBarStyleBlack];
[self presentViewController:aNavigationController animated:YES completion:nil];
// Present second view controller modally from first view controller: On user tap on a button
MySecondModalViewController *secondModal = [[MySecondModalViewController alloc] init];
MyCustomNavigationViewController *aNavigationController = [[MyCustomNavigationViewController alloc] initWithRootViewController:secondModal];
[aNavigationController.navigationBar setBarStyle:UIBarStyleBlack];
[self presentViewController:aNavigationController animated:YES completion:nil];
If the code you're displaying in your question is in the exact order that it runs on app launch, it's because it's trying to animate the second Modal viewcontroller (the third actual view controller being presented) when the first modal has not finished animating.
On these two lines - [self presentViewController:aNavigationController animated:YES completion:nil]; set animated:NO. This will make those views appear instantaneously, and may remove the error you're getting.
If you need the animation, and you need the views to all open instantaneously like you've written, then you might want to implement a delay before the third viewcontroller (second modal) is called.
You say you're calling these views from buttons? The code indicates otherwise.
I'm building a complex app that has kind of a branch in the middle.
At some point in the app, a particular UIViewController is presented, we'll call it mainViewController (shortened mainVC).
The mainVC presents another view controller, by code, using the following code (I strip out parts of it for privacy reasons):
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"SecondaryStoryboard" bundle:secondaryBundle];
SecondViewController *secondVC = [storyboard instantiateInitialViewController];
[self presentViewController:secondVC animated:YES completion:nil];
So the secondVC will later present another view controller, called thirdVC. This is done using a custom segue, set in the storyboard used in the code above, which code looks like this:
#implementation VCCustomPushSegue
- (void)perform {
UIView *sourceView = ((UIViewController *)self.sourceViewController).view;
UIView *destinationView = ((UIViewController *)self.destinationViewController).view;
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
destinationView.center = CGPointMake(sourceView.center.x + sourceView.frame.size.width, destinationView.center.y);
[window insertSubview:destinationView aboveSubview:sourceView];
[UIView animateWithDuration:0.4
animations:^{
destinationView.center = CGPointMake(sourceView.center.x, destinationView.center.y);
sourceView.center = CGPointMake(0 - sourceView.center.x, destinationView.center.y);
}
completion:^(BOOL finished){
[self.sourceViewController presentViewController:self.destinationViewController animated:NO completion:nil];
}];
}
#end
As you can see this segue presents the destination view controller modally (by the use of presentViewController:) with a custom animation (a slide from right to left).
So basically up to here everything is fine. I present the secondVC with a classic modal animation (slide up from bottom) and present the thirdVC with my custom transition.
But when I want to dismiss the thirdVC, what I want is to go back directly to the mainVC. So I call the following from the thirdVC :
self.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:_animate completion:nil];
That way, I'm calling dismissViewControllerAnimated: directly on mainVC (referenced by self.presentingViewController.presentingViewController), and I'm expecting the thirdVC to be dismissed with an animation, and the secondVC to just disappear without animation.
As Apple says in the UIViewController Class Documentation:
The presenting view controller is responsible for dismissing the view
controller it presented. If you call this method on the presented view
controller itself, it automatically forwards the message to the
presenting view controller.
If you present several view controllers in succession, thus building a
stack of presented view controllers, calling this method on a view
controller lower in the stack dismisses its immediate child view
controller and all view controllers above that child on the stack.
When this happens, only the top-most view is dismissed in an animated
fashion; any intermediate view controllers are simply removed from the
stack. The top-most view is dismissed using its modal transition
style, which may differ from the styles used by other view controllers
lower in the stack.
The issue is that it's not what happens. In my scenario, the thirdVC disappears, and shows the secondVC being dismissed with the classic modal slide to bottom animation.
What am I doing wrong ?
Edit :
So #codeFi's answer is probably working in a classic project, but the problem here is that I'm working on a framework. So mainVC would be in a client app, and the secondVC and thirdVC are in my framework, in a separate storyboard. I don't have access to mainVC in any other way than a reference to it in my code, so unwind segues are unfortunately not an option here.
I've been having this exact same issue, and I've managed to visually work around it by adding a snapshot of the screen as a subview to secondVC.view, like so:
if (self.presentedViewController.presentedViewController) {
[self.presentedViewController.view addSubview:[[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]];
}
[self dismissViewControllerAnimated:YES completion:nil];
Not pretty, but it seems to be working.
NOTE: if your secondVC has a navigation bar, you will need to hide the navigation bar in between snapshotting the screen and adding the snapshot as a subview to secondVC, as otherwise the snapshot will appear below the navigation bar, thus seemingly displaying a double navigation bar during the dismissal animation. Code:
if (self.presentedViewController.presentedViewController) {
UIView *snapshot = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
[self.presentedViewController.navigationController setNavigationBarHidden:YES animated:NO];
[self.presentedViewController.view addSubview:snapshot];
}
[self dismissViewControllerAnimated:YES completion:nil];
I had the same issue and I've fixed it by using UnwindSegues.
Basically, all you have to do is add an IBAction Unwind Segue method in the ViewController that you want to segue to and then connect in IB the Exit action to your Unwind Segue method.
Example:
Let's say you have three ViewControllers (VC1, VC2, VC3) and you want to go from VC3 to VC1.
Step 1
Add a method to VC1 like the following:
- (IBAction)unwindToVC1:(UIStoryboardSegue*)sender
{
}
Step 2
Go in Interface Builder to VC3 and select it. Then CTRL-drag from your VC icon to Exit icon and select the method you've just added in VC1.
Step 3
While still in IB and with VC3 selected, select your Unwind Segue and in the Attributes Inspector add a Segue Identifier.
Step 4
Go to VC3 where you need to perform your segue (or dismiss the VC) and add the following:
[self performSegueWithIdentifier:#"VC1Segue" sender:self];
I am updating our app to be compiled with xcode6/iOS8.
One issue I am running into is that when a modal view is presented. the underlying subview is removed. It is completely blacked out.. until the modal view is dismissed.. then it re-appears.
Has anyone run into this with iOS8? The same code has worked since iOS4.
Code:
PigDetailViewController *pigDetailViewController = [[PigDetailViewController alloc] initWithNibName:#"PigDetailViewController" bundle:nil];
self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
self.navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:pigDetailViewController animated:YES completion:nil];
In iOS 8 they've added a new presentation style that behaves like UIModalPresentationCurrentContext in the circumstance you've described, it's UIModalPresentationOverCurrentContext. The catch here is that unlike with UIModalPresentationCurrentContext, you want to set the view controller to be PRESENTED with this presentation style, like so:
PigDetailViewController *pigDetailViewController = [[PigDetailViewController alloc] initWithNibName:#"PigDetailViewController" bundle:nil];
pigDetailViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
self.navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:pigDetailViewController animated:YES completion:nil];
Note that to support iOS 7 and below you'll likely need to check the OS version and decide how to present the view controller based on that.
edit: I'd like to add one more note to this. In iOS7 with UIModalPresentationCurrentContext, when the presented VC was dismissed, the underlying VC had its viewDidAppear method called. In iOS8 with UIModalPresentationOverCurrentContext, I've found the underlying VC does not have its viewDidAppear method called when the VC presented over top of it is dismissed.
Adding a point to BrennanR's answer.. even viewWillAppear doesn't call when the VC presented over top of it is dismissed.
I think you are misunderstanding how a modal view controller works.
When you present a view controller modally it will control the entire screen. It has an opaque background (normally black) and then draws its view on top of that.
So, if you set the view.backgroundColor to yellow (for example) it will have a yellow background. If you set it to clear then it will show through to the black background.
What I think you want is for the other view to "show through" so it looks like the modal view is sitting on top of it.
The best way I have found of doing this is to use this method...
// in the view controller that is presenting the modal VC
modalVC = // the modal VC that you will be presenting
UIView *snapshotView = [self.view snapshotViewAfterScreenUpdates:NO];
[modalVC.view insertSubView:snapshotView atIndex:0];
// present the modal VC
This will take a "screenshot" of the current view hierarchy and then place that snapshot underneath everything in the modal VC.
That way your black screen will be replaced by a screenshot of the previous view controller.
I'm using a custom segue which looks like this:
#implementation ModalPushSegue
- (void)perform {
UIViewController *fromController = self.sourceViewController;
UIViewController *toController = self.destinationViewController;
UIView *fromView = fromController.view;
UIView *toView = toController.view;
CGPoint centerStage = toView.centerStage;
toView.center = toView.rightStage;
[fromView.window addSubview:toView];
[fromController addChildViewController:toController];
[UIView transitionWithView:toView
duration:0.5 options:0
animations:^{
toView.center = centerStage;
}
completion:nil];
}
This works well in that the view is slide on from the right as expected and the controller is added to the controller hierarchy.
But later in the added controller I do this:
[self presentViewController:anotherController animated:YES completion:nil];
I would expect this to slide the new controller's view up the screen ala modal style. But what happens instead is the the new view doesn't appear. And when I later remove this controller, it's view flashes up and slides off the screen, leaving a black background instead of the view that was originally there.
I've been playing around with this for a while and if I change the code to
//[self presentViewController:oauthController animated:YES completion:nil];
[self.view addSubview:oauthController.view];
[self addChildViewController:oauthController];
Then the view appears as expected, although not resized.
My problem appears to be with the way that the segues setup the hierarchy vs the way that presentViewController does things. I've done lots of reading and searching but so far have not been able to get a clear picture of exactly what is going on.
I've also played around with using presentViewController in the segue but instead of laying the new view over the old one, the screen goes black and the new view then slides on.
Any help appreciated.
Set a Storyboard ID on your destination view controller (in the storyboard), then try the following code:
AnotherController *viewC = [self.storyboard instantiateViewControllerWithIdentifier:#"AnotherController"];
ModalPushSegue *segue = [[ZHCustomSegue alloc] initWithIdentifier:#"coolSegueName" source:self destination:viewC];
[segue perform];