1) I have a task to present and dismiss modal UIViewController with custom animation.
2) Custom animation is to change alpha and move one child element
3) I created FadeInAnimationController and FadeOutAnimationController classes to implement UIViewControllerAnimatedTransitioning like this:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
// obtain state from the context
CIToViewController *toViewController = (CIToViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// obtain the container view
UIView *containerView = [transitionContext containerView];
// set the intial state
toViewController.view.alpha = 0.0f;
toViewController.elementBottomPosition.constant -= 20.0f;
[toViewController.view layoutIfNeeded];
// add the view
[containerView addSubview:toViewController.view];
// animate
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
toViewController.view.alpha = 1.0f;
toViewController.elementBottomPosition.constant += 20.0f;
[toViewController.view layoutIfNeeded];
}
completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
4) elementBottomPosition is NSLayoutConstraint and it works fine for Present animation
5) PROBLEM:
For Dismiss animation NSLayoutConstraint doesn't work, so I had to do the same thing using Frame and it worked. It is not very good with AutoLayout and iOS7, but since I need to dismiss this view I don't care for its autolayout.
So the question is why NSLayoutConstraint approach doesn't work??? I logged constraints in animateTransition:
NSLog(#"constraints %#", fromViewController.view.constraints);
And they are still present.
Do not set auto layout constants in your animation block.
toViewController.elementBottomPosition.constant -= 20.0f;
[self.view layoutIfNeeded];
toViewController.elementBottomPosition.constant += 20.0f;
//Animation block here ^{
[self.view layoutIfNeeded];
}
Related
I am trying to get UIViewControllerContextTransitioning working.
What I want:
What I would like to have, is presenting modal view controller with custom animation and transparent background.
Wthat I did:
I created animator implementing UIViewControllerTransitioningDelegate,
Set for modal controller:
self.modalPresentationStyle = UIModalPresentationCustom;
self.transitioningDelegate = self;
What I achieved so far,
Modal controller animates presenting and dismissing view correctly, but after dismissing is finished, entire app becomes black. I used xCode tool to pick what's in window hierarchy, and there is nothing. My guess is, that I changed VC's superview when adding to context's container.
Animator
#implementation AlertAnimator
const static CGFloat kAnimationDuration = 1.2;
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *to = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *from = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
if (self.transitionType == ModalAnimatedTransitioningTypePresent) {
[self animatePresentingInContext:transitionContext toVC:to fromVC:from];
} else if (self.transitionType == ModalAnimatedTransitioningTypeDismiss) {
[self animateDismissingInContext:transitionContext toVC:to fromVC:from];
}
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return kAnimationDuration;
}
- (void)animatePresentingInContext:(id<UIViewControllerContextTransitioning>)transitionContext toVC:(UIViewController *)toVC fromVC:(UIViewController *)fromVC {
CGRect fromVCRect = [transitionContext initialFrameForViewController:fromVC];
CGRect toVCRect = fromVCRect;
toVCRect.origin.y = toVCRect.size.height;
toVC.view.frame = toVCRect;
UIView *container = [transitionContext containerView];
[container addSubview:fromVC.view];
[container addSubview:toVC.view];
[UIView animateWithDuration:kAnimationDuration animations:^{
toVC.view.frame = fromVCRect;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
- (void)animateDismissingInContext:(id<UIViewControllerContextTransitioning>)transitionContext toVC:(UIViewController *)toVC fromVC:(UIViewController *)fromVC {
CGRect fromVCRect = [transitionContext initialFrameForViewController:fromVC];
fromVCRect.origin.y = fromVCRect.size.height;
UIView *container = [transitionContext containerView];
[container addSubview:toVC.view];
[container addSubview:fromVC.view];
[UIView animateWithDuration:kAnimationDuration animations:^{
fromVC.view.frame = fromVCRect;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
#end
Code example:
https://www.dropbox.com/s/oaghtgwvga4nxs4/Test.zip?dl=0
Question:
What I am doing wrong and why screen becomes black?
if this is a presenting animator you should not add the fromVC.view as a subview because its already there. This will cause it to bug out. Try it out and tell me what happens. If its a dismissal animator you shouldn't add the presentingViewController to the hierarchy either.
I am using some UIViewControllers contained in a UINavigationController.
One is presented with a push segue from a UIButton on the storyboard, and is then dismissed using a swipe gesture which calls
popViewControllerAnimated
I'm using a UINavigationControllerDelegate to provide a custom object which conforms to UINavigationControllerDelegate. The code for animateTransition is shown below.
My problem is that the first time this runs, the view animates in when presenting, but every time after that, it doesn't animate (it just appears instantly).
Can anyone help?
Thanks!
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
//Get references to the view hierarchy
UIView *containerView = [transitionContext containerView];
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
if (self.isPresenting) {
[containerView insertSubview:toViewController.view belowSubview:fromViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.transform = CGAffineTransformMakeTranslation(-1000, 0);
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
} else if (!self.isPresenting) {
//Add 'to' view to the hierarchy
[containerView insertSubview:toViewController.view belowSubview:fromViewController.view];
//Scale the 'from' view down until it disappears
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//toViewController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);
fromViewController.view.transform = CGAffineTransformMakeScale(0.01, 0.01);
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
}
I ran into a similar set of issues after upgrading to iOS8. A few things to check:
Make sure to call these before starting the animations:
[toViewController beginAppearanceTransition:YES animated:YES];
[fromViewController beginAppearanceTransition:NO animated:YES];
Then call these in the completion block:
[toViewController endAppearanceTransition];
[fromViewController endAppearanceTransition];
[transitionContext completeTransition:finished];
The above is needed to ensure viewWillAppear: and viewWillDisappear: are called at the right times.
Set modalPresentationStyle = UIModalPresentationFullScreen, instead of UIModalPresentationCustom (which behaves very differently).
Hope this helps.
Cheers!
Be careful setting self.navigationController.delegate
you probably place it in somewhere that only runs once like ViewDidLoad
try placing it in ViewDidAppear
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
self.navigationController.delegate = self
}
I have an issue with custom transitions on the iPad. I create a custom transition that animates correctly and seems to work (i.e. the transition occurs). However, when I arrive at the destination view controller (after executing the isLoggedIn block), the destination view controller is unresponsive (it doesn't respond to touch events). II have a feeling it has something to do with the call to [container insertSubview:toViewController.view belowSubview:fromViewController.view]; because if I call [container insertSubview:toViewController.view aboveSubview:fromViewController.view]; the touches work as expected (but you cannot see the animation, as it happens on the source view controller).
Any idea why the touch events aren't being recognized?
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
//Prepare the view
if (self.isLoggedIn) {
//Insert the main view under the login view
CGRect frame = CGRectMake(0, 0, toViewController.view.frame.size.height,
toViewController.view.frame.size.width);
toViewController.view.frame = frame;
[container insertSubview:toViewController.view belowSubview:fromViewController.view];
} else {
CGRect frame = CGRectMake(0, 0, toViewController.view.frame.size.height,
toViewController.view.frame.size.width);
toViewController.view.frame = frame;
if([toViewController respondsToSelector:#selector(openWalls)]) {
[(DJVLoginViewController*)toViewController openWalls];
}
if([toViewController respondsToSelector:#selector(toggleLoginViewsAlpha:)]) {
[(DJVLoginViewController*)toViewController toggleLoginViewsAlpha:0];
}
//Insert the login view above the main view
[container insertSubview:toViewController.view aboveSubview:fromViewController.view];
}
//Make animations
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
if (self.isLoggedIn) {
//Perform animation
} else {
//Perform animation
}
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
Try to remove fromView from superview:
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
if (self.isLoggedIn) {
//Perform animation
} else {
//Perform animation
}
} completion:^(BOOL finished) {
[fromViewController.view removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
i'm using iOS 7 Custom transition to present a UINavigationController.
but there is a problem. while its animating, the size of navigation bar is only 44points. then after done animating, navigation controllers figured out there is a status bar, so its added 20points for status bar.
my question is, is there possible to set navigation bar to 64point when its animating, so it doesn't change anymore when its done animating.
please see it here for more detail Custom View Transitions
This is my custom animation code:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
CGRect finalFrame = [transitionContext finalFrameForViewController:toViewController];
UIView *containerView = [transitionContext containerView];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
toViewController.view.frame = CGRectOffset(finalFrame, 0, screenBounds.size.height);
[containerView addSubview:toViewController.view];
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:0.6 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
toViewController.view.frame = finalFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
UPDATE: somebody fixed this problem. but pretty hacky.
add this code after added toViewController.view to containerView.
if ([toViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*) toViewController;
UINavigationBar* bar = navigationController.navigationBar;
CGRect frame = bar.frame;
bar.frame = CGRectMake(frame.origin.x, frame.origin.y + 20.0f, frame.size.width, frame.size.height);
}
is there a better way to do it?
I had the same problem, and solved adding the toViewController to container before I set this frame.
Invert the lines like as follow:
[containerView addSubview:toViewController.view];
toViewController.view.frame = CGRectOffset(finalFrame, 0, screenBounds.size.height);
I'm testing around the new iOS 7 custom transition API but i have some troubles with the navigation controller case. I tried a very basic test for the moment with this :
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
[transitionContext completeTransition:YES];
}
As you guessed, this code do nothing except to complete the transition with no animations.
But here's the problem : if it's working normally with present / dismiss a controller, all i see with push and pop methods is a black screen, as if [transitionContext completeTransition:YES] didn't work.
I've set all the delegate properties and delegate methods properly, since this method is called all the time (present, dismiss, push, pop).
Did someone already face this issue ?
Try something more like this, I was having trouble with it as well and this helped make more sense of it
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
// 1. obtain state from the context
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
CGRect finalFrame = [transitionContext finalFrameForViewController:toViewController];
// 2. obtain the container view
UIView *containerView = [transitionContext containerView];
// 3. set initial state
CGRect screenBounds = [[UIScreen mainScreen] bounds]; toViewController.view.frame =
CGRectOffset(finalFrame, 0, screenBounds.size.height);
// 4. add the view
[containerView addSubview:toViewController.view];
// 5. animate
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration animations:^{
toViewController.view.frame = finalFrame;
} completion:^(BOOL finished) {
// 6. inform the context of completion
[transitionContext completeTransition:YES];
}];
}
Source: http://www.raywenderlich.com/forums/viewtopic.php?f=37&t=8851