I am presenting a UIViewController that contains a UIVisualEffectView as follows:
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self performSegueWithIdentifier:#"segueBlur" sender:nil];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"segueBlur"]) {
((UIViewController *)segue.destinationViewController).providesPresentationContextTransitionStyle = YES;
((UIViewController *)segue.destinationViewController).definesPresentationContext = YES;
((UIViewController *)segue.destinationViewController).modalPresentationStyle = UIModalPresentationOverFullScreen;
}
}
As you can see, I'm using the UIModalPresentationStyleOverFullScreen so that when the view controller with blur appears, the blur will be 'applied' to the content of the view controller that is presenting it; the segue has a Cross Dissolve transition style.
The effect looks as expected. However, in iOS 9 the presentation is smoother than in iOS 10. In iOS 10 when the view controller appears it seems like a 2-step animation, while in iOS 9 the blur is applied immediately.
A picture is worth a thousand words so I uploaded a video showing this strange behavior:
UIVisualEffectView iOS 9 vs iOS 10
My question is: How can I present the view controller in iOS 10 as it is presented in iOS 9?
iOS 10 has changed the way UIVisualEffectView works, and it has broken many use cases which were not strictly speaking "legal", but worked before. Sticking to documentation, you should not be fading in UIVisualEffectView, which is what happens when you use UIModalTransitionStyleCrossDissolve. It seems now broken on iOS 10, along with masking of visual effect views, and others.
In your case, I would suggest an easy fix which will also create a better effect than before, and is supported on both iOS 9 and 10. Create a custom presentation and instead of fading the view in, animate the effect property from nil to the blur effect. You can fade in the rest of your view hierarchy if needed. This will neatly animate the blur radius similar to how it looks when you pull the home screen icons down.
UIView.animate(withDuration: 0.5) {
self.effectView.effect = UIBlurEffect(style: .light)
}
Tested on both iOS9 and 10. Works fine for me
Code below blur parent view controller when ViewController presented. Tested on both iOS9 and 10.
#interface ViewController () <UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning>
#property (nonatomic) UIVisualEffectView *blurView;
#end
#implementation ViewController
- (instancetype)init
{
self = [super init];
if (!self)
return nil;
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
self.transitioningDelegate = self;
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor clearColor];
self.blurView = [UIVisualEffectView new];
[self.view addSubview:self.blurView];
self.blurView.frame = self.view.bounds;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source
{
return self;
}
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.3;
}
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *container = [transitionContext containerView];
[container addSubview:self.view];
self.blurView.effect = nil;
[UIView animateWithDuration:0.3 animations:^{
self.blurView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}
#end
Related
Help please. How to make a clear Color for modal View on iOS 8, write so
self.view.superview.backgroundColor = [UIColor clearColor];
Work on iOS 7 but not on iOS 8. It becomes clear but not completely
Try to change the alpha value like that :-
[[UIColor clearColor] colorWithAlphaComponent:0.6];
If you set background color for the main UIViewController view, you need to setup it before:
- (void)setupModalPresentationStyle
{
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
self.modalPresentationStyle = UIModalPresentationOverFullScreen;
}
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self setupModalPresentationStyle];
}
And when you present this UIViewController, you need to setup current presenting UIViewController:
- (void)doneButtonTap:(id)sender {
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:detailsViewController animated:YES completion:nil];
}
I am trying to achieve a modal presentation effect where the presented view covers the parent view only partially as shown in the picture below.
I know I could achieve this by implementing custom transitions using UIPresentationController. I don't want to reinvent the wheel so before I roll on with development I would like to ask.
Is there a build in support for this kind of transition in the APIs?
I researched all available Modal Presentation Styles and it appears to me there is no support for the transition I want to make and the only way of achieving it is just to code it.
I ran into this exact same issue. I went down the modal presentation styles route as well and kept hitting a wall (specifically getting it working on an iPhone rather than an iPad).
After some digging around, I was able to get it working though. Here's how I did it:
To start, we need a view controller that we will be presenting (the modal one) to set it's view's background color to transparent and set the frame of the navigation controller's view to some offset.
ModalViewController.h
#import UIKit;
#class ModalViewController;
#protocol ModalViewControllerDelegate <NSObject>
- (void)modalViewControllerDidCancel:(ModalViewController *)modalViewController;
#end
#interface ModalViewController : UIViewController
#property (weak, nonatomic) id<ModalViewControllerDelegate> delegate;
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController;
#end
ModalViewController.m
static const CGFloat kTopOffset = 50.0f;
#implementation ModalViewController {
UINavigationController *_navController;
}
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
rootViewController.navigationItem.leftBarButtonItem = [self cancelButton];
_navController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.view.backgroundColor = [UIColor clearColor];
[self.view addSubview:_navController.view];
// this is important (prevents black overlay)
self.modalPresentationStyle = UIModalPresentationOverFullScreen;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect bounds = self.view.bounds;
_navController.view.frame = CGRectMake(0, kTopOffset, CGRectGetWidth(bounds), CGRectGetHeight(bounds) - kTopOffset);
}
- (UIBarButtonItem *)cancelButton
{
return [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonItemStylePlain target:self action:#selector(cancelButtonClicked:)];
}
- (void)cancelButtonClicked:(id)sender
{
[_delegate modalViewControllerDidCancel:self];
}
#end
Next, we need to set up the presenting controller to run the following animation:
Scale itself down
Fade out a lil' bit
Present the modal view controller using presentViewController:animated:completion
This is what I did
PresentingViewController.m
static const CGFloat kTransitionScale = 0.9f;
static const CGFloat kTransitionAlpha = 0.6f;
static const NSTimeInterval kTransitionDuration = 0.5;
#interface PresentingViewController <ModalViewControllerDelegate>
#end
#implementation PresentingViewController
...
...
- (void)showModalViewController
{
self.navigationController.view.layer.shouldRasterize = YES;
self.navigationController.view.layer.rasterizationScale = [UIScreen mainScreen].scale;
UIViewController *controller = // init some view controller
ModalViewController *container = [[ModalViewController alloc] initWithRootViewController:controller];
container.delegate = self;
__weak UIViewController *weakSelf = self;
[UIView animateWithDuration:kTransitionDuration animations:^{
weakSelf.navigationController.view.transform = CGAffineTransformMakeScale(kTransitionScale, kTransitionScale);
weakSelf.navigationController.view.alpha = kTransitionAlpha;
[weakSelf presentViewController:container animated:YES completion:nil];
} completion:^(BOOL finished) {
weakSelf.navigationController.view.layer.shouldRasterize = NO;
}];
}
#pragma mark - ModalViewControllerDelegate
- (void)modalViewControllerDidCancel:(ModalViewController *)modalViewController
{
__weak UIViewController *weakSelf = self;
[UIView animateWithDuration:kTransitionDuration animations:^{
weakSelf.navigationController.view.alpha = 1;
weakSelf.navigationController.view.transform = CGAffineTransformIdentity;
[weakSelf dismissViewControllerAnimated:YES completion:nil];
}];
}
#end
pretty sure its done like this
let newVC = <view controller you want to display>
let nav: UINavigationController = UINavigationController(rootViewController: newVC)
if let currVc = UIApplication.sharedApplication().keyWindow?.rootViewController {
nav.transitioningDelegate = currVc
nav.modalPresentationStyle = UIModalPresentationStyle.Custom;
currVc.presentViewController(nav, animated: true, completion: nil)
}
I'm pretty sure this is your answer - Page sheet - as in UIModalPresentationPageSheet
https://developer.apple.com/library/ios/documentation/userexperience/conceptual/mobilehig/Alerts.html#//apple_ref/doc/uid/TP40006556-CH14-SW3
I have a controller embedded in navigation controller. Let's say that i have a button that repositions self.navigationController.navigationBar a bit. Then i do presentViewControllerAnimated with any controller (doesn't matter if it's nav or not) and after dismissing it navigation bar returns to it's original position (actually it is at its original position at dismiss animation start). In iOS 6 and earlier the bar would not be repositioned automatically. Any idea how can i prevent this repositioning in iOS 7?
OK, so I finally got it right.
First of all - Apple does not want us to change position of UINavigationBar. Therefore you should avoid it at all cost. In my case i got an app to fix which moved UINavigationBar to show slide-out menu. The proper solution to slide-out menu problem is to put UINavigationController inside - then you can slide whole UINavigationController with its content (whatever it is) and everything works fine. For some reason UINavigationController was outside in this app. So, i had to resort to a hack. Do not use this method if you have ANY option not to use it. It's a hack, it might break in further iOS versions and Apple would certainly not appreciate it.
First, explore new transitioning system in iOS7: http://www.doubleencore.com/2013/09/ios-7-custom-transitions/
Then, replace:
[self presentViewController:navigationController animated:YES completion:nil];
with
if([UIApplication iOS7]) /* or any other custom iOS7 detection method you implement */
{ /* we simulate old transition with nav bar slided out */
navigationController.transitioningDelegate = [OMModalTransitionDelegate new];
}
[self presentViewController:navigationController animated:YES completion:nil];
So, we need a transition delegate to simulate standard behaviour and do the trick as well.
#import "OMModalTransitionDelegate.h"
#import "OMAnimatedTransitioning.h"
#implementation OMModalTransitionDelegate
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
OMAnimatedTransitioning *transitioning = [OMAnimatedTransitioning new];
return transitioning;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
OMAnimatedTransitioning *transitioning = [OMAnimatedTransitioning new];
transitioning.reverse = YES;
return transitioning;
}
#end
And now the actual animation manager (you have to implement sharedBar in a category on UINavigationBar yourself):
static NSTimeInterval const DEAnimatedTransitionDuration = 0.4f;
static NSTimeInterval const DEAnimatedTransitionMarcoDuration = 0.15f;
#implementation OMAnimatedTransitioning
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
UIView *superView = [UINavigationBar sharedBar].superview;
CGRect barFrame = [UINavigationBar sharedBar].frame;
if(self.reverse)
{ /* Trick - first, remove the bar from it's superview before animation starts */
[[UINavigationBar sharedBar] removeFromSuperview];
}
CGRect oldFrame = container.bounds;
if (self.reverse)
{
[container insertSubview:toViewController.view belowSubview:fromViewController.view];
}
else
{
toViewController.view.frame = oldFrame;
toViewController.view.transform = CGAffineTransformMakeTranslation(0, CGRectGetHeight(oldFrame));
[container addSubview:toViewController.view];
}
[UIView animateKeyframesWithDuration:DEAnimatedTransitionDuration delay:0 options:0 animations:^
{
if (self.reverse)
{
fromViewController.view.transform = CGAffineTransformMakeTranslation(0, CGRectGetHeight(oldFrame));
double delayInSeconds = 0.01; /* Trick - after an imperceivable delay - add it back - now it is immune to whatever Apple put there to move it */
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
[UINavigationBar sharedBar].frame = barFrame;
[superView addSubview:[UINavigationBar sharedBar]];
});
}
else
{
toViewController.view.transform = CGAffineTransformIdentity;
}
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return DEAnimatedTransitionDuration;
}
#end
In your custom navigation controller, add
- (void)viewWillLayoutSubviews {
//do your navigation bar layout
}
hope this can help you. Remind, above method only be supported ios >= 5.0.
I'm using a custom animation to present my view controllers. Here's the code:
-(void)launchCustomModal:(id)sender
{
UIButton *buttonClicked = (UIButton *)sender;
int selection;
selection = buttonClicked.tag;
[ticker removeFromSuperview];
ticker = nil;
if (selection == 3)
{
MyViewController *myVC = [[MyViewController alloc]initWithNibName:nil bundle:nil];
modalViewController= [[UINavigationController alloc]initWithRootViewController:myVC];
modalViewController.navigationBarHidden = YES;
[modalViewController setToolbarHidden:YES];
CGRect result = self.view.bounds;
result.origin.y = -result.size.height;
modalViewController.view.frame=result;
[self.view addSubview:modalViewController.view];
[UIView animateWithDuration:.375
animations:^{
modalViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}
completion:^(BOOL finished) {
NSLog(#"Finished");
}];
}
return;
}
I've noticed this method makes for a very laggy transition. If I launch the VC in a normal modal, it works quite smoothly. Also, if I animate just a view independent of a view controller, it also works perfectly smoothly. I'm wondering if there is something about the VC that might be causing it to animate so poorly? If its a symptom of something I'm doing or if view controllers are just not meant to be handled this way, etc. Any input is greatly appreciated. Thanks!
It was the CALayer shadows. removed them and it worked fine.
I'm trying to have the iPad splash screen fade out to reveal my app's main interface over 2 secs. The main interface is my main view controller and view, inside a navigation controller.
So I did the following:
UINavigationController with my root view controller.
root view controller has its interface all laid out and, as a last step, a CALayer with the same png used for the splash screen covering the interface.
The idea is that once the real splash screen is gone, there's still the CALayer. And then I fade the CAlayer out to reveal the interface. It kind of works: the fade happens, but no matter what duration I set for the animation, it still happens too fast.
Here's the code: (logoLayer is an ivar and mainCanvas is a container view inside self.view, in which I insert most subviews -- for other screen-dim-type effects.)
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
// Custom initialization
self.view.layer.backgroundColor = [UIColor clearColor].CGColor;
[self setLayerContent:[[NSBundle mainBundle] pathForResource:#"interfaceBackground" ofType:#"png"]];
[self layoutNavigationButtonWithResource:#"button1" glowing:YES forAction:#"biblioteca"];
[self layoutNavigationButtonWithResource:#"button2"glowing:YES forAction:#"portfolio"];
NSString *logoPath = [[NSBundle mainBundle] pathForResource:#"Default-Portrait~ipad" ofType:#"png"];
UIImage *image = [UIImage imageWithContentsOfFile:logoPath];
logoLayer = [[CALayer layer] retain];
logoLayer.contents = (id)image.CGImage;
logoLayer.bounds = CGRectMake(0, 0, image.size.width, image.size.height);
logoLayer.anchorPoint = CGPointMake(0, 0);
logoLayer.opacity = 1;
[mainCanvas.layer addSublayer:logoLayer];
}
return self;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(fadeOutLogo) withObject:nil afterDelay:0.1];
}
- (void)fadeOutLogo
{
CABasicAnimation *a = [CABasicAnimation animation];
a.duration = 10;
[logoLayer addAnimation:a forKey:#"opacity"];
[logoLayer setOpacity:0];
}
Notice I even delayed the call to the animation code just in case. And I also ended up with a value of 10sec. which is absurd for a fade. Still... the fade happens in about 0.2 secs.
Any thoughts?
That CABasicAnimation isn’t actually doing anything, because you’re not giving it a fromValue or toValue; when you’re setting the layer’s opacity, it’s only animating because setting properties on Core Animation layers triggers an implicit animation, whose default duration is about a quarter of a second. What you want to do is this:
- (void)fadeOutLogo
{
[CATransaction begin];
[CATransaction setAnimationDuration:2];
[logoLayer setOpacity:0];
[CATransaction commit];
}
Alternatively, in viewWillAppear: you could instantiate a UIImageView with Default-Portrait~ipad.png, add it to self.view, use the UIView animateWithDuration:animations:completion: class method to animate the value of alpha to 0 over a period of 2 seconds, and then remove and release the UIImageView in the completion block. The following code demonstrates the idea, using a UIView instead of a UIImageView:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear: animated];
UIView __block *fadeView = [[UIView alloc]
initWithFrame: CGRectMake(0, 0, 320, 460)];
fadeView.backgroundColor = [UIColor blackColor];
[self.view addSubview: fadeView];
[UIView animateWithDuration: 2 animations:^{
fadeView.alpha = 0;
} completion:^(BOOL finished) {
fadeView = nil;
}];
}