Animating the presenting view in a UIPresentationController - ios

For some context, I recommend reading this:
Very relevant question: "From View Controller" disappears using UIViewControllerContextTransitioning
Very relevant answer: https://stackoverflow.com/a/25901154/751268
I'm trying to implement a custom view controller transition that animates the new view controller to cover half the screen, while simultaneously shrinking the presenting view controller to 90% (centered in the window, underneath the presented view controller).
First, my problem was that viewFromKey: returned nil. To solve that, the answer mentioned:
If you want to animate the presenting view controllers's view you should consider using UIModalPresentationFullscreen style or continue using UIModalPresentationCustom and implement your own subclass of UIPresentationController with shouldRemovePresentersView returning YES.
I did that, and viewFromKey: doesn't return nil anymore, but now the presenting view controller disappears completely (which makes sense considering I explicitly say it should by implementing shouldRemovePresentersView).
I add the presenting view controller's view to the container view, but it still gets removed. Is there anything else I should be doing to get this working?
Here's some relevant code:
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
BOOL show = self.isPresentation;
UIView *menuView = show ? toView : fromView;
UIView *backView = show ? fromView : toView;
UIView *containerView = [transitionContext containerView];
[containerView addSubview:backView];
[containerView addSubview:dimmedView];
[containerView addSubview:menuView];
// Adjust transforms, alpha and perform animations
I thought that by returning YES from shouldRemovePresentersView and manually adding it to the containerView, that should fix the issue, but backView gets removed anyway...

I'm adding another answer, as my response is too long to fit in a comment.
First of all the viewForKey is available in iOS8, so unless you are targeting iOS8 only (why?) you should not use it, or use it after checking that the UIViewControllerContextTransitioning responds to that selector and use the viewControllerForKey for iOS7.
With that being said, it seems to me that this is a bug and I explain my self:
If you look at the UIPresentationController header file, you will see that it says
// Indicate whether the view controller's view we are transitioning from will be removed from the window in the end of the
// presentation transition
// (Default: YES)
- (BOOL)shouldRemovePresentersView;
So as you see the default is YES, so this should only overriden when you specifically want to say NO.
However, you are right, without this being set explicitly to YES, the viewForKey for the UITransitionContextFromViewControllerKey remains nil.
I think you should fill in a bug report for this and for now use the viewControllerForKey which is fine to be used (nothing wrong with this) as it's not deprecated and works in both OS versions without a problem.
And the reason this is most likely a bug is that the viewForKey should return a view for theUITransitionContextFromViewControllerKey when the shouldRemovePresentersView is explicitly set to NO and not YES.
My 2 cents

Why are you setting shouldRemovePresentersView to YES if what you want is that the fromView remains visible?
I had the same problem as you, as in my custom 3d presentation the parent view controller was removed as soon as the transition was completed.
The solution there is to change the modalPresentationStyle to UIModalPresentationOverCurrentContext
This will specifically prohibit the system to remove the owner viewController when the transition ends.
Still my 3D transition suffered from animation issues, when using this approach.
Si I ended up using a UIModalPresentationCustom modalPresentationStyle which solved almost all my issues, with the exception that the new view controller (which was a UINavigationController) would not move down when the in-call status bar would appear.
To solve this, I ended up changing the modalPresentationStyle back to UIModalPresentationFullScreen after the transition had been completed.
Here is my code that shows the new viewController with the custom presentation:
//show Login Screen
LoginViewController *viewController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
UINavigationController *loginNavController = [[UINavigationController alloc] initWithRootViewController:viewController];
loginNavController.interactivePopGestureRecognizer.enabled = YES;
loginNavController.transitioningDelegate = self.customTransitionDelegate;
loginNavController.modalPresentationStyle = UIModalPresentationCustom;
[navigationController presentViewController:loginNavController animated:YES
completion:^{
//important, else status bar is not moving entire screen down....
loginNavController.modalPresentationStyle = UIModalPresentationFullScreen;
}
];
Also very important is that you must NOT add the old view to the containerView when you run your dismissal animation
So if the animation is the presenting, add your toView to the containerView
UIView* inView = [transitionContext containerView];
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
.....
......
[inView insertSubview:toViewController.view aboveSubview:fromViewController.view];
But on the dismissal presentation DO NOT ADD the view to the containerView as it is still showing (since we specifically asked the system NOT to remove it), but simply animate between the two views.

