Create an animation for UIView to coincide with UINavigationController push/pop - ios

The initial ViewController of my app has 2 container views: The first contains a ViewController inside which is a UINavigationController which controls the main content of the app. The second container view of the initial VC is a small strip with UILabels positioned directly below where the the Navigation Bar will be.
The initial VC of the navigation stack of the main content VC has the Navigation Bar hidden. All subsequent VC's will display the Navigation Bar along with the second container view of the initial VC. I would like to animate in the second container VC when the app navigates away from the initial VC of the navigation stack.
My question is, what animation do I need to create to match the animation a Navigation Controller performs when it pushes/pops VC on/off the navigation stack? I'm currently using:
[UIView animateWithDuration:0.27f delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.playerCardVC.frame = secondFrame;
} completion:nil];
But it's not matching up exactly when the Navigation Bar animates into view. Any thoughts/ideas would be greatly appreciated.
UPDATE:
I've been searching online for an answer to this issue along with trying to tweak the animation posted in my initial question but cannot seem to perfectly match the navigation bar as it animates into view. Any comments or points in the right direction will be truly appreciated.

It's very tough to understand your view hierarchy and navigation design from the description above, perhaps you can post some screenshots or sketches?
You can override the standard horizontal push/pop animations of a UINavigationController. You do so by defining a custom UINavigationControllerDelegate object and a few other things. See below.
Setup your navController and navControllerDelegate like so:
UINavigationController *navigationController = [[UINavigationController alloc] init];
self.navigationControllerDelegate = [[NavigationControllerDelegate alloc] init];
navigationController.delegate = self.navigationControllerDelegate;
Where the NavigationControllerDelegate class looks like this:
#interface NavigationControllerDelegate : NSObject <UINavigationControllerDelegate>
#end
#import "NavigationControllerDelegate.h"
#import "CrossFadePushAnimator.h"
#import "CrossFadePopAnimator.h"
#interface NavigationControllerDelegate ()
#property (nonatomic, strong) CrossFadePushAnimator* pushAnimator;
#property (nonatomic, strong) CrossFadePopAnimator* popAnimator;
#end
#implementation NavigationControllerDelegate
- (id)init
{
self = [super init];
if (self)
{
self.pushAnimator = [CrossFadePushAnimator new];
self.popAnimator = [CrossFadePopAnimator new];
}
return self;
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPop)
{
return self.popAnimator;
}
else if (operation == UINavigationControllerOperationPush)
{
return self.pushAnimator;
}
return nil;
}
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
return nil;
}
The pushAnimator looks something like this:
#interface CrossFadePushAnimator ()
#property (nonatomic, strong) id<UIViewControllerContextTransitioning> transitionContext;
#end
#implementation CrossFadePushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return AnimationDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 1.0f;
// We are using CAAnimation instead of UIView animation because we need the UIToolbar blur layer to animate
CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:#"opacity"];
fade.fromValue = #0;
fade.toValue = #1;
fade.duration = [self transitionDuration:transitionContext];
fade.removedOnCompletion = YES;
fade.delegate = self;
self.transitionContext = transitionContext;
[toViewController.view.layer addAnimation:fade forKey:AnimationKey];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.transitionContext completeTransition:![self.transitionContext transitionWasCancelled]];
self.transitionContext = nil;
}
And the popAnimator looks something like the pushAnimator.
Hope this helps!

