ios 7 custom present and dismiss transition - ios

I'm making custom present and dismiss transitions and have some problems with it. What I want to do is repeat this cool deep animations in iOS 7 (when we open/close some app).
I have First and Second Controllers. All animations are in First controller (it supports UIViewControllerTransitioningDelegate and UIViewControllerAnimatedTransitioning). So, I'm just checking: if it is presenting - I'm doing one animations (scaling up first and second view), if it is dismissing - I'm doing another animation (scaling down first and second view). Present animation works fine, the problem occurs with dismiss animation. In some reason when I'm scaling down my second controller (it is UINavigationController), I see black background behind it (and it's wrong, because I want to see my First controller while it's scaling down). Here is my code from First Controller
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIView *transitionView = [transitionContext containerView];
id toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
id fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
BOOL isPresenting;
isPresenting = [toViewController isKindOfClass:[UINavigationController class]];
UINavigationController *navigator = isPresenting ? toViewController : fromViewController;
if (isPresenting) {
[transitionView addSubview:navigator.view];
navigator.view.transform = CGAffineTransformMakeScale(0.1, 0.1);
navigator.view.alpha = 0;
}
navigator.view.center = self.startButton.center;
void(^AnimationBlock)(void) = ^ {
if (isPresenting) {
navigator.view.transform = CGAffineTransformMakeScale(1, 1);
self.view.transform = CGAffineTransformMakeScale(4, 4);
navigator.view.alpha = 1;
self.startButton.alpha = 0;
} else {
navigator.view.transform = CGAffineTransformMakeScale(0.1, 0.1);
self.view.transform = CGAffineTransformMakeScale(1, 1);
navigator.view.alpha = 0;
self.startButton.alpha = 1;
}
};
[UIView animateWithDuration:1
delay:0.0f
usingSpringWithDamping:50.0
initialSpringVelocity:4
options:UIViewAnimationOptionLayoutSubviews
animations:^{
AnimationBlock();
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
if (!isPresenting) {
[navigator.view removeFromSuperview];
}
}];
}
- (void)completeTransitionInContext:(id<UIViewControllerContextTransitioning>)transitionContext{
[transitionContext completeTransition:YES];
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return 1;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return self;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return self;
}
Please tell me if I should to provide some additional code or screens.
Thanks in advance!

You need to set modalPresentationStyle = UIModalPresentationCustom on the toVC before presenting it if you want to keep the fromVC in the window hierarchy after the present transition completes.
See my implementation of the sample code for WWDC Session 218: Custom Transitions Using View Controllers. If you click on 'Options' you'll see this type of transition. The relevant code is in SOLViewController.m prepareForSegue: and SOLOptionsTransitionAnimator.m
https://github.com/soleares/SOLPresentingFun

When you present the custom VC, you should use:
vc.modalPresentationStyle = UIModalPresentationCustom;
but, if you typed wrong to
vc.modalTransitionStyle = UIModalPresentationCustom;
you will get a black background behind the custom VC.

I think it is a good idea to use two separate AnimationController classes for Present and Dismiss animations and implement UIViewControllerTransitioningDelegate ( animationControllerForPresentedController and animationControllerForDismissedController ) inside your Parent ViewController.
To create an AnimationController just subclass NSObject and implement UIViewControllerAnimatedTransitioning there.
Hope it helps.

Related

Going to a ViewController with a custom segue causes viewWillAppear to get called twice