Related

Is it possible to change frame of view controller in UITabBarController so that UITabBarController view will be visible?

I have my own subclass of UITabBarViewController.
Is it possible to change frame for all embedded viewcontrollers' views so that own UITabBarViewController view will be visible partially?
On the attached image I set purple color for own tabBarController view.
I want to change frame of each selected view controller so that this purple view (UITabBarController view) will be visible.
I stumbled upon this answer looking for a solution myself, and found an okayish way to handle this: wrap your viewController inside another viewController as a childViewController.
Essentially, you would present a viewController with clear background, which has your content controller as childController with a frame you want it to have:
UIViewController *wrapperController = [UIViewController new];
wrapperController.backgroundColor = [UIColor clearColor];
[wrapperController addChildViewController:vc];
[wrapperController.view addSubview:vc.view];
vc.view.frame = CGRectMake(...);
Just make sure to pass the tabbarItem to the wrapper, and use that one instead of the child.

Custom presented UIViewController changing to fullscreen

I have a view controller presented using UIModalPresentationCustom presentation style. I use a custom UIViewControllerTransitioningDelegate to present the view controller as a sidebar (so it slides in from the edge of the screen and does not occupy the full screen).
However when I then present another view controller from this one using UIModalPresentationFullScreen — and then dismiss the full screen view controller, my underlying custom presented controller is suddenly resized to occupy the full screen. Does anyone know why this is the case?
Edit: this is essentially my animateTransition method for presenting the sidebar — I've stripped out most of the code to make it readable. Basically it gets the container from the transitionContext, adds and animates the destination view controller's view to the container.
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *container = transitionContext.containerView;
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
if( toVC.isBeingPresented )
{
[container addSubview:toView];
//... Animate some new frame for toView
//Call [transitionContext completeTransition:YES] on animation completion
}
else
{
//... Animate fromView out
//On completion remove fromView from superview
//Call [transitionContext completeTransition:YES] on animation completion
}
}
Edit 2: Doing a little more research, I notice that the frame of my custom presented view controller's view is being set when the view controller above it in the modal stack is dismissed. The following stack trace leads to the frame being set as full screen:
0 -[MyCustomPresentedViewControllerView setFrame:]
1 -[UIView(MPAdditions) setFrameOrigin:]
2 -[UIViewControllerAccessibility(SafeCategory) dismissViewControllerWithTransition:completion:]
3 -[UIViewController dismissViewControllerAnimated:completion:]
Changing the presentation style by assigning:
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
results in the modal presentation controller occupying a fraction of the screen, the same fraction as the original presented view controller of the UIPresentationController.
However, using viewController.modalPresentationStyle = UIModalPresentationOverFullScreen
is better for this case, since his modal controller is full screen.
I am experiencing the same issue. I've tried debugging it using symbolic breakpoints and there seem to be some internal call on some kind of layout manager that does this.
While I wasn't able to "solve" this (it seems to me like a bug in the SDK), I was able to come up with a workaround that fixes this. Basically, you have to set the correct dimensions of the presented view at two opportune times. Like this:
In the view controller that was presented using your custom UIPresentationController, add this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
dispatch_async(dispatch_get_main_queue(), ^{
self.view.frame = [self.presentationController frameOfPresentedViewInContainerView];
});
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.view.frame = [self.presentationController frameOfPresentedViewInContainerView];
}
And if you are wondering: yes, you do need it to do in two places. Because weirdly enough, when I did it only in the viewDidAppear async block, it got broken (resized to fullscreen) once the animation finished.
Try to use:
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
It helped me in iOS 8.1