It's very tough to understand your view hierarchy and navigation design from the description above, perhaps you can post some screenshots or sketches?
You can override the standard horizontal push/pop animations of a UINavigationController. You do so by defining a custom UINavigationControllerDelegate object and a few other things. See below.
Setup your navController and navControllerDelegate like so:
UINavigationController *navigationController = [[UINavigationController alloc] init];
self.navigationControllerDelegate = [[NavigationControllerDelegate alloc] init];
navigationController.delegate = self.navigationControllerDelegate;
Where the NavigationControllerDelegate class looks like this:
#interface NavigationControllerDelegate : NSObject <UINavigationControllerDelegate>
#end
#import "NavigationControllerDelegate.h"
#import "CrossFadePushAnimator.h"
#import "CrossFadePopAnimator.h"
#interface NavigationControllerDelegate ()
#property (nonatomic, strong) CrossFadePushAnimator* pushAnimator;
#property (nonatomic, strong) CrossFadePopAnimator* popAnimator;
#end
#implementation NavigationControllerDelegate
- (id)init
{
self = [super init];
if (self)
{
self.pushAnimator = [CrossFadePushAnimator new];
self.popAnimator = [CrossFadePopAnimator new];
}
return self;
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPop)
{
return self.popAnimator;
}
else if (operation == UINavigationControllerOperationPush)
{
return self.pushAnimator;
}
return nil;
}
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
return nil;
}
The pushAnimator looks something like this:
#interface CrossFadePushAnimator ()
#property (nonatomic, strong) id<UIViewControllerContextTransitioning> transitionContext;
#end
#implementation CrossFadePushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return AnimationDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 1.0f;
// I'm using CABasicAnimation here for a specific reason, but you could also use the animation method you use above.
CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:#"opacity"];
fade.fromValue = #0;
fade.toValue = #1;
fade.duration = [self transitionDuration:transitionContext];
fade.removedOnCompletion = YES;
fade.delegate = self;
self.transitionContext = transitionContext;
[toViewController.view.layer addAnimation:fade forKey:AnimationKey];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.transitionContext completeTransition:![self.transitionContext transitionWasCancelled]];
self.transitionContext = nil;
}
And the popAnimator looks something like the pushAnimator.
Hope this helps!

Related

Interactive Animated Transition between NavigationControllers