I have written a custom segue, because i wanted to add my own animations to it. Everything works alright, besides the fact that the viewDidLoad method in the target view CiewController gets called twice. Here is the perform method for my segue:
- (void)perform
{
UIViewController* sourceViewController = self.sourceViewController;
UIViewController* destinationViewController = self.destinationViewController;
[sourceViewController.view addSubview:destinationViewController.view];
CGPoint originalCenter = destinationViewController.view.center;
destinationViewController.view.center = CGPointMake(self.originatingPoint.x * 3, self.originatingPoint.y);
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
destinationViewController.view.center = originalCenter;
}
completion:^(BOOL finished) {
[sourceViewController presentViewController:destinationViewController animated:NO completion:NULL]; // present VC
}];
}
Does anyone have any idea what could cause this?
[----- EDIT -----]
The segue is present in the storyboard as a custom segue with a segue class that I have written myself. The only thing that is different in my class is the perform method which I have put above. The segue is called through a button, and the prepareForSegue method is called only once.
[----- EDIT 2 -----]
I checked the viewDidLoad method of the targetVC and it is only called once per segue. Nonentheless, it would be much more convenient for me to use viewWillAppear, so do you maybe know a different way in which i can do this animation?
I suggest you to use UIViewControllerTransitioningDelegate and UIViewControllerAnimatedTransitioning which are the more appropriate conventions to use for transitions since iOS 7.0. Since iOS 8.0 you also gain UIPresentationController support which allows you to build even richer transitions.
Example:
#interface ModalTransitionAnimator : NSObject<UIViewControllerAnimatedTransitioning>
#property (nonatomic) CGPoint originatingPoint;
#end
#implementation ModalTransitionAnimator
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.25;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
NSTimeInterval duration = [self transitionDuration:transitionContext];
UIView* sourceView = [transitionContext viewForKey:UITransitionContextFromViewKey];
UIView* destinationView = [transitionContext viewForKey:UITransitionContextToViewKey];
UIView* container = transitionContext.containerView;
[container addSubview:destinationView];
CGPoint originalCenter = destinationView.center;
destinationView.center = CGPointMake(self.originatingPoint.x * 3, self.originatingPoint.y);
[UIView animateWithDuration:duration animations:^{
destinationView.center = originalCenter;
}
completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
#end
Then in prepareForSegue you simply assign transitioning delegate and implement UIViewControllerTransitioningDelegate to return appropriate animators for presentation or dismissal.
#interface ViewController : UIViewController<UIViewControllerTransitioningDelegate>
#end
#implementation ViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UIViewController* controller = (UIViewController*)segue.destinationViewController;
controller.transitioningDelegate = self;
controller.modalPresentationStyle = UIModalPresentationCustom;
controller.modalPresentationCapturesStatusBarAppearance = YES;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
ModalTransitionAnimator *animator = [[ModalTransitionAnimator alloc] init];
animator.originatingPoint = /* ... */;
return animator;
}
#end
Since this is a modal transition, you have to use presentViewController:animated: when presenting controllers with it. Therefore use normal "show" segues in Storyboards and they will automatically run all animations under the hood, no need to reinvent segues here.
I had example of how to build custom transitions somewhere on Github:
https://github.com/pronebird/CustomModalTransition/tree/ios8

Custom Segue Animation Flicker

I am building an application using UISplitViewController as my root view controller (as prescribed by Apple). However, I needed a custom view for login / management to be displayed prior to the UISplitViewController, so I created a custom UIStoryboardSegue that calls some custom animations. I am attempting to recreate the push / pop segues through a modal segue, without actually pushing an popping view controllers. I've implemented everything correctly, however, at the end of my animation I have a flicker. Here is a gif of it:
Here is my custom Segue's code:
- (void)perform {
UIViewController *srcViewController = (UIViewController *) self.sourceViewController;
UIViewController *destViewController = self.destinationViewController;
UIView *prevView = srcViewController.view;
UIView *destView = destViewController.view;
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
[window insertSubview:destView aboveSubview:prevView];
[destView enterRight:0.1 then:^{
[destView removeFromSuperview];
[srcViewController.presentingViewController dismissViewControllerAnimated: NO completion:nil];
}];
}
And here is my custom animation (Implemented as a category on UIView):
-(void)enterRight:(float)delay then:(void(^)(void))after
{
CGPoint moveTo = self.center;
CGPoint moveFrom = self.center;
// Grab a point from off the screen
CGFloat simpleOffscreen = [UIScreen mainScreen].bounds.size.width;
// come from off the right side (+)
moveFrom.x = moveFrom.x + simpleOffscreen;
self.center = moveFrom;
self.hidden = NO;
[UIView animateWithDuration:0.5
delay:delay
usingSpringWithDamping:1
initialSpringVelocity:0.1
options:UIViewAnimationOptionCurveEaseIn
animations:^
{
self.center = moveTo;
}
completion:^(BOOL finished)
{
if (after) after();
}
];
}
As you can see in the Segue, I am animating the view into the current view controller, then without animation presenting the actual destination view controller. I think this is where the flicker is introduced, yet I am unsure about how to go about preventing this.
My Storyboard for this custom segue is
Anyone know how to implement this?

