I have an Image view in my View Controller. I want to dismiss the view controller when a Pan gesture is recognized towards the down side of the view controller. While I have achieved dismiss the view controller with a Pan gesture, I am trying to figure out how can I add an animation before dismissing just like as in the Photos application in the iPhone.
-(void)addPanGesture{
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dismissViewGes:)];
gestureRecognizer.delegate = self;
[self.view addGestureRecognizer:gestureRecognizer];
}
-(void)dismissViewGes:(UIPanGestureRecognizer *)gesture {
CGPoint velocity = [gesture velocityInView:self.view];
if(velocity.y > 0)
{
CATransition *transition = [[CATransition alloc]init];
transition.duration = 2;
[transition setValue:[NSNumber numberWithFloat:0.5f] forKey:kCATransitionFade];
transition.subtype = kCATransitionFromTop;
[self.view.layer addAnimation:transition forKey:nil];
[[NetWrapper shared] addRemovedController:self];
[self dismissViewControllerAnimated:NO completion:nil];
}
}
I tried adding a CATransition hoping that the fade out animation will work but it is not working, the view dismisses as soon as the pan gesture is applied.
How can i add in a fade out while with the view dismissing just like in the photos application.
Try this:
#IBAction func clickDismiss(_ sender: Any) {
let transition: CATransition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
transition.subtype = kCATransitionFromBottom
self.view.window!.layer.add(transition, forKey: nil)
self.dismiss(animated: false, completion: nil)
}
I present a SubViewController and add a UIButton on it. Add those codes to The button click event and It's work for me.
Swift 4.2, iOS 11
Here is an elegant way using extensions:
extension CATransition {
func fadeTransition() -> CATransition {
let transition = CATransition()
transition.duration = 0.4
transition.type = CATransitionType.fade
transition.subtype = CATransitionSubtype.fromRight
return transition
}
}
Then you can just write these three lines of code in a function or inside the action button in your UIViewController:
private func dismissVC() {
let transition = CATransition().fadeTransition()
navigationController?.view.layer.add(transition, forKey: kCATransition)
navigationController?.popViewController(animated: false)
}
If you need to add animation on presenting or dismissing a ViewController you should use UIViewControllerAnimatedTransitioning, there's a similar answer here:
How do I do a Fade/No transition between view controllers
If you need it to be interactive, you can use UIPercentDrivenInteractiveTransition, which is a class that controls the transition in percentages.
About all this there's a great sample code from Apple:
https://developer.apple.com/library/content/samplecode/CustomTransitions/Introduction/Intro.html
It's a little old but it's simple.
Related
To me the transition from right to left while opening new view is the most essential part of iPhone UI. How can it be that nearly in 2020 there is no way to have it for transition between two ViewControllers? At least I could not find the way.
Due to reasons I am not using NavigationController in the app. Instead I just call self.present(newVC) to open new one and self.dismiss() to go back.
I just want the new ViewController to slide from the right, that's all.
Any hints?
It seems you want to slide from right to left.
So you can do it using a custom segue transition.
First Create CATransition extenstion than add layer to your segue .
func rightToLeft() -> CATransition {
self.duration = 0.1 //set the duration to whatever you'd like.
self.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
self.type = kCATransitionReveal
self.subtype = kCATransitionFromRight
return self
}
}
Implementation :
DispatchQueue.main.async { //make sure all UI updates are on the main thread.
nav?.view.layer.add(CATransition().rightToLeft(), forKey: nil)
nav?.pushViewController(YourViewController(), animated: false)
}
So you want just slide from right to left here is code :
let transition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
let presentedVC = self.storyboard!.instantiateViewController(withIdentifier: "PresentingViewController") \\ view Controller identifire in storyboard
presentedVC.view.backgroundColor = UIColor.orange
present(presentedVC, animated: false, completion: nil)
Here is Sample project
What I am trying to do is to make a custom transition effect for my modal presented view controller (only for dismissing it). I just want it to look like the original but slower. The problem is I am getting a black shadow between my modal presented VC and the VC I want to go back.
That's my code so far:
let transition: CATransition = CATransition()
transition.duration = 0.6
transition.type = kCATransitionReveal
transition.subtype = kCATransitionFromBottom
self.view.window!.layer.add(transition, forKey: nil)
self.dismiss(animated: false, completion: nil)
I am creating a board game where each board piece is an instance of a view controller. I have subclassed UIStoryboardSegue so that each piece smoothly flows from one to the other. However, my game piece stays centered in this frame during the transition. I would like to have it so that my gamePiece (a UIImageView) stays in the center of the screen throughout the entire transition, but haven't had any luck searching.
Here is my Segue subclass, if it helps at all:
import UIKit
import QuartzCore
class RightToLeftSegue: UIStoryboardSegue {
override func perform() {
let src: UIViewController = self.sourceViewController
let dst: UIViewController = self.destinationViewController
let transition: CATransition = CATransition()
let timeFunc : CAMediaTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
transition.duration = 1.25
transition.timingFunction = timeFunc
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromLeft
src.navigationController!.view.layer.addAnimation(transition, forKey: kCATransition)
src.navigationController!.pushViewController(dst, animated: true)
}
}
You can add this UIImageView to the window as subview before the transition happens.This way you will have it throughout the transition.
Let me know if this works out for you.
When i push a viewcontroller the animation is always from right to left. I want to change that animation to the inverse left to right.
I have tried this code but doesnt work. The animation is still from right to left. How can i achive this simply and correctly? Need help please. Thanks in advance.
//NOT WORKING
myViewController *mController = [self.storyboard instantiateViewControllerWithIdentifier:#"myViewController"];
CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
[self.view.layer addAnimation:transition forKey:nil];
[self.navigationController pushViewController:mController animated:YES];
Since this question mentions iOS 7 I'm quite surprised the answer doesn't mention the Animated Transitions API introduced in iOS 7.
If you want to get straight into GitHub the objc.io guys have a great post with a linked project here
Take a look at the documentation and you will see the following:
#availability(iOS, introduced=7.0)
optional func navigationController(navigationController: UINavigationController,
animationControllerForOperation operation: UINavigationControllerOperation,
fromViewController fromVC: UIViewController,
toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
Or in Objective-C
- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
What this means is that from iOS 7 you can control how the navigation controller implements its animations for pushes and pops.
All that is needed is for you to set the delegate of the navigation controller and vend the correct animator object when appropriate. So lets get started:
1. Set the UINavigationControllerDelegate
I typically like to have an App-Wide Navigation Coordinator that manages all my transitions etc. So ideally you want some object that will be alive for the duration of the NavigationController. So we might have a coordinator that looks like this:
class NavigationCoordinationController: NSObject {
private(set) var navigationController:UINavigationController
required init(navigationController: UINavigationController) {
self.navigationController = navigationController
super.init()
navigationController.delegate = self
}
}
I typically create this in the App Delegate so it can be referenced anywhere. You could also set the delegate on a per view controller basis, creating a custom UINavigationController subclass, or wherever you see fit.
2. Create A Custom Animator
Now we need an object that conforms to the UIViewControllerAnimatedTransitioning protocol. This object will be called upon to apply the animation whenever an animated transition is needed. This example is a simple animation that fades and expands the fromView in the transition so its relevant for a Push.
class CustomPushExpansionTransitioner: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 1
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromView: UIView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toView: UIView = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let container = transitionContext.containerView()
container.addSubview(toView)
container.bringSubviewToFront(fromView)
toView.frame = container.convertRect(fromView.bounds, fromView: fromView)
UIView.animateWithDuration(transitionDuration(transitionContext),
animations: { () -> Void in
fromView.alpha = 0
fromView.transform = CGAffineTransformConcat(fromView.transform, CGAffineTransformMakeScale(1.3, 1.3))
}) { (complete) -> Void in
transitionContext.completeTransition(true)
}
}
}
3. Vend The Animator Where Necessary
Now we need to implement the UINavigationController delegate and vend our custom animator when needed.
extension NavigationCoordinationController: UINavigationControllerDelegate {
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .Push:
return LaunchToHomeTransitioner()
case .Pop:
break
case .None:
break
}
return nil
}
}
And there you go, you now have total control over your UINavigationController transitions
You are actually doing it right, but as far as I understand from your code, you are not overriding the method:
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
So, you need to inherit from UINavigationController and override the above method.
Here's how I do it (push + pop):
Push:
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
UIView *theWindow = self.view ;
if( animated ) {
CATransition *animation = [CATransition animation];
[animation setDuration:0.45f];
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromLeft];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
[[theWindow layer] addAnimation:animation forKey:#""];
}
//make sure we pass the super "animated:NO" or we will get both our
//animation and the super's animation
[super pushViewController:viewController animated:NO];
[self swapButtonsForViewController:viewController];
}
Pop :
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
UIView *theWindow = self.view ;
if( animated ) {
CATransition *animation = [CATransition animation];
[animation setDuration:0.45f];
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromRight];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
[[theWindow layer] addAnimation:animation forKey:#""];
}
return [super popViewControllerAnimated:NO];
}
NextViewController *next = [[NextViewController alloc] initWithNibName:#"NextViewController" bundle:nil];
[UIView transitionWithView:self.navigationController.view
duration:0.75
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[self.navigationController pushViewController:next animated:NO];
}
completion:nil];
Try Above code
You can try this as well.
CATransition *transition = [CATransition animation];
transition.duration = 0.5;
transition.type = kCATransitionMoveIn;
transition.subtype = kCATransitionFromLeft;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
DrawingResultsViewController *dr = [DrawingResultsViewController createAndShowProfile:YES];
[self.navigationController pushViewController:dr animated:YES];
If some one is looking for the same answer in Xamarin.iOS (based on gran33 answer) :
public override void PushViewController(UIViewController viewController, bool animated)
{
if (animated)
{
CATransition animation = new CATransition()
{
Duration = 0.5,
Type = CAAnimation.TransitionPush,
Subtype = CAAnimation.TransitionFromTop,
TimingFunction = AMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut)
};
View.Layer.AddAnimation(animation, CALayer.Transition);
base.PushViewController(viewController, false);
}
}
public override UIViewController PopViewController(bool animated)
{
if (animated)
{
CATransition animation = new CATransition()
{
Duration = 0.5,
Type = CAAnimation.TransitionPush,
Subtype = CAAnimation.TransitionFromBottom,
TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut)
};
View.Layer.AddAnimation(animation, CALayer.Transition);
return base.PopViewController(false);
}
return base.PopViewController(animated);
}
For Swift 4.0+
1 - Create a extension:
extension UINavigationController {
func customPopView(_ viewController: UIViewController) {
let transition: CATransition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromLeft
view.layer.add(transition, forKey: nil)
pushViewController(viewController, animated: false)
}
}
2 - Call:
self.navigationController?.customPopView(yourViewController)
Assuming myViewController is your view controller, pass custom transition to the nav controller layer.
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
[self.navigationController.view.layer addAnimation:transition forKey:#"pushViewController"];
[self.navigationController pushViewController:myViewController animated:NO];
If I simply call the push method with:
[self.navigationController pushViewController:viewController animated:YES];
then it uses a push animation. How do I change it to use a cross dissolve animation, like I can with a modal segue?
I use an extension for easy reuse:
extension UINavigationController {
func fadeTo(_ viewController: UIViewController) {
let transition: CATransition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.fade
view.layer.add(transition, forKey: nil)
pushViewController(viewController, animated: false)
}
}
Notice how animated is false; when you set it to true, you still see the standard 'push' animation (right to left).
You can use a CATransition as demonstrated in this answer:
CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.type = kCATransitionFade;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController pushViewController:viewController animated:NO];
The UINavigationControllerDelegate protocol has a method that can return a custom UIViewControllerAnimatedTransitioning object which will control the animation between the two view controllers involved in the transition.
Create an Animator class to control the cross-dissolve transition:
class Animator: NSObject, UIViewControllerAnimatedTransitioning {
let animationDuration = 0.25
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return animationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
toVC?.view.alpha = 0.0
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
transitionContext.containerView.addSubview(fromVC!.view)
transitionContext.containerView.addSubview(toVC!.view)
UIView.animate(withDuration: animationDuration, animations: {
toVC?.view.alpha = 1.0
}) { (completed) in
fromVC?.view.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
And provide it in your UINavigationControllerDelegate:
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return Animator()
}
Here is a more in-depth tutorial: https://web.archive.org/web/20191204115047/http://blog.rinatkhanov.me/ios/transitions.html
SWIFT 3, 4.2 -- as of October, 2019
let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey:nil)
You can set Push viewcontroller like this.
CATransition* transition = [CATransition animation];
transition.duration = 0.4;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
transition.type = kCATransitionFade;
[self.navigationController.view.layer addAnimation:transition forKey:#"kCATransition"];
[self.navigationController pushViewController:readerViewController animated:false];
You can set Pop viewcontroller like this.
CATransition* transition = [CATransition animation];
transition.duration = 0.4;
transition.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionEaseOut];
transition.type = kCATransitionFade;
[self.navigationController.view.layer addAnimation:transition
forKey:#"kCATransition"];
[self.navigationController popViewControllerAnimated:false];
Update Swift 5
Using prepare(for segue:) instead of pushViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// other preparations
let transition: CATransition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.fade
navigationController?.view.layer.add(transition, forKey: nil)
}
Following michaels answer you can use UINavigationController extension.. something like this
/// Create an animatied fade push with default duration time.
/// - Parameters:
/// - controller: Which controller are we pushing...?
/// - animationDuration: What's the animation duration - has a default
func pushViewControllerWithFade(_ controller: UIViewController, animationDuration: TimeInterval = .Animation.defaultAnimationTime) {
let transition: CATransition = .init()
transition.duration = animationDuration
transition.type = .fade
pushViewController(controller, withTransition: transition)
}
/// Custom transition animation for pushing view contorller
/// - Parameters:
/// - controller: Which controller are we presenting now?
/// - transition: The transition for the push animation
func pushViewController(_ controller: UIViewController, withTransition transition: CATransition) {
view.layer.add(transition, forKey: nil)
pushViewController(controller, animated: false)
}