When presenting a new ViewController using the:
UINavigtionController *nav = [[UINavigationController alloc] initWithRoot(myVC)]
[self presentViewController:nav animated:YES completion:nil];
The UIViewControllerTransitionDelegate is never triggered since I am declaring this in myVC. My question is thus: how do I properly setup a interactive transition using the above initializiation?
EDIT:
I tried to follow the advice given below with no luck yet:
VC1.m :
LTFollowersListViewController *f = [LTFollowersListViewController new];
f.delegate = self;
LTNavigationController *nav = [[LTNavigationController alloc] initWithRootViewController:f];
[self presentViewController:nav animated:YES completion:nil];
VC2.h:
#interface LTFollowersListViewController : UIViewController <UIViewControllerTransitioningDelegate>
#property (nonatomic, strong) id<UIViewControllerTransitioningDelegate> delegate;
VC2.m:
#property (nonatomic, strong) CEPanAnimationController *animationController;
#property (nonatomic, strong) CEHorizontalSwipeInteractionController *interactionController;
in the viewDidLoad: {
self.delegate = self;
self.animationController = [[CEPanAnimationController alloc] init];
self.interactionController = [[CEHorizontalSwipeInteractionController alloc] init];
}
in the bottom of file:
#pragma mark - UIViewControllerTransitionsingDelegate
- (id<UIViewControllerAnimatedTransitioning>)
animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source {
NSLog(#"Reached the animation dismissal");
// allow the interaction controller to wire-up its gesture recognisers
[self.interactionController wireToViewController:self.navigationController forOperation:CEInteractionOperationDismiss];
self.animationController.reverse = NO;
return self.animationController;
}
- (id<UIViewControllerAnimatedTransitioning>)
animationControllerForDismissedController:(UIViewController *)dismissed {
self.animationController.reverse = YES;
return self.animationController;
}
- (id<UIViewControllerInteractiveTransitioning>)
interactionControllerForDismissal:
(id<UIViewControllerAnimatedTransitioning>)animator {
NSLog(#"Reached the animation interaction");
// provide the interaction controller, if an interactive transition is in progress
return self.interactionController.interactionInProgress
? self.interactionController : nil;
}
Any suggestions as to what I am missing here? Thanks!
Did you miss
.h file declare the delegate and protocol ViewController will implement
#interface ViewController : UIViewController<UIViewControllerTransitioningDelegate>
#property (nonatomic, strong) id<UIViewControllerTransitioningDelegate> delegate;
.m file Setting the delegate
self.delegate = self;
and also implement the delegate methods.

Modal transition style like in Mail app

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

View controller does not auto-rotate after canceling interactive dismissal transition for presented view controller

I have a view controller presenting another view controller with modalPresentationStyle = UIModalPresentationCustom. Things are set up so that part of the presenting view controller's view shows up underneath the presented view controller's view. In this state, the presenting view controller still handles auto-rotation correctly, and I handle rotation for the presented view controller using autolayout.
I'm now trying to implement interactively dismissing the presented view controller using iOS 7's custom view controller transitioning API. It works except that, when the interactive dismissal is canceled, handling of auto-rotation stops working. (It works again after the presented view controller is dismissed later.) Why is this happening, and how can I fix this?
EDIT: Here is code you can run to demonstrate the problem. A view pops up from below, and you can dismiss it by swiping it down. If you cancel dismissal by not swiping it all the way down, the presenting view controller's view no longer responds to rotations, and the presented view controller's view has messed-up layout.
EDIT: Here is the link to the code below as an Xcode project:
https://drive.google.com/file/d/0BwcBqUuDfCG2YlhVWE1QekhUWlk/edit?usp=sharing
Sorry for the massive code dump, but I don't know what I'm doing wrong. Here's a sketch of what is going on: ViewController1 presents ViewController2. ViewController1 implements UIViewControllerTransitioningDelegate, so it is returning the animation/interactive controllers for the transitions. ViewController2 has a pan gesture recognizer that drives the interactive dismissal; it implements UIViewControllerInteractiveTransitioning to serve as the interactive controller for dismissal. It also keeps a reference to the animation controller for dismissal to finish the transition if the user drags the view down far enough. Finally, there are two animation controller objects. PresentAnimationController sets up the autolayout constraints to handle rotations for the presented view controller's view, and DismissAnimationController finishes up the dismissal.
ViewController1.h
#import <UIKit/UIKit.h>
#interface ViewController1 : UIViewController <UIViewControllerTransitioningDelegate>
#end
ViewController1.m
#import "ViewController1.h"
#import "ViewController2.h"
#import "PresentAnimationController.h"
#import "DismissAnimationController.h"
#implementation ViewController1
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = #"View 1";
self.navigationItem.prompt = #"Press “Present” and then swipe down to dismiss.";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Present" style:UIBarButtonItemStylePlain target:self action:#selector(pressedPresentButton:)];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// Some subview just to check if layout is working.
UIView * someSubview = [[UIView alloc] initWithFrame:self.view.bounds];
someSubview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
someSubview.backgroundColor = [UIColor orangeColor];
someSubview.layer.borderColor = [UIColor redColor].CGColor;
someSubview.layer.borderWidth = 2;
[self.view addSubview:someSubview];
}
// --------------------
- (void)pressedPresentButton:(id)sender
{
ViewController2 * presentedVC = [[ViewController2 alloc] initWithNibName:nil bundle:nil];
presentedVC.modalPresentationStyle = UIModalPresentationCustom;
presentedVC.transitioningDelegate = self;
[self presentViewController:presentedVC animated:YES completion:nil];
}
// --------------------
// View Controller Transitioning Delegate Methods.
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return [[PresentAnimationController alloc] init];;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
DismissAnimationController * animationController = [[DismissAnimationController alloc] init];
ViewController2 * presentedVC = (ViewController2 *)self.presentedViewController;
if (presentedVC.dismissalIsInteractive) {
presentedVC.dismissAnimationController = animationController;
}
return animationController;
}
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator
{
return nil;
}
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator
{
ViewController2 * presentedVC = (ViewController2 *)self.presentedViewController;
if (presentedVC.dismissalIsInteractive) {
return presentedVC;
}
else {
return nil;
}
}
#end
ViewController2.h
#import <UIKit/UIKit.h>
#import "DismissAnimationController.h"
#interface ViewController2 : UIViewController <UIViewControllerInteractiveTransitioning>
#property (weak, nonatomic) UIView * contentView;
#property (nonatomic, readonly) BOOL dismissalIsInteractive;
#property (strong, nonatomic) DismissAnimationController * dismissAnimationController;
#end
ViewController2.m
#import "ViewController2.h"
#interface ViewController2 ()
#property (strong, nonatomic) id<UIViewControllerContextTransitioning> transitionContext;
#end
#implementation ViewController2
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_dismissalIsInteractive = NO;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
// Set up content view.
CGRect frame = UIEdgeInsetsInsetRect(self.view.bounds, UIEdgeInsetsMake(15, 15, 15, 15));
UIView * contentView = [[UIView alloc] initWithFrame:frame];
self.contentView = contentView;
contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
contentView.backgroundColor = [UIColor cyanColor];
contentView.layer.borderColor = [UIColor blueColor].CGColor;
contentView.layer.borderWidth = 2;
[self.view addSubview:contentView];
// Set up pan dismissal gesture recognizer.
UIPanGestureRecognizer * panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dismissalPan:)];
[self.view addGestureRecognizer:panGesture];
}
// --------------------
- (void)dismissalPan:(UIPanGestureRecognizer *)panGesture
{
switch (panGesture.state) {
case UIGestureRecognizerStateBegan: {
_dismissalIsInteractive = YES;
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint translation = [panGesture translationInView:self.view];
CGFloat percent;
if (translation.y > 0) {
percent = translation.y / self.view.bounds.size.height;
percent = MIN(percent, 1.0);
}
else {
percent = 0;
}
// Swiping content view down.
CGPoint center;
center.x = CGRectGetMidX(self.view.bounds);
center.y = CGRectGetMidY(self.view.bounds);
if (translation.y > 0) {
center.y += translation.y; // Only allow swiping down.
}
self.contentView.center = center;
self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:(0.5 * (1.0 - percent))];
[self.transitionContext updateInteractiveTransition:percent];
break;
}
case UIGestureRecognizerStateEnded: // Fall through.
case UIGestureRecognizerStateCancelled: {
_dismissalIsInteractive = NO;
id<UIViewControllerContextTransitioning> transitionContext = self.transitionContext;
self.transitionContext = nil;
DismissAnimationController * dismissAnimationController = self.dismissAnimationController;
self.dismissAnimationController = nil;
CGPoint translation = [panGesture translationInView:self.view];
if (translation.y > 100) {
// Complete dismissal.
[dismissAnimationController animateTransition:transitionContext];
}
else {
// Cancel dismissal.
void (^animations)() = ^() {
CGPoint center;
center.x = CGRectGetMidX(self.view.bounds);
center.y = CGRectGetMidY(self.view.bounds);
self.contentView.center = center;
self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
};
void (^completion)(BOOL) = ^(BOOL finished) {
[transitionContext cancelInteractiveTransition];
[transitionContext completeTransition:NO];
};
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:animations completion:completion];
}
break;
}
default: {
break;
}
}
}
// --------------------
// View Controller Interactive Transitioning Methods.
- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
self.transitionContext = transitionContext;
}
#end
PresentAnimationController.h
#import <Foundation/Foundation.h>
#interface PresentAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
#end
PresentAnimationController.m
#import "PresentAnimationController.h"
#import "ViewController2.h"
#implementation PresentAnimationController
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController * fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
ViewController2 * toVC = (ViewController2 *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView * containerView = [transitionContext containerView];
CGPoint toCenter = fromVC.view.center;
CGRect toBounds = fromVC.view.bounds;
toVC.view.center = toCenter;
toVC.view.bounds = toBounds;
[toVC.view layoutIfNeeded];
[containerView addSubview:fromVC.view];
[containerView addSubview:toVC.view];
CGRect contentViewEndFrame = toVC.contentView.frame;
CGRect contentViewStartFrame = contentViewEndFrame;
contentViewStartFrame.origin.y += contentViewStartFrame.size.height;
toVC.contentView.frame = contentViewStartFrame;
UIColor * endBackgroundColor = toVC.view.backgroundColor;
toVC.view.backgroundColor = [UIColor clearColor];
void (^animations)() = ^() {
toVC.contentView.frame = contentViewEndFrame;
toVC.view.backgroundColor = endBackgroundColor;
};
void (^completion)(BOOL) = ^(BOOL finished) {
toVC.view.autoresizingMask = UIViewAutoresizingNone;
toVC.view.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint * centerXConstraint = [NSLayoutConstraint constraintWithItem:toVC.view
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:fromVC.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0];
NSLayoutConstraint * centerYConstraint = [NSLayoutConstraint constraintWithItem:toVC.view
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:fromVC.view
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0];
NSLayoutConstraint * widthConstraint = [NSLayoutConstraint constraintWithItem:toVC.view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:fromVC.view
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0];
NSLayoutConstraint * heightConstraint = [NSLayoutConstraint constraintWithItem:toVC.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:fromVC.view
attribute:NSLayoutAttributeHeight
multiplier:1
constant:0];
[containerView addConstraint:centerXConstraint];
[containerView addConstraint:centerYConstraint];
[containerView addConstraint:widthConstraint];
[containerView addConstraint:heightConstraint];
[transitionContext completeTransition:YES];
};
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:animations completion:completion];
}
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
#end
DismissAnimationController.h
#import <Foundation/Foundation.h>
#interface DismissAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
#end
DismissAnimationController.m
#import "DismissAnimationController.h"
#import "ViewController2.h"
#implementation DismissAnimationController
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
ViewController2 * fromVC = (ViewController2 *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController * toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView * containerView = [transitionContext containerView];
[containerView addSubview:toVC.view];
[containerView addSubview:fromVC.view];
void (^animations)() = ^() {
CGRect contentViewEndFrame = fromVC.contentView.frame;
contentViewEndFrame.origin.y = CGRectGetMaxY(fromVC.view.bounds) + 15;
fromVC.contentView.frame = contentViewEndFrame;
fromVC.view.backgroundColor = [UIColor clearColor];
};
void (^completion)(BOOL) = ^(BOOL finished) {
if ([transitionContext isInteractive]) {
[transitionContext finishInteractiveTransition];
}
[transitionContext completeTransition:YES];
};
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:animations completion:completion];
}
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
#end
AppDelegate.m
#import "AppDelegate.h"
#import "ViewController1.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
ViewController1 * vc = [[ViewController1 alloc] initWithNibName:nil bundle:nil];
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
return YES;
}
#end
I think I found your problem. in your PresentAnimationController.m you specify toVC.view.translatesAutoresizingMaskIntoConstraints = NO; and you set all of your constraints in the completion block you set in
- (void)animateTransition:
Comment that line out and all of the constraints and addConstraint: calls and it should work
EDIT:
Just saw it worked only when the gesture was cancelled and not when the view is initially displayed. Comment out everything in the completion block except for
[transitionContext completeTransition:YES];

