How can I get custom transitions (iOS7) when pushing a view controller onto UINavigationController? I tried setting the TransitioningDelegate both in the UINavigationController and also on the controller I'm pushing
The methods never get called.
All examples I find use custom transitions when presenting modally.
#rounak has the right idea, but sometimes it helps to have code ready without having to download from github.
Here are the steps that I took:
Make your FromViewController.m conform to UINavigationControllerDelegate. Other sample code out there tells you to conform to UIViewControllerTransitioningDelegate, but that's only if you're presenting the ToViewController.
#interface ViewController : UIViewController
Return your custom transition animator object in the delegate callback method in FromViewController:
- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
TransitionAnimator *animator = [TransitionAnimator new];
animator.presenting = (operation == UINavigationControllerOperationPush);
return animator;
}
Create your custom animator class and paste these sample methods:
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return 0.5f;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
// Grab the from and to view controllers from the context
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// Set our ending frame. We'll modify this later if we have to
CGRect endFrame = CGRectMake(80, 280, 160, 100);
if (self.presenting) {
fromViewController.view.userInteractionEnabled = NO;
[transitionContext.containerView addSubview:fromViewController.view];
[transitionContext.containerView addSubview:toViewController.view];
CGRect startFrame = endFrame;
startFrame.origin.x += 320;
toViewController.view.frame = startFrame;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
toViewController.view.frame = endFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
else {
toViewController.view.userInteractionEnabled = YES;
[transitionContext.containerView addSubview:toViewController.view];
[transitionContext.containerView addSubview:fromViewController.view];
endFrame.origin.x += 320;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic;
fromViewController.view.frame = endFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
}
Essentially, the animator is the object doing the heavy lifting. Of course, you can make your UINavigationControllerDelegate be a separate object, but that depends on how your architect your app.
objc.io's post on view controller transitions are specifically for pushing and popping view controllers. http://objc.io/issue-5/view-controller-transitions.html
I've done this animation (http://i.imgur.com/1qEyMu3.gif) solely based on the objc.io post.
In short you have to have a class(es) implementing UINavigationControllerDelegate, and UIViewControllerAnimatedTransitioning with the required methods for returning the correct animator, and performing the animations.
You can look at my demo project which demonstrates using custom transitions in UINavigationController. Look at https://github.com/Vaberer/BlurTransition.
EDIT: Just realised this might not answer your question. But it is an alternative.
If you're using a storyboard you can do a custom transition by creating a custom segue.
In the attributes inspector change the segue class name to your custom transition class e.g. MySegue. Then create the MySegue class and implement the -(void)perform method to perform your transition.
- (void) perform{
UIViewController *source = self.sourceViewController;
UIViewController *destination = self.destinationViewController;
[UIView transitionFromView:source.view
toView:destination.view
duration:0.50f
options:UIViewAnimationOptionTransitionFlipFromTop
completion:nil];
}
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 UIViewControllerTransitioningDelegate to build custom transitions between two view controllers (from a MKMapView) to a custom Camera built on (AVFoundation). Everything goes well until I call the presentViewController and the phone seems to hang for about 1 second (when I log everything out). This even seems to happen when I am transitioning to a much simpler view (I have a view controller that only displays a UITextview and even with that there appears to be about a .4 - .5 second delay before the transition is actually called).
This is currently how I am calling the transition
dispatch_async(dispatch_get_main_queue(), ^{
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
CameraViewController *cvc2 = [sb instantiateViewControllerWithIdentifier:#"Camera"];
cvc2.modalPresentationStyle = UIModalPresentationFullScreen; // Needed for custom animations to work
cvc2.transitioningDelegate = self; //Conforms to the UIViewControllerTransitioningDelegate protocol
[self presentViewController:cvc2 animated:YES completion:nil];
});
Here is my animateTransition method for that call. Very straight forward and currently the view that is presenting this only has a MkMapView on it (no additional views or methods).
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
if (self.type == MapAnimationTypePresent) {//From map to another view
UIView *containerView = [transitionContext containerView];
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// Amazing category for iOS 7 compatibility found here - http://stackoverflow.com/a/25193675/2939977
UIView *toView = [toViewController viewForTransitionContext:transitionContext];
UIView *fromView = [fromViewController viewForTransitionContext:transitionContext];
toView.frame = self.view.frame;
fromView.frame = self.view.frame;
//Add 'to' view to the hierarchy
toView.alpha = 0;
[containerView insertSubview:toView aboveSubview:fromView];
[UIView animateWithDuration:.5 animations:^{
toView.alpha = 1;
}completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
Any help is greatly appreciated.
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'm using the iOS 7 UIviewControllerAnimatedTransitioning protocol to present a modal ViewController with a custom animation. The animation works correctly, however, I want the newly presented ViewController to have a different status bar style than the presenting VC.
What I'm seeing is that -(UIStatusBarStyle)preferredStatusBarStyle gets called on the PRESENTING ViewController (several times in fact) and never on the newly presented ViewController. If I remove the custom animation everything with the status bar works as I'd expect.
Is there something special I need to do in my animateTransition function to update the root view controller or something? I've tried manually setting the statusBar with [UIApplication sharedApplication] setStatusBarStyle but it doesn't work (I think because I'm using the ios 7 view controller based status bar styling).
This is my code for animateTransition:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UICollectionViewCell *activeCell;
if ([self.collectionView.visibleCells containsObject:self.cellForActiveIdeaVC]) {
activeCell = self.cellForActiveIdeaVC;
}
UIView *container = transitionContext.containerView;
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
CGRect beginFrame;
if (activeCell) {
beginFrame = [container convertRect:activeCell.bounds fromView:activeCell];
} else {
beginFrame = CGRectMake(container.width / 2, container.height / 2, 0, 0);
}
CGRect endFrame = [transitionContext initialFrameForViewController:fromVC];
UIView *move = nil;
if (toVC.isBeingPresented) {
toView.frame = endFrame;
move = [toView snapshotViewAfterScreenUpdates:YES];
move.frame = beginFrame;
} else {
if (activeCell) {
move = [activeCell snapshotViewAfterScreenUpdates:YES];
} else {
move = [fromView snapshotViewAfterScreenUpdates:YES];
}
move.frame = fromView.frame;
[fromView removeFromSuperview];
}
[container addSubview:move];
[UIView animateWithDuration:.5
delay:0
usingSpringWithDamping:700
initialSpringVelocity:15
options:0
animations:^{
move.frame = toVC.isBeingPresented ? endFrame : beginFrame;
}
completion:^(BOOL finished) {
[move removeFromSuperview];
if (toVC.isBeingPresented) {
toView.frame = endFrame;
[container addSubview:toView];
} else {
if (self.cellForActiveIdeaVC) {
self.cellForActiveIdeaVC = nil;
}
}
[transitionContext completeTransition:YES];
}];
}
Any pointers much appreciated!
With iOS 7 custom transitions, it's possible to present a view controller that isn't fullscreen and therefore wouldn't affect the statusbar appearance. You have to explicitly tell iOS that your custom presented view controller will, in fact, control the status bar's appearance.
UIViewController *controllerToPresent = [...]
controllerToPresent.modalPresentationStyle = UIModalPresentationStyleCustom;
controllerToPresent.modalPresentationCapturesStatusBarAppearance = YES;
[self presentViewController:controllerToPresent animated:YES completion:nil];
There's some more information here. Hope that helps!
This worked for me:
[UIView animateWithDuration:0.25
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
_preferredBarStyle = UIStatusBarStyleLightContent;
[self setNeedsStatusBarAppearanceUpdate];
}];
And then you just have to return this value on the preferredStatusBarStyle method:
- (UIStatusBarStyle) preferredStatusBarStyle {
return _preferredBarStyle;
}
I hope it helps!
I'm trying to build a custom transition in iOS 7. The transition occurs fine, but then when transition context complete transition the modal screen disappears from the view entirely. I've followed several tutorials and I don't see what I am doing wrong. In addition, if I don't call "complete transition" then the view stays, but will not receive any touch events. I checked in Reveal App and there is no view sitting on top of it. Any ideas?
Here is the method where I initiate the transition
- (IBAction)settingsButtonClicked:(id)sender
{
UINavigationController *navigationController =[[self storyboard] instantiateViewControllerWithIdentifier:#"SettingsNavigationViewController"];
navigationController.transitioningDelegate = self;
[self presentViewController:navigationController animated:YES completion:nil];
}
Here is the code for the custom transition:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toViewController.view];
CGRect sourceRect = [transitionContext initialFrameForViewController:fromViewController];
CGRect initialTargetFrame = [transitionContext initialFrameForViewController:toViewController];
CGRect initialFrame = CGRectMake(sourceRect.size.width + initialTargetFrame.size.width, 0, initialTargetFrame.size.width, initialTargetFrame.size.height);
CGPoint destinationPoint = CGPointMake(sourceRect.size.width - 500, 0);
CGAffineTransform translate = CGAffineTransformMakeTranslation(initialFrame.origin.x, initialFrame.origin.y);
toViewController.view.transform = translate;
[UIView animateWithDuration:PRESENT_DURATION delay:0 options:UIViewAnimationOptionCurveEaseOut
animations:^{
toViewController.view.transform = CGAffineTransformMakeTranslation(destinationPoint.x, destinationPoint.y);
}
completion:^(BOOL completed) {
if (completed) {
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}
}];
}
So I found out what I was doing wrong. I was changing the modalPresentationStyle to custom on the view controller that was being popped onto the navigation controller when I should have been setting it on the navigation controller itself. I added this line to the settingsButtonClicked method above and it worked properly.
navigationController.modalPresentationStyle = UIModalPresentationCustom;