When working on this question, I notice that maybe fade of CATransitionType in CATransition is not working or broken. The rest of CATransitionType: moveIn, push, reveal works correctly.
I'm working on iPhone 13, iOS 16.0.
Code of the transition:
extension CALayer {
func makeFadeTransition() {
let transition = CATransition()
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
transition.duration = 0.5
transition.type = .fade
self.add(transition, forKey: nil)
}
}
Usage:
self.customView.layer.makeFadeTransition()
I know that we can switch to using UIView.animate combined with alpha to change opacity instead.
UIView.animate(withDuration: 0.15, animations: {
self.customView.alpha = 0.0
})
or using CABasicAnimation:
extension CALayer {
func makeFadeTransition() {
let transition = CABasicAnimation(keyPath: "opacity")
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
transition.duration = 0.5
transition.fromValue = 1.0
transition.toValue = 0.0
self.add(transition, forKey: nil)
}
}
But again, is CATransitionType type fade broken, or am I wrong at something?
You have to do something to change the layer...
For example, to fade-out / fade-in, we could do this:
extension CALayer {
func makeFadeTransition() {
let transition = CATransition()
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
// let's use duration of 2.5 to make it more obvious
transition.duration = 2.5
transition.type = .fade
// change the layer... opacity, backgroundColor, frame, etc
// fade transition doesn't like opacity of 0.0, so we'll use 0.01
self.opacity = self.opacity == 1.0 ? 0.01 : 1.0
// or, for example,
// "shrink" the layer by 20-points
//self.frame = self.frame.insetBy(dx: 20.0, dy: 20.0)
self.add(transition, forKey: nil)
}
}
I have found a nice curl view animation, and I'd like to add segue after finishing, but segue is calling first (and even if go back to viewcontroller I can see animation ends). Please help me find a mistake or the way to achieve my goal
UIView.animate(withDuration: 1, animations: {
let animation = CATransition()
animation.duration = 1
animation.startProgress = 0.0
animation.endProgress = 1
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
animation.type = CATransitionType(rawValue: "pageCurl")
animation.subtype = CATransitionSubtype(rawValue: "fromRight")
animation.isRemovedOnCompletion = false
animation.isRemovedOnCompletion = false
self.selectedCell!.view1.layer.add(animation, forKey: "pageFlipAnimation")
}, completion: { _ in
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "PageVC") as? PageVC
secondViewController!.modalPresentationStyle = .fullScreen
self.navigationController?.present(secondViewController!, animated: false, completion: nil)
})
What you are doing is trying to animate the addition of animation to the view.
The animations block is taking 1 second to finish. Basically it is trying to animate the addition of animation in a second. On completion of that, it starts navigating.
Rather than doing that, you can use CATransaction to create the required functionality.
CATransaction.begin()
CATransaction.setCompletionBlock {
if let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "PageVC") as? PageVC {
secondViewController.modalPresentationStyle = .fullScreen
self.navigationController?.present(secondViewController, animated: false, completion: nil)
}
}
let animation = CATransition()
animation.duration = 1
animation.startProgress = 0.0
animation.endProgress = 1
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
animation.type = CATransitionType(rawValue: "pageCurl")
animation.subtype = CATransitionSubtype(rawValue: "fromRight")
animation.isRemovedOnCompletion = false
animation.isRemovedOnCompletion = false
self.selectedCell!.view1.layer.add(animation, forKey: "pageFlipAnimation")
CATransaction.commit()
In my application I have to present the screen from Top to bottom and I have tried below code its giving the same normal presenting style.
let screen = self.storyboard?.instantiateViewController(withIdentifier: "Screen1p5") as? Screen1p5
let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromTop
view.window!.layer.add(transition, forKey: kCATransition)
self.present(screen!, animated: true, completion: nil)
For that you need to set subtype of CATransition to kCATransitionFromBottom and for batter animation set animated to false with present(_:animated:completion:).
let screen = self.storyboard?.instantiateViewController(withIdentifier: "Screen1p5") as? Screen1p5
let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromBottom
view.window!.layer.add(transition, forKey: kCATransition)
self.present(screen!, animated: false, completion: nil)
For dismiss set subtype of CATransition to kCATransitionFromTop.
let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromTop
view.window!.layer.add(transition, forKey: kCATransition)
self.dismiss(animated: false)
Just changed it transition.subtype to kCATransitionFromBottom
transition.subtype = kCATransitionFromBottom
For dismiss the controller.
let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromTop
view.window!.layer.add(transition, forKey: kCATransition)
self.dismiss(animated: true, completion: nil)
Please find the below GIF representation.
If you are using the .XIB then please find the below code.
For present the controller.
let newController = NewViewController(nibName: "NewView", bundle: nil)
let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromBottom
view.window!.layer.add(transition, forKey: kCATransition)
self.present(newController, animated: true, completion: nil)
For dismiss the controller. It is the same code as above.
let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromTop
view.window!.layer.add(transition, forKey: kCATransition)
self.dismiss(animated: true, completion: nil)
I am using presentViewController to present new screen
let dashboardWorkout = DashboardWorkoutViewController()
presentViewController(dashboardWorkout, animated: true, completion: nil)
This presents new screen from bottom to top but I want it to presented from right to left without using UINavigationController.
I am using Xib instead of storyboard so how can I do that ?
It doesn't matter if it is xib or storyboard that you are using. Normally, the right to left transition is used when you push a view controller into presentor's UINavigiationController.
UPDATE
Added timing function kCAMediaTimingFunctionEaseInEaseOut
Sample project with Swift 4 implementation added to GitHub
Swift 3 & 4.2
let transition = CATransition()
transition.duration = 0.5
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
present(dashboardWorkout, animated: false, completion: nil)
ObjC
CATransition *transition = [[CATransition alloc] init];
transition.duration = 0.5;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
[transition setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[self.view.window.layer addAnimation:transition forKey:kCATransition];
[self presentViewController:dashboardWorkout animated:false completion:nil];
Swift 2.x
let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromRight
transition.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
view.window!.layer.addAnimation(transition, forKey: kCATransition)
presentViewController(dashboardWorkout, animated: false, completion: nil)
Seems like the animated parameter in the presentViewController method doesn't really matter in this case of custom transition. It can be of any value, either true or false.
Complete code for present/dismiss, Swift 3
extension UIViewController {
func presentDetail(_ viewControllerToPresent: UIViewController) {
let transition = CATransition()
transition.duration = 0.25
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromRight
self.view.window!.layer.add(transition, forKey: kCATransition)
present(viewControllerToPresent, animated: false)
}
func dismissDetail() {
let transition = CATransition()
transition.duration = 0.25
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromLeft
self.view.window!.layer.add(transition, forKey: kCATransition)
dismiss(animated: false)
}
}
Read up all answers and can't see correct solution. The right way do to so is to make custom UIViewControllerAnimatedTransitioning for presented VC delegate.
So it assumes to make more steps, but the result is more customizable and haven't some side effects, like moving from view together with presented view.
So, assume you have some ViewController, and there is a method for presenting
var presentTransition: UIViewControllerAnimatedTransitioning?
var dismissTransition: UIViewControllerAnimatedTransitioning?
func showSettings(animated: Bool) {
let vc = ... create new vc to present
presentTransition = RightToLeftTransition()
dismissTransition = LeftToRightTransition()
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = self
present(vc, animated: true, completion: { [weak self] in
self?.presentTransition = nil
})
}
presentTransition and dismissTransition is used for animating your view controllers.
So you adopt your ViewController to UIViewControllerTransitioningDelegate:
extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return presentTransition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return dismissTransition
}
}
So the last step is to create your custom transition:
class RightToLeftTransition: NSObject, UIViewControllerAnimatedTransitioning {
let duration: TimeInterval = 0.25
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
container.addSubview(toView)
toView.frame.origin = CGPoint(x: toView.frame.width, y: 0)
UIView.animate(withDuration: duration, delay: 0, options: .curveEaseOut, animations: {
toView.frame.origin = CGPoint(x: 0, y: 0)
}, completion: { _ in
transitionContext.completeTransition(true)
})
}
}
class LeftToRightTransition: NSObject, UIViewControllerAnimatedTransitioning {
let duration: TimeInterval = 0.25
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: .from)!
container.addSubview(fromView)
fromView.frame.origin = .zero
UIView.animate(withDuration: duration, delay: 0, options: .curveEaseIn, animations: {
fromView.frame.origin = CGPoint(x: fromView.frame.width, y: 0)
}, completion: { _ in
fromView.removeFromSuperview()
transitionContext.completeTransition(true)
})
}
}
In that code view controller is presented over current context, you can make your customizations from that point. Also you may see custom UIPresentationController is useful as well (pass in using UIViewControllerTransitioningDelegate)
You can also use custom segue.
Swift 5
class SegueFromRight: UIStoryboardSegue {
override func perform() {
let src = self.source
let dst = self.destination
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width, y: 0)
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: UIView.AnimationOptions.curveEaseInOut,
animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
},
completion: { finished in
src.present(dst, animated: false, completion: nil)
})
}
}
Try this,
let animation = CATransition()
animation.duration = 0.5
animation.type = kCATransitionPush
animation.subtype = kCATransitionFromRight
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
vc.view.layer.addAnimation(animation, forKey: "SwitchToView")
self.presentViewController(vc, animated: false, completion: nil)
Here vc is viewcontroller, dashboardWorkout in your case.
import UIKit and create one extension for UIViewController:
extension UIViewController {
func transitionVc(vc: UIViewController, duration: CFTimeInterval, type: CATransitionSubtype) {
let customVcTransition = vc
let transition = CATransition()
transition.duration = duration
transition.type = CATransitionType.push
transition.subtype = type
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
present(customVcTransition, animated: false, completion: nil)
}}
after simlpy call:
let vC = YourViewController()
transitionVc(vc: vC, duration: 0.5, type: .fromRight)
from left to right:
let vC = YourViewController()
transitionVc(vc: vC, duration: 0.5, type: .fromleft)
you can change the duration with your preferred duration...
updated 2022 syntax
If you do want to use the "quick fix" CATransition method....
class AA: UIViewController
func goToBB() {
let bb = .. instantiateViewcontroller, storyboard etc .. as! AlreadyOnboardLogin
let tr = CATransition()
tr.duration = 0.25
tr.type = CATransitionType.moveIn
tr.subtype = CATransitionSubtype.fromRight
view.window!.layer.add(tr, forKey: kCATransition)
present(bb, animated: false)
bb.delegate, etc = set any other needed values
}
and then ...
func dismissingBB() {
let tr = CATransition()
tr.duration = 0.25
tr.type = kCATransitionReveal // use "Reveal" here
tr.subtype = kCATransitionFromLeft
view.window!.layer.add(tr, forKey: kCATransition)
dismiss(self) .. or dismiss(bb), or whatever
}
All of this is unfortunately not really correct :(
CATransition is not really made for doing this job.
Note you will get the annoying cross fade to black which unfortunately ruins the effect.
Many devs (like me) really don't like using NavigationController. Often, it is more flexible to just present in an ad-hoc manner as you go, particularly for unusual and complex apps. However, it's not difficult to "add" a nav controller.
simply on storyboard, go to the entry VC and click "embed -> in nav controller". Really that's it.
Or, if you prefer
in didFinishLaunchingWithOptions it's easy to add your nav controller in code
you really don't even need to keep the variable anywhere, as .navigationController is always available as a property - easy.
Really, once you have a navigationController, it's trivial to do transitions between screens,
let nextScreen = instantiateViewController etc as! NextScreen
navigationController?
.pushViewController(nextScreen, animated: true)
and you can pop.
There's another problem! That however only gives you the standard apple "dual push" effect...
(The old one slides off at a lower speed, as the new one slides on.)
Generally and surprisingly, you usually have to make the effort to do a full custom transition.
Even if you just want the simplest, most common, move-over/move-off transition, you do have to do a full custom transition.
Fortunately, to do that there's some cut and paste boilerplate code on this QA... https://stackoverflow.com/a/48081504/294884 .
Happy new year 2018!
let transition = CATransition()
transition.duration = 0.25
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromLeft
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
tabBarController?.view.layer.add(transition, forKey: kCATransition)
self.navigationController?.popToRootViewController(animated: true)
present view controller from right to left in iOS using Swift
func FrkTransition()
{
let transition = CATransition()
transition.duration = 2
transition.type = kCATransitionPush
transitioningLayer.add(transition,forKey: "transition")
// Transition to "blue" state
transitioningLayer.backgroundColor = UIColor.blue.cgColor
transitioningLayer.string = "Blue"
}
Refrence By :
[https://developer.apple.com/documentation/quartzcore/catransition][1]
Try this.
let transition: CATransition = CATransition()
transition.duration = 0.3
transition.type = kCATransitionReveal
transition.subtype = kCATransitionFromLeft
self.view.window!.layer.addAnimation(transition, forKey: nil)
self.dismissViewControllerAnimated(false, completion: nil)
// Never Easy than this before :)
// you just need to add a Static function for navigation transition
// This Code is recommended for the view controller.
public class CustomNavigation: UIViewController {
public override func loadView() {
super.loadView();
}
public override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true);
}
public static func segueNavToLeft(view: UIView) {
let transition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromLeft
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
}
public static func segueNavToRight(view: UIView) {
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)
}
}
// simply call in your viewcontroller:
func moveToRight() {
CustomNavigation.segueNavToRight(view: view)
let controller = self.storyboard?.instantiateViewController(withIdentifier: "id") as! YourViewController
let navigationController = UINavigationController(rootViewController: YourViewController)
navigationController.modalPresentationStyle = .fullScreen
self.present(navigationController, animated: false, completion: nil)
}
func moveToLeft() {
CustomNavigation.segueNavToLeft(view: view)
self.dismiss(animated: true, completion: nil)
}
gray background when push
class RightToLeftPresentationController: UIPresentationController {
lazy var blackView: UIView = {
let view = UIView()
view.frame = self.containerView?.bounds ?? .zero
view.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.5)
return view
}()
// MARK:- Presentation
override func presentationTransitionWillBegin() {
self.containerView?.addSubview(blackView)
self.presentingViewController.transitionCoordinator?
.animate(alongsideTransition: { _ in
self.blackView.alpha = 0.5
}, completion: nil)
}
// MARK:- Dismiss
override func dismissalTransitionWillBegin() {
UIView.animate(withDuration: 0.25) {
self.blackView.alpha = 0
}
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
if completed {
blackView.removeFromSuperview()
}
}
}
support storyboard
class RightToLeftPresentationSegue: UIStoryboardSegue {
override func perform() {
destination.modalPresentationStyle = .custom
destination.transitioningDelegate = self
source.present(destination, animated: true)
}
}
extension RightToLeftPresentationSegue: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let controller = RightToLeftPresentationController(presentedViewController: presented, presenting: presenting)
presented.transitioningDelegate = controller
return controller
}
}
support dismiss(thanks for #hotjard)
extension RightToLeftPresentationController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return RightToLeftTransition()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return LeftToRightTransition()
}
}
class RightToLeftTransition: NSObject, UIViewControllerAnimatedTransitioning {
let duration: TimeInterval = 0.25
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
container.addSubview(toView)
toView.frame.origin = CGPoint(x: toView.frame.width, y: 0)
UIView.animate(withDuration: duration, delay: 0, options: .curveEaseOut, animations: {
toView.frame.origin = CGPoint(x: 0, y: 0)
}, completion: { _ in
transitionContext.completeTransition(true)
})
}
}
class LeftToRightTransition: NSObject, UIViewControllerAnimatedTransitioning {
let duration: TimeInterval = 0.25
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: .from)!
container.addSubview(fromView)
fromView.frame.origin = .zero
UIView.animate(withDuration: duration, delay: 0, options: .curveEaseIn, animations: {
fromView.frame.origin = CGPoint(x: fromView.frame.width, y: 0)
}, completion: { _ in
fromView.removeFromSuperview()
transitionContext.completeTransition(true)
})
}
}
I have a better solution that has worked for me if you want to mantain the classic animation of Apple, without see that "black" transition that you see using CATransition(). Simply use popToViewController method.
You can look for the viewController you need in
self.navigationController.viewControllers // Array of VC that contains all VC of current stack
You can look for the viewController you need by searching for it's restorationIdentifier.
BE CAREFUL: for example, if you are navigating through 3 view controllers, and when arrive to the last you want to pop the first. You will lose, in this case, the refer to the effective SECOND view controller because the popToViewController has overwritten it. BTW, there's a solution: you can easily save before the popToViewcontroller, the VC you will need later. It worked for me very well.