UIKit Dynamics in a Custom Segue

I'm trying to make a view fall as a custom segue transition, but when the perform method is called in the UIStoryboardSegue implementation, it does not fall. I have tried moving the view to be dropped into the source's view to see if it does anything, but it doesn't.
-(void)perform {
UIViewController *src = (UIViewController *)self.sourceViewController;
UIViewController *dest = (UIViewController *)self.destinationViewController;
UIView *viewdrop = [dest.view snapshotViewAfterScreenUpdates:YES];
viewdrop.frame = CGRectMake(0, -src.view.frame.size.height, dest.view.frame.size.width, dest.view.frame.size.height);
[src.view addSubview:viewdrop];
animator = [[UIDynamicAnimator alloc] initWithReferenceView:src.view];
UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[viewdrop]];
[animator addBehavior:gravityBehavior];
}
The reason it doesn't drop is because the gravity behavior takes time, but the segue itself is deallocated as soon as the perform method finishes. So, you need a way to keep the segue alive at least until the movement is complete. One way to do this is to make a strong property for the segue in the source view controller, and set its value in prepareForSegue,
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
self.dropSegue = segue;
}
I made a modified version of your segue that also adds a collision behavior, and sets the source view controller as the delegate of the collision behavior, so I can use the delegate method, collisionBehavior:endedContactForItem:withBoundaryIdentifier:, to set the dropSegue property to nil (after a slight delay) which causes the segue to be deallocated,
-(void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier {
NSLog(#"collision ended with %#", identifier);
[self performSelector:#selector(setDropSegue:) withObject:nil afterDelay:1];
}
Here is my version of the gravity drop segue,
#interface RDSegue ()
#property (strong, nonatomic) UIDynamicAnimator *animator;
#end
#implementation RDSegue
-(id)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination {
if (self = [super initWithIdentifier:identifier source:source destination:destination]) {
UIViewController *src = self.sourceViewController;
UIViewController *dest = self.destinationViewController;
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:src.view];
[src addChildViewController:dest];
[dest didMoveToParentViewController:src];
dest.view.frame = CGRectMake(0, -src.view.bounds.size.height, src.view.bounds.size.width, src.view.bounds.size.height);
[src.view addSubview:dest.view];
}
return self;
}
-(void)perform {
UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[[self.destinationViewController view]]];
UICollisionBehavior *collide = [[UICollisionBehavior alloc] initWithItems:#[[self.destinationViewController view]]];
CGPoint left = CGPointMake(self.animator.referenceView.bounds.origin.x, self.animator.referenceView.bounds.origin.y + self.animator.referenceView.bounds.size.height);
CGPoint right = CGPointMake(self.animator.referenceView.bounds.origin.x + self.animator.referenceView.bounds.size.width, self.animator.referenceView.bounds.origin.y + self.animator.referenceView.bounds.size.height);
[collide addBoundaryWithIdentifier:#"bottom" fromPoint:left toPoint:right];
[collide setCollisionDelegate:self.sourceViewController];
[self.animator addBehavior:gravityBehavior];
[self.animator addBehavior:collide];
}
-(void)dealloc {
NSLog(#"In dealloc");
}

Can't switch between 2 view controller's with a Segmented Control

I created a UISegmentedControl programmatically in the navigation bar of a UIViewController and I want to be able to switch view controllers when i toggle the segmented control.
This is what I have so far:
#interface TVExploreViewController : TVViewController
#property (nonatomic, strong) UISegmentedControl *scopeControl;
#property (nonatomic, assign) NSInteger scope;
#property (nonatomic, strong) UIViewController *premiumContentViewController;
#property (nonatomic, strong) UIViewController *trendingContentViewController;
#property (nonatomic, strong) UIViewController *currentViewController;
#end
TVExploreViewController's implementation:
- (void)viewDidLoad {
[super viewDidLoad];
_scopeControl = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:#"TV & MOVIES", #"VIRAL CLIPS", nil]];
[_scopeControl setFrame:CGRectMake(80, 0, 200, 30)];
[_scopeControl addTarget:self
action:#selector(scopeChanged:)
forControlEvents:UIControlEventValueChanged];
[[self navigationItem] setTitleView:_scopeControl];
[_scopeControl setSelectedSegmentIndex:0];
_premiumContentViewController = [[TVPremiumContentViewController alloc] init];
_trendingContentViewController = [[TVTrendingFeedController alloc] init];
[self setScope:0];
}
- (void)scopeChanged:(id)sender {
[self setScope:self.scopeControl.selectedSegmentIndex];
}
- (void)transitionToViewController:(UIViewController *)controller
{
[self.currentViewController willMoveToParentViewController:nil];
if (controller) {
[self addChildViewController:controller];
}
controller.view.frame = self.view.bounds;
[self.view addSubview:controller.view];
[self.currentViewController.view removeFromSuperview];
[self.currentViewController removeFromParentViewController];
[controller didMoveToParentViewController:self];
self.currentViewController = controller;
}
- (void)setScope:(NSInteger)scope {
if (scope != _scope) {
_scope = scope;
UIViewController *nextController = nil;
if (_scope == 0) {
nextController = self.premiumContentViewController;
} else if (_scope == 1) {
nextController = self.trendingContentViewController;
}
[self transitionToViewController:nextController];
}
}
When I toggle the segmented control in TVExploreViewController, the rest of the view stays white and no view controller is loaded. Anyone know what I'm doing wrong?
This is not an answer, but It is convenient to write some log code here.
Can you show me the log in the method ?
- (void)transitionToViewController:(UIViewController *)controller
{
[self.currentViewController willMoveToParentViewController:nil];
if (controller) {
[self addChildViewController:controller];
}
controller.view.frame = self.view.bounds;
[self.view addSubview:controller.view];
[self.currentViewController.view removeFromSuperview];
[self.currentViewController removeFromParentViewController];
[controller didMoveToParentViewController:self];
self.currentViewController = controller;
// can you show me the log here ?
NSLog(#"%#, %#, %#", controller.view, controller.view.superview, self.view) ;
// you should see the subviews that you added.
NSLog(#"subviews:%#", [controller.view subviews]) ;
}
It is really not a good idea to add Viewcontrollers to ViewController,you should always have one ViewController on screen. You could do this using NavigationController with pushing with no animation.

Resources