Interactive View Controller dismissal and changing statusBarStyle not compatible?

I'm presenting a modal UINavigationController with an interactive dismiss transition. The parent view controller has a dark status bar and the modal view controller a light status bar. I'm using the iOS 7 view controller-based status bar appearance configuration.
All works fine as long as I present and dismiss the view controller non-interactively. However, when I start an interactive dismiss transition and cancel it, the status bar color remains dark.
I created a sample project. Tap the "Menu" button, then start the interactive transition by panning from the right screen edge.
Things I've tried:
calling -setNeedsStatusBarAppearanceUpdate on any of the navigation and view controllers involved after the transition has been canceled
Changing the navigationBar.barStyle to UIBarStyleDefault and back to UIBarStyleBlack
I also verified that the statusBarStyle of my modal navigation controller is set correctly:
(lldb) p (UIStatusBarStyle) [[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentedViewController] preferredStatusBarStyle]
(UIStatusBarStyle) $8 = UIStatusBarStyleLightContent
Still, the status bar is black.
Any further idea what I could try?
To me this looks like a bug (rdar://15902745) in UINavigationController. After a canceled dismissal UINavigationController doesn't query again its presentedViewController for the preferredStatusBarStyle but uses the preferredStatusBarStyle from itself. I worked around this by overwriting -childViewControllerForStatusBarStyle:
- (UIViewController*)childViewControllerForStatusBarStyle {
if (self.presentedViewController) {
return self.presentedViewController.childViewControllerForStatusBarStyle;
}
return [super childViewControllerForStatusBarStyle];
}
Then, to animate the change during (and not after) the dismissal, I also overwrote -preferredStatusBarStyle.
I pushed the workaround to the sample project.
Do not forget to call
[self.transitionContext cancelInteractiveTransition];
within your's UIPercentDrivenInteractiveTransition subclasses - (void)cancelInteractiveTransition implementation. For inspiration, this is mine implementation
- (void)cancelInteractiveTransition {
id<UIViewControllerContextTransitioning> transitionContext = self.transitionContext;
[transitionContext cancelInteractiveTransition];
UIView *fromView = [UIViewController fromViewForTransitioningContext:transitionContext];
UIView *toView = [UIViewController toViewForTransitioningContext:transitionContext];
if (self.presenting)
{
CGRect endFrame = CGRectOffset([[transitionContext containerView] bounds], 0, CGRectGetHeight([[transitionContext containerView] bounds]));
[UIView animateWithDuration:ANIMATION_DURATION_PAN animations:^{
toView.frame = endFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:NO];
}];
}
else {
CGRect endFrame = [[transitionContext containerView] bounds];
[UIView animateWithDuration:ANIMATION_DURATION_PAN animations:^{
fromView.frame = endFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:NO];
}];
}
}
edit: this seems to help on iOS 8.4. Tested on 7.1 but no way. Maybe Apple fixed it lately.

Custom Segue without presentViewController doesn't unwind correctly