In iOS 7, with customer VC transitioning, is it possible to make "fromviewcontroller" on screen after presentViewController?

Many apps have the feature which, user can pull down one view, and another view shows up from underneath it. But the first view is still visible on the bottom. (e.g. Facebook Paper App).
If the other view is from another view controller, is it possible to achieve this by using iOS 7's custom view controller transitioning API ?
In my test, it is possible to do the "presenting" part, but the "dismissing" part has a glitch. Whenever we call dismissViewController, the "toViewController" takes over the full screen even before calling the transitioningDelegate methods.
Anyone more familiar with this ? Thanks !
If the other view is from another view controller, is it possible to achieve this by using iOS 7's custom view controller transitioning API ?
Yes, it's possible. You can use the snapshot API to take a snapshot of any UIView. With this you can take a snapshot of view controller you're transitioning from, then add it to the containerView below the view controller you're transitioning to.
For example, in the -animateTransition: method of your class that adopts UIViewControllerAnimatedTransitioning take a snapshot of the view controller you're transitioning from and add it as a subview beneath the view controller you're transitioning to:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIView * containerView = transitionContext.containerView;
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// take snapshot of from view controller
UIView * fromSnapshotView = [fromViewController.view snapshotViewAfterScreenUpdates:NO];
[containerView insertSubview:fromSnapshotView belowSubview:toViewController.view];
// Then do your animations on the to view controller to animate it into view as well as the fromSnapshotView
// Finally, don't forget to call:
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}
In my test, it is possible to do the "presenting" part, but the "dismissing" part has a glitch. Whenever we call dismissViewController, the "toViewController" takes over the full screen even before calling the transitioningDelegate methods.
If your call to dismiss doesn't fire your transitioning delegate methods make sure that you set the transitioning delegate on that view controller before dismissing.

iOS - Semi-transparent modal view controller

