How to solve this problem with custom transition in iOS? - ios

I built a class to implement a circular transition between view controllers. When I hit the button to navigate to the other view controller a circle starts growing from the button until it fills the screen with the new controller. When I dismiss the view controller I expected this circle to shrink down back to the original position. It's also working. The only problem is that when the dismiss is underway the back of the screen while the circle is shrinking is completely black and after the animation is completed the new viewController appears abruptly.
Here are some photos of the effect:
Here's the code of the custom class:
class customTransition: NSObject, UIViewControllerAnimatedTransitioning{
var duration: TimeInterval = 0.5
var startPoint = CGPoint.zero
var circle = UIView()
var circleColor = UIColor.white
enum transitMode: Int {
case presenting, dismissing
}
var transitionMode: transitMode = .presenting
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
guard let to = transitionContext.view(forKey: UITransitionContextViewKey.to) else {return}
guard let from = transitionContext.view(forKey: UITransitionContextViewKey.from) else {return}
circleColor = to.backgroundColor ?? UIColor.white
if transitionMode == .presenting {
to.translatesAutoresizingMaskIntoConstraints = false
to.center = startPoint
circle = UIView()
circle.backgroundColor = circleColor
circle.frame = getFrameForCircle(rect: to.frame)
circle.layer.cornerRadius = circle.frame.width / 2
circle.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
circle.alpha = 0
circle.addSubview(to)
to.centerXAnchor.constraint(equalTo: circle.centerXAnchor).isActive = true
to.centerYAnchor.constraint(equalTo: circle.centerYAnchor).isActive = true
to.widthAnchor.constraint(equalToConstant: to.frame.width).isActive = true
to.heightAnchor.constraint(equalToConstant: to.frame.height).isActive = true
container.addSubview(circle)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = from.center
self.circle.transform = CGAffineTransform.identity
self.circle.alpha = 1
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
} else if transitionMode == .dismissing {
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = self.startPoint
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
self.circle.alpha = 0
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
}
}
func getFrameForCircle(rect: CGRect) -> CGRect{
let width = Float(rect.width)
let height = Float(rect.height)
let diameter = CGFloat(sqrtf(width * width + height * height))
let x: CGFloat = rect.midX - (diameter / 2)
let y: CGFloat = rect.midY - (diameter / 2)
return CGRect(x: x, y: y, width: diameter, height: diameter)
}
}
and the implementation...
let circularTransition = customTransition()
the call for the present view controller... I tried to set secondVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext but when I set this line it ignores completely the animation transition I don't know why...
`
#objc func handlePresent(sender: UIButton){
let secondVC = nextVC()
secondVC.transitioningDelegate = self
present(secondVC, animated: true, completion: nil)
}
delegate methods:
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.startPoint = presentButton.center
circularTransition.transitionMode = .presenting
return circularTransition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.transitionMode = .dismissing
circularTransition.startPoint = presentButton.center
return circularTransition
}
What am I missing here? Any suggestions?
No storyboard being used, just code.

If you don't use navigationController, it's necessary to use the .custom mode in the presentedviewController.
import UIKit
class TransViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
let circularTransition = customTransition()
#IBOutlet var presentButton : UIButton!
#IBAction func handlePresent(sender: UIButton){
if let secondVC = storyboard?.instantiateViewController(withIdentifier: "next"){
secondVC.modalPresentationStyle = .custom
secondVC.transitioningDelegate = self
present(secondVC, animated: true, completion: nil)
}
}
}
class BackViewController: UIViewController {
#IBAction func dismissMe(sender: UIButton){
self.dismiss(animated: true, completion: nil)
}
}
extension TransViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.startPoint = presentButton.center
circularTransition.transitionMode = .presenting
return circularTransition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.transitionMode = .dismissing
circularTransition.startPoint = presentButton.center
return circularTransition
}
}
If there is no from or to view, we have use the from and to view from containView.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
var to : UIView!
var from : UIView!
to = transitionContext.view(forKey: UITransitionContextViewKey.to)
if to == nil {to = container}
from = transitionContext.view(forKey: UITransitionContextViewKey.from)
if from == nil {from = container}
The rest is same:
circleColor = to.backgroundColor ?? UIColor.white
if transitionMode == .presenting {
to.translatesAutoresizingMaskIntoConstraints = false
to.center = startPoint
circle = UIView()
circle.backgroundColor = circleColor
circle.frame = getFrameForCircle(rect: to.frame)
circle.layer.cornerRadius = circle.frame.width / 2
circle.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
circle.alpha = 0
circle.addSubview(to)
to.centerXAnchor.constraint(equalTo: circle.centerXAnchor).isActive = true
to.centerYAnchor.constraint(equalTo: circle.centerYAnchor).isActive = true
to.widthAnchor.constraint(equalToConstant: to.frame.width).isActive = true
to.heightAnchor.constraint(equalToConstant: to.frame.height).isActive = true
container.addSubview(circle)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = from.center
self.circle.transform = CGAffineTransform.identity
self.circle.alpha = 1
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
} else if transitionMode == .dismissing {
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = self.startPoint
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
self.circle.alpha = 0
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
}
}

Related

CGAffineTransform.identity doesn't reset transform correctly after device rotation

I am doing a custom transition and if after present animation, device will be rotated and then destinationVC will be dismissed, originVC transform is not correct (not fulfil screen). If there is no device rotation, everything works perfectly fine. Does any one can help me?
Here is my code for present and dismiss animation:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let originViewController = transitionContext.viewController(forKey: .from),
let destinationViewController = transitionContext.viewController(forKey: .to) else { return }
destinationViewController.view.transform = CGAffineTransform(translationX: 0, y: destinationViewController.view.frame.height)
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
destinationViewController.view.transform = CGAffineTransform(translationX: 0, y: 0)
originViewController.view.transform = originViewController.view.transform.scaledBy(x: 0.95, y: 0.95)
originViewController.view.layer.cornerRadius = 8.0
}, completion: { completed in
transitionContext.completeTransition(completed)
})
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let originViewController = transitionContext.viewController(forKey: .from),
let destinationViewController = transitionContext.viewController(forKey: .to) else { return }
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
originViewController.view.transform = CGAffineTransform(translationX: 0, y: destinationViewController.view.frame.height)
destinationViewController.view.transform = CGAffineTransform.identity
destinationViewController.view.layer.cornerRadius = 0.0
}, completion: { completed in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
Screens:
Before present animation
After present animation
After device rotation
After dismiss animation
EDIT:
when I add destinationViewController.view.frame = transitionContext.finalFrame(for: destinationViewController) to dismiss animation everything seems works fine but I don't know if this is right way
Add a subView in ViewC1 with leading, top, bottom, trailing constraints.
Working full code
class ViewC1: UIViewController {
#IBAction func presentNow(_ sender: Any) {
let viewc = storyboard!.instantiateViewController(withIdentifier: "ViewC2") as! ViewC2
viewc.transitioningDelegate = viewc
viewc.modalPresentationStyle = .overCurrentContext
present(viewc, animated: true, completion: nil)
}
}
class ViewC2: UIViewController {
let pres = PresentAnimator()
let diss = DismissAnimator()
#IBAction func dissmisNow(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
extension ViewC2: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return pres
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return diss
}
}
class PresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1.0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let originViewController = transitionContext.viewController(forKey: .from),
let destinationViewController = transitionContext.viewController(forKey: .to) else { return }
transitionContext.containerView.addSubview(destinationViewController.view)
destinationViewController.view.frame = transitionContext.containerView.bounds
let originView = originViewController.view.subviews.first
destinationViewController.view.transform = CGAffineTransform(translationX: 0, y: destinationViewController.view.frame.height)
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
destinationViewController.view.transform = CGAffineTransform(translationX: 0, y: 0)
originView?.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
originView?.layer.cornerRadius = 8.0
}, completion: { completed in
transitionContext.completeTransition(completed)
})
}
}
class DismissAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1.0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let originViewController = transitionContext.viewController(forKey: .from),
let destinationViewController = transitionContext.viewController(forKey: .to) else { return }
let originView = destinationViewController.view.subviews.first
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
originViewController.view.transform = CGAffineTransform(translationX: 0, y: destinationViewController.view.frame.height)
originView?.transform = CGAffineTransform.identity
originView?.layer.cornerRadius = 0.0
}, completion: { completed in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
Update:
or you can override willRotate and didRotate if you dont want to use view.subviews.first
var prevTrans: CGAffineTransform?
override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
prevTrans = view.transform
view.transform = CGAffineTransform.identity
}
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
if let prevTrans = prevTrans {
view.transform = prevTrans
}
}

Screen flashes black when using custom animation controller to dismiss

I have written a custom animation controller to animate my view controller transition. Code as follows :
class PopupAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
var backgroundColorView: UIView!
let duration: TimeInterval = 0.35
var dismiss = false
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if !dismiss {
animatePresentTransition(using: transitionContext)
} else {
animateDismissTransition(using: transitionContext)
}
}
func animatePresentTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else {
return
}
let containerView = transitionContext.containerView
let finalFrameForVC = transitionContext.finalFrame(for: toVC) // What is this about?
let bounds = UIScreen.main.bounds
let snapshotView = fromVC.view.snapshotView(afterScreenUpdates: false)
snapshotView?.frame = bounds
backgroundColorView = UIView(frame: bounds)
backgroundColorView.backgroundColor = UIColor.black.withAlphaComponent(0)
// Put the window at the bottom of the screen
toVC.view.frame = finalFrameForVC.offsetBy(dx: 0, dy: bounds.size.height)
// Layer out the views
containerView.addSubview(snapshotView!)
containerView.addSubview(backgroundColorView)
containerView.addSubview(toVC.view)
// The animation happens here
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
self.backgroundColorView.backgroundColor = UIColor.black.withAlphaComponent(0.4)
toVC.view.frame = finalFrameForVC
}, completion: {
finished in
transitionContext.completeTransition(true)
})
}
func animateDismissTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else {
return
}
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
self.backgroundColorView.backgroundColor = UIColor.black.withAlphaComponent(0.0)
let bounds = UIScreen.main.bounds
fromVC.view.frame = fromVC.view.frame.offsetBy(dx: 0, dy: bounds.size.height)
}, completion: {
finished in
transitionContext.completeTransition(true)
})
}
}
I have implemented the UIViewControllerTransitioningDelegate methods in the controller that will use this transition like so :
extension PopupViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transitionAnimator.dismiss = false
return transitionAnimator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transitionAnimator.dismiss = true
return transitionAnimator
}
}
When I'm not using a spring in the animation (eg damping set to 1) it works perfectly. However if I include spring in the animation like in this example the screen flashes black when the controller is dismissed:
Not sure why this is happening. Any ideas on what I might be doing wrong where? Any pointers would be greatly appreciated!

Issue with Custom Transition in Swift3

I've been using this code to go a a different view controller.
let details = storyboard?.instantiateViewController(withIdentifier: "ViewMainMurmur")
details?.modalTransitionStyle = .coverVertical
present(details!, animated: true, completion: nil)
And I have created a slideAnimator class to customize my transition
class SlideAnimator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
let duration = 0.5
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {
return
}
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {
return
}
let container = transitionContext.containerView
let screenOffUp = CGAffineTransform(translationX: 0, y: -container.frame.height)
let screenOffDown = CGAffineTransform(translationX: 0, y: container.frame.height)
container.addSubview(fromView)
container.addSubview(toView)
toView.transform = screenOffUp
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: {
fromView.transform = screenOffDown
fromView.alpha = 0.5
toView.transform = CGAffineTransform.identity
toView.alpha = 1
}) { (success) in
transitionContext.completeTransition(success )
}
}
}
The problem now is that I only know how to use the slideAnimator class when I have a drawn a segue on the storyboard.
How can i use the slideAnimator when i use present() ?
Before calling present, set the presented view controller's transitioningDelegate to your SlideAnimator.

UIViewControllerAnimatedTransitioning: Black screen fragments after rotation change

I have created a Viewcontrollertransition and everything is working as long as I don't change the device orientation.
Image 1 shows the screen as it should be. I then switch to the next viewcontroller where I change the orientation. Now I go back to first viewcontroller and again switch the orientation. Then I get the result visible in image 2. A black border appears. Please don't mind the white box in the center of the screen.
Below you find the code of my animation. Can you see what is wrong?
import Foundation
import UIKit
class ZoomOutCircleViewTransition: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
var hideDelayed = false
var transitionContext: UIViewControllerContextTransitioning?
init(hideDelayed : Bool = true) {
self.hideDelayed = hideDelayed
super.init()
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.6
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
guard let toViewController: UIViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) else {
return
}
guard let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) else {
return
}
guard let toViewTransitionFromView = toViewController as? TransitionFromViewProtocol else {
return
}
let containerView = transitionContext.containerView()
let imageViewSnapshot = toViewTransitionFromView.getViewForTransition()
imageViewSnapshot.alpha = 0.0
let imageViewSnapshotOriginalFrame = imageViewSnapshot.frame
let startFrame = CGRectMake(-CGRectGetWidth(toViewController.view.frame)/2, -CGRectGetHeight(toViewController.view.frame)/2, CGRectGetWidth(toViewController.view.frame)*2, CGRectGetHeight(toViewController.view.frame)*2)
let quadraticStartFrame = CGRect(x: startFrame.origin.x - (startFrame.height - startFrame.width)/2, y: startFrame.origin.y, width: startFrame.height, height: startFrame.height)
containerView!.insertSubview(toViewController.view, atIndex: 0)
containerView!.addSubview(imageViewSnapshot)
// UIViewController circle shrink animation
let bigCirclePath = UIBezierPath(ovalInRect: quadraticStartFrame)
let smallCirclePath = UIBezierPath(ovalInRect: imageViewSnapshot.frame)
let maskLayer = CAShapeLayer()
maskLayer.frame = toViewController.view.frame
maskLayer.path = bigCirclePath.CGPath//maskPath.CGPath
fromViewController.view.layer.mask = maskLayer
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.delegate = self
pathAnimation.fromValue = bigCirclePath.CGPath
pathAnimation.toValue = smallCirclePath.CGPath
pathAnimation.duration = transitionDuration(transitionContext)
maskLayer.path = smallCirclePath.CGPath
maskLayer.addAnimation(pathAnimation, forKey: "pathAnimation")
// pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
imageViewSnapshot.frame = quadraticStartFrame
// Make imageView visible with animation
let showImageViewAnimation = {
imageViewSnapshot.alpha = 1.0
}
let showImageViewDelay = 0.3
UIView.animateWithDuration(transitionDuration(transitionContext) - showImageViewDelay , delay: showImageViewDelay, options: UIViewAnimationOptions.CurveLinear, animations: showImageViewAnimation) { (completed) -> Void in
}
// Shrink the imageView to the original size
let scaleImageViewAnimation = {
imageViewSnapshot.frame = imageViewSnapshotOriginalFrame
}
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: scaleImageViewAnimation) { (completed) -> Void in
// After the complete animations have endet
// Hide ImageView after it is completely resized. Added some animation delay to not abruptly change the contactImage
if self.hideDelayed {
let hideImageViewAnimation = {
imageViewSnapshot.alpha = 0.0
}
UIView.animateWithDuration(0.2 , delay: 0.2, options: UIViewAnimationOptions.CurveLinear, animations: hideImageViewAnimation) { (completed) -> Void in
imageViewSnapshot.removeFromSuperview()
}
}
else {
imageViewSnapshot.removeFromSuperview()
}
}
}
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
if let transitionContext = self.transitionContext {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}
// MARK: UIViewControllerTransitioningDelegate protocol methods
// return the animataor when presenting a viewcontroller
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
// return the animator used when dismissing from a viewcontroller
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
}
You need set new frame to toViewController.view. This will update the view to current orientation.
toViewController.view.frame = fromViewController.view.frame
Use my updated code :
class ZoomOutCircleViewTransition: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
var hideDelayed = false
var transitionContext: UIViewControllerContextTransitioning?
init(hideDelayed : Bool = true) {
self.hideDelayed = hideDelayed
super.init()
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.6
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
guard let toViewController: UIViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) else {
return
}
guard let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) else {
return
}
guard let toViewTransitionFromView = toViewController as? TransitionFromViewProtocol else {
return
}
toViewController.view.frame = fromViewController.view.frame
let containerView = transitionContext.containerView()
let imageViewSnapshot = toViewTransitionFromView.getViewForTransition()
imageViewSnapshot.alpha = 0.0
let imageViewSnapshotOriginalFrame = imageViewSnapshot.frame
let startFrame = CGRectMake(-CGRectGetWidth(fromViewController.view.frame)/2, -CGRectGetHeight(fromViewController.view.frame)/2, CGRectGetWidth(toViewController.view.frame)*2, CGRectGetHeight(toViewController.view.frame)*2)
let quadraticStartFrame = CGRect(x: startFrame.origin.x - (startFrame.height - startFrame.width)/2, y: startFrame.origin.y, width: startFrame.height, height: startFrame.height)
containerView!.insertSubview(toViewController.view, atIndex: 0)
containerView!.addSubview(imageViewSnapshot)
// UIViewController circle shrink animation
let bigCirclePath = UIBezierPath(ovalInRect: quadraticStartFrame)
let smallCirclePath = UIBezierPath(ovalInRect: imageViewSnapshot.frame)
let maskLayer = CAShapeLayer()
maskLayer.frame = toViewController.view.frame
maskLayer.path = bigCirclePath.CGPath//maskPath.CGPath
fromViewController.view.layer.mask = maskLayer
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.delegate = self
pathAnimation.fromValue = bigCirclePath.CGPath
pathAnimation.toValue = smallCirclePath.CGPath
pathAnimation.duration = transitionDuration(transitionContext)
maskLayer.path = smallCirclePath.CGPath
maskLayer.addAnimation(pathAnimation, forKey: "pathAnimation")
// pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
imageViewSnapshot.frame = quadraticStartFrame
// Make imageView visible with animation
let showImageViewAnimation = {
imageViewSnapshot.alpha = 1.0
}
let showImageViewDelay = 0.3
UIView.animateWithDuration(transitionDuration(transitionContext) - showImageViewDelay , delay: showImageViewDelay, options: UIViewAnimationOptions.CurveLinear, animations: showImageViewAnimation) { (completed) -> Void in
}
// Shrink the imageView to the original size
let scaleImageViewAnimation = {
imageViewSnapshot.frame = imageViewSnapshotOriginalFrame
}
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: scaleImageViewAnimation) { (completed) -> Void in
// After the complete animations have endet
// Hide ImageView after it is completely resized. Added some animation delay to not abruptly change the contactImage
if self.hideDelayed {
let hideImageViewAnimation = {
imageViewSnapshot.alpha = 0.0
}
UIView.animateWithDuration(0.2 , delay: 0.2, options: UIViewAnimationOptions.CurveLinear, animations: hideImageViewAnimation) { (completed) -> Void in
imageViewSnapshot.removeFromSuperview()
}
}
else {
imageViewSnapshot.removeFromSuperview()
}
toViewController.view.layer.mask = nil
}
}
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
if let transitionContext = self.transitionContext {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}
// MARK: UIViewControllerTransitioningDelegate protocol methods
// return the animataor when presenting a viewcontroller
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
// return the animator used when dismissing from a viewcontroller
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
}
class ZoomOutCircleViewTransition: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
var hideDelayed = false
var transitionContext: UIViewControllerContextTransitioning?
init(hideDelayed : Bool = true) {
self.hideDelayed = hideDelayed
super.init()
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.6
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
guard let toViewController: UIViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) else {
return
}
guard let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) else {
return
}
guard let toViewTransitionFromView = toViewController as? TransitionFromViewProtocol else {
return
}
toViewController.view.frame = fromViewController.view.frame
let containerView = transitionContext.containerView()
let imageViewSnapshot = toViewTransitionFromView.getViewForTransition()
imageViewSnapshot.alpha = 0.0
let imageViewSnapshotOriginalFrame = imageViewSnapshot.frame
let startFrame = CGRectMake(-CGRectGetWidth(fromViewController.view.frame)/2, -CGRectGetHeight(fromViewController.view.frame)/2, CGRectGetWidth(toViewController.view.frame)*2, CGRectGetHeight(toViewController.view.frame)*2)
let quadraticStartFrame = CGRect(x: startFrame.origin.x - (startFrame.height - startFrame.width)/2, y: startFrame.origin.y, width: startFrame.height, height: startFrame.height)
containerView!.insertSubview(toViewController.view, atIndex: 0)
containerView!.addSubview(imageViewSnapshot)
// UIViewController circle shrink animation
let bigCirclePath = UIBezierPath(ovalInRect: quadraticStartFrame)
let smallCirclePath = UIBezierPath(ovalInRect: imageViewSnapshot.frame)
let maskLayer = CAShapeLayer()
maskLayer.frame = toViewController.view.frame
maskLayer.path = bigCirclePath.CGPath//maskPath.CGPath
fromViewController.view.layer.mask = maskLayer
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.delegate = self
pathAnimation.fromValue = bigCirclePath.CGPath
pathAnimation.toValue = smallCirclePath.CGPath
pathAnimation.duration = transitionDuration(transitionContext)
maskLayer.path = smallCirclePath.CGPath
maskLayer.addAnimation(pathAnimation, forKey: "pathAnimation")
// pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
imageViewSnapshot.frame = quadraticStartFrame
// Make imageView visible with animation
let showImageViewAnimation = {
imageViewSnapshot.alpha = 1.0
}
let showImageViewDelay = 0.3
UIView.animateWithDuration(transitionDuration(transitionContext) - showImageViewDelay , delay: showImageViewDelay, options: UIViewAnimationOptions.CurveLinear, animations: showImageViewAnimation) { (completed) -> Void in
}
// Shrink the imageView to the original size
let scaleImageViewAnimation = {
imageViewSnapshot.frame = imageViewSnapshotOriginalFrame
}
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: scaleImageViewAnimation) { (completed) -> Void in
// After the complete animations have endet
// Hide ImageView after it is completely resized. Added some animation delay to not abruptly change the contactImage
if self.hideDelayed {
let hideImageViewAnimation = {
imageViewSnapshot.alpha = 0.0
}
UIView.animateWithDuration(0.2 , delay: 0.2, options: UIViewAnimationOptions.CurveLinear, animations: hideImageViewAnimation) { (completed) -> Void in
imageViewSnapshot.removeFromSuperview()
}
}
else {
imageViewSnapshot.removeFromSuperview()
}
toViewController.view.layer.mask = nil
}
}
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
if let transitionContext = self.transitionContext {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}
// MARK: UIViewControllerTransitioningDelegate protocol methods
// return the animataor when presenting a viewcontroller
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
// return the animator used when dismissing from a viewcontroller
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
}
I had the same problem and tried solution proposed by #Arun but it did not work. For me worked this:
guard let toViewController: UIViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) else { return }
let toFrame = transitionContext.finalFrameForViewController(toViewController)
and in completion block of animation:
}) { finished in
toViewController.view.frame = toFrame
transitionContext.completeTransition(finished)
}
Hope that helps!

UIViewController Animation ios?

Trying to create something like below in Objective-C, this is available in Swift.
I tried using bridging etc, to use this code with my Objective C project but it's having import issues.
In Objective-C I tried below things and partially it works for UIView, but I guess I need to add transitions to complete UIViewController.
What I tried
- (IBAction)menuButtonPressed:(id)sender
{
if ([self.delegate respondsToSelector:#selector(menuButtonPressed)]) {
[self.delegate menuButtonPressed];
}
if (!isVertical) {
[UIView animateWithDuration: 0.6
delay: 0.0
usingSpringWithDamping: 0.3
initialSpringVelocity: .8
options: 0
animations: ^
{
self.transform = CGAffineTransformTranslate(self.transform, -240, 0);
self.transform = CGAffineTransformRotate(self.transform, 90 * M_PI / 180);
self.transform = CGAffineTransformTranslate(self.transform, 240, 0);
}
completion:^(BOOL finished) {
isVertical = YES;
}
];
}else{
[UIView animateWithDuration: 0.45
delay: 0.0
usingSpringWithDamping: 0.44
initialSpringVelocity: .8
options: 0
animations: ^
{
self.transform = CGAffineTransformTranslate(self.transform, -240, 0);
self.transform = CGAffineTransformRotate(self.transform, -90 * M_PI / 180);
self.transform = CGAffineTransformTranslate(self.transform, 240, 0);
}
completion:^(BOOL finished) {
isVertical = NO;
}
];
}
}
Any inputs will be helpful.
Here is a very simple implementation that you can use to animate transitions between view controllers. You could basically use the same animation gist if you want to use it with UIViews.
let generateRandomColor: Void -> UIColor = {
let red = CGFloat(arc4random_uniform(255)) / 255.0
let green = CGFloat(arc4random_uniform(255)) / 255.0
let blue = CGFloat(arc4random_uniform(255)) / 255.0
return UIColor(red:red, green: green, blue: blue, alpha: 1)
}
class SlideAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
let presenting: Bool
init(presenting: Bool) {
self.presenting = presenting
super.init()
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView()!
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
var animatingView: UIView
var initialTransform: CGAffineTransform
var finalTransform: CGAffineTransform
if presenting {
containerView.addSubview(toView)
animatingView = toView
initialTransform = CGAffineTransformMakeRotation(CGFloat(-M_PI_2))
finalTransform = CGAffineTransformIdentity
} else {
containerView.insertSubview(toView, belowSubview: fromView)
animatingView = fromView
initialTransform = CGAffineTransformIdentity
finalTransform = CGAffineTransformMakeRotation(CGFloat(-M_PI_2))
}
animatingView.layer.anchorPoint = CGPointMake(0, 0)
animatingView.frame = containerView.bounds
animatingView.transform = initialTransform
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 16, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
animatingView.transform = finalTransform
}) {
_ in
toView.layer.anchorPoint = CGPointMake(0.5, 0.5)
toView.frame = containerView.bounds
transitionContext.completeTransition(true)
}
}
}
class PresentedViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = generateRandomColor()
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: "tapped")
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapped() {
dismissViewControllerAnimated(true, completion: nil)
}
}
class TestViewController: UIViewController, UIViewControllerTransitioningDelegate {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = generateRandomColor()
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: "tapped")
definesPresentationContext = true
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapped() {
let presentedViewController = PresentedViewController()
presentedViewController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
presentedViewController.transitioningDelegate = self
presentViewController(presentedViewController, animated: true, completion: nil)
}
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SlideAnimationController(presenting: true)
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SlideAnimationController(presenting: false)
}
}

Resources