I'm implementing a custom segue using controller containment API, e.g.
#implementation CustomSegue
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
[sourceViewController addChildViewController:destinationViewController];
destinationViewController.view.alpha = 0.0;
[sourceViewController.view addSubview:destinationViewController.view];
[UIView animateWithDuration:0.3 animations:^{
destinationViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[destinationViewController didMoveToParentViewController:sourceViewController];
}];
}
#end
View controller hierarchy is trivial: sourceViewController → destinationViewController
When unwinding from the destinationViewController to the sourceViewController, app crashes in [UIStoryboardUnwindSegueTemplate _perform:] with exception Could not find a view controller to execute unwinding for <…>
I did not implement custom -[UIViewController viewControllerForUnwindSegueAction:fromViewController:withSender: or
-[UIViewController canPerformUnwindSegueAction:fromViewController:withSender: which means framework returns correct values (although I implemented it once to check).
When replacing my custom addChildViewController:… code with presentViewController:… in the segue, it works fine: unwinding performs like expected.
The question: is it possible to have a custom segue that creates a custom view controller hierarchy?
Test case project: https://bitbucket.org/zats/unwind/
I think that George Green's comment is relevant, and the way you have your controllers set up is the cause of the crash. I think to do what you want, you should have ZTSFirstViewController added as a child to a custom container controller which will do the unwinding, and the (forward) segue will exchange the children of that custom container controller (switch from ZTSFirstViewController to ZTSSecondViewController). The viewControllerForUnwindSegueAction:fromViewController:withSender: method needs to be implemented in the custom container controller (ViewController in my example). I tested this by adding a container view in IB to ViewController, and changed the class of the embedded controller to ZTSFirstViewController. I added a segue from a button in ZTSFirstViewController to ZTSSecondViewController, and connected the unwind segue from a button in that controller. The unwind: method is in ZTSFirstViewController. The code in the custom segue was this,
- (void)perform {
UIViewController *destinationViewController = self.destinationViewController;
UIViewController *sourceViewController = self.sourceViewController;
if (!self.unwind) {
[sourceViewController.parentViewController addChildViewController:destinationViewController];
destinationViewController.view.alpha = 0.0;
[sourceViewController.parentViewController.view addSubview:destinationViewController.view];
[UIView animateWithDuration:0.3 animations:^{
destinationViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[destinationViewController didMoveToParentViewController:sourceViewController.parentViewController];
}];
} else {
[self.sourceViewController willMoveToParentViewController:nil];
[UIView animateWithDuration:0.3 animations:^{
sourceViewController.view.alpha = 0.0;
} completion:^(BOOL finished) {
[sourceViewController.view removeFromSuperview];
[sourceViewController removeFromParentViewController];
}];
}
}
I kept this segue close to your implementation -- it doesn't actually switch the controllers, it just adds the second one as a second child and hides the view of the first.

How to transition from vc1.view to vc2.view in iOS, without any navigation controller?

I have vc1.view covering the whole screen, and I want to be able to dim vc1.view, and have vc2.view zoom into the whole screen.
I don't have any navigation controller in the app, so what's the best practice to achieve my goal? The solution I'm thinking of is:
Add both vc1.view and vc2.view into a common container view
Use [UIView transitionFromView:vc1.view toView:vc2.view ......]
I dislike the idea of having to add views of different vc into a common container view. Any suggestions? Thanks in advance.
You can use transitionFromView:toView:... without adding the new view to a common container, because that transition method takes care of adding the view. The following worked for me. The code is in the view controller whose view is the "from view". I'm using a cross fade here, but you could change that to any of the other available methods:
-(void)switchViews:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
yellow.view.frame = self.view.frame;
[UIView transitionFromView:self.view toView:yellow.view duration:2 options:UIViewAnimationOptionTransitionCrossDissolve completion:^(BOOL finished) {
win.rootViewController = yellow;
}];
}
However, to do a custom transition, you do have to add the new view as a subview of whatever view the "from view" is in (I think). In this example, that is the window's view. This code grows the new view from the center of the old one, while that one fades out. At the end of the transition, the view controller is switched to the one that owns the new view (yellow in this case)
After Edit: I changed this method to use a CGAffineTransform (thanks to jrturton for that suggestion made in an answer to my question):
-(void)switchViews3:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
yellow.view.frame = self.view.frame;
yellow.view.transform = CGAffineTransformMakeScale(.1, .1);
[win addSubview:yellow.view];
[UIView animateWithDuration:.6 animations:^{
yellow.view.transform = CGAffineTransformIdentity;
self.view.alpha = 0;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
win.rootViewController = yellow;
}];
}
To present from vc1 to vc2 without a navigation controller, use
[vc1 presentViewController:vc2 animated:YES completion:nil];
To change the presenting style, Apple provides a few. You just need to set it before calling the above code:
vc2.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
Here is the list:
typedef enum {
UIModalTransitionStyleCoverVertical = 0,
UIModalTransitionStyleFlipHorizontal,
UIModalTransitionStyleCrossDissolve,
UIModalTransitionStylePartialCurl,
} UIModalTransitionStyle;

Resources