I want to present a view controller with a slightly transparent background modally over the current view, such that the first view is slightly visible under the modal view.
I set the alpha value of the modal view controller and set the modalPresentationStyle to UIModalPresentationCurrentContext, as suggested in another post.
The result is that the view background is transparent when animating up, but when view controller is in place it changes to opaque black. It goes back to being transparent while animating the dismissal.
How can I get it to be transparent when active ?
I have tested in iOS 6 and 7. The code I am using follows:
MyModalViewController *viewController = [[MyModalViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
[navController setNavigationBarHidden:YES];
self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self.navigationController presentViewController:navController animated:YES completion:NULL];
iOS 8 added a new modal presentation style specifically for this purpose:
presentedViewController.modalPresentationStyle = UIModalPresentationOverFullScreen
From the spec:
UIModalPresentationOverFullScreen
A view presentation style in which the presented view covers the screen. The views beneath the presented content are not removed from the view hierarchy when the presentation finishes. So if the presented view controller does not fill the screen with opaque content, the underlying content shows through.
If you are targeting ios 8 and above you can set the modal presentation style to "over current context" and you are done.
If ios 7 and below, you would have to create a custom transition style so that the presenting screen doesn't go blank after transition. That is rather complicated.
The solution I present offers a lot of flexibility: make a screenshot before showing the modal dialog and set that as the background image for the application window. By default, that background is black (that is what you see when the back view controller dissapears). Change the background to the screenshot of the app. Make the screenshot in the viewWillAppear or viewDidLoad method of your transparent view. This works even with push segues, not only modal dialogs, but you should avoid animations. In general, avoid animations which affect the position of the background view because those will make it seem like it snaps back into place when transition finishes. It is a good idea to reset the background to its previous black image on viewDidDissapear to avoid unwanted effects.
You can maintain a stack of such background images and you can do multiple "transparent" push seques. Or have some complex/deep menu which appears on top of some main screen. For these many reasons I think this solution is better than rolling your own transitioning code. It is more flexible and easier to implement, and you don't have to deal with the animations yourself.
The reason that the BG view controllers disappear after a modal is shown is that the default transition in iOS 7 removes the BG view after animation completed. If you define your own transition and you set your BG view not to be removed (just changing its alpha) then you will have the transparent modal view.
Same problem occured to me. I have solved it by looking at the following url about a custom alert controller. I managed to get it working even with a UINavigationController.
Swift
let viewController = UIViewController()
viewController.providesPresentationContextTransitionStyle = true
viewController.definesPresentationContext = true
viewController.modalPresentationStyle = .overCurrentContext
viewController.modalTransitionStyle = .crossDissolve
DispatchQueue.main.async {
self.navigationController?.present(viewController, animated: true, completion: nil)
}
Objective C
UIViewController *viewController = [UIViewController new];
viewController.providesPresentationContextTransitionStyle = true;
viewController.definesPresentationContext = true;
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
viewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController presentViewController:viewController animated:true completion:nil];
});
Here is a solution.
Create your presenting view controller. Add a backView to this view controller's main view. Name this as backView.
In SecondViewController.m
-(void)viewDidLoad
{
// Make the main view's background clear, the second view's background transparent.
self.view.backgroundColor = [UIColor clearColor];
UIView* backView = [[UIView alloc] initWithFrame:self.view.frame];
backView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
[self.view addSubview:backView];
}
Now you have a view controller with half transparent background. You can add anything you want to the self.view , the rest will be half transparent.
After that, in FirstViewController.m
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:secondViewController animated:YES completion:nil];
My solution is this:
Create a custom transparent overlay UIView that comes over any view, navigationbar and tabbbar.
-In the navigation controller (or tabbar controller) that your view controller is embedded in I create a custom view with it's frame equal to the frame of the navigation controller's view.
-Then I set it offscreen by setting it's origin.y to navigationController.view.height
-Then I create 2 functions -(void)showOverlay and -(void)hideOverlay that animate the overlay view on and off screen:
- (void)hideOverlay{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
CGRect frm = self.helpView.frame;//helpView is my overlay
frm.origin.y = self.offscreenOffset; //this is an Y offscreen usually self.view.height
self.helpView.frame = frm;
[UIView commitAnimations];
}
- (void)showOverlay{
[self.view bringSubviewToFront:self.helpView];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
CGRect frm = self.helpView.frame;
frm.origin.y = self.onscreenOffset;
self.helpView.frame = frm;
[UIView commitAnimations];
}
-In my view controller I can just call
[(MyCustomNavCtrl *)self.navigationController showOverlay];
[(MyCustomNavCtrl *)self.navigationController hideOverlay];
And that's about it.
FYI: The syntax is now:
childVC.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
Why don't you try setting this in AppDelegate
self.window.rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
then changing the alpha on the view being presented

How can I add a transparent view?

I want to push a view controller onto the navigation stack but I don't want its view to initially appear - in other words I want the view that was visible when the view controller is push to still be visible.
I tried setting the view controller's view's alpha value to 0.0 which I thought would make it transparent. But instead what is happening is that when I push the view controller on the the stack the screen is white. If I set the alpha to 1.0 then the view controller's view appears as expected.
Why is it white and not transparent?
you will have to add the view to the viewcontrollers manually
Not pushing it
For example do the following
YourViewController *vc = [[YourViewController alloc] init];
[self.view addSubview:vc.view];
vc.view.alpha = 0.0;
//Animate Here
vc.view.alpha = 1.0;
//Commit Animate Here
Please not that you will have to do some additional coding to implement the release of the vc, since now you have retained vc.view you will not be able to release vc easily,
Another solution is instead of implementing the second view as a viewcontoller implement it as uiview, and the xib class will be view and not uiviewcontroller
Maybe make sure that the opaque property is set to NO?
Or perhaps the view you're pushing on was built in interface builder, and you have a background color of white with another view you put on top of it and you only changed the opacity of the subview?

Resources