Custom horizontal pop transition not working as it should - ios

I'm trying to create a custom horizontal slide UINavigationController transition. And the push animation seems to work fine. But when I'm trying to pop (and have that horizontal slide back) where's only a blank screen and after animation time the view appears where it should
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)
let offScreenLeft = CGAffineTransform(translationX: -container.frame.width, y: 0)
fromView.frame = transitionContext.initialFrame(for: transitionContext.viewController(forKey: .from)!)
toView.frame = transitionContext.finalFrame(for: transitionContext.viewController(forKey: .to)!)
toView.transform = isPresenting == true ? offScreenRight : offScreenLeft
container.addSubview(toView)
container.addSubview(fromView)
let duration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: [], animations: {
fromView.transform = self.isPresenting == true ? offScreenLeft : offScreenRight
toView.transform = .identity
}, completion: { finished in
toView.frame = transitionContext.initialFrame(for: transitionContext.viewController(forKey: .from)!)
transitionContext.completeTransition(true)
})
}

Looks like you need to reset any existing transforms on your views.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
// add these two lines
fromView.transform = .identity
toView.transform = .identity
// ... the rest of your existing code

Related

Custom VC Transition not Dismissing Properly

I am trying to recreate the stock UIAlertAction in a VC with a custom transition. I currently have the presenting working perfectly(backgroundView fades in and the 'notification' VC slides from the bottom). The problem I am facing is when I dismiss the VC the backgroundView doesn't fade out. It's as if it's completely bypassing my animation block. Once completeTransition is called the backgroundView disappears completely. What is happening?
class AnimationController: NSObject {
let backgroundView = UIView()
}
extension AnimationController: UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toViewController = transitionContext.viewController(forKey: .to),
let fromViewController = transitionContext.viewController(forKey: .from) else {
transitionContext.completeTransition(false)
return
}
switch animationType {
case .present:
backgroundView.frame = CGRect(x: 0, y: 0, width: toViewController.view.frame.width, height: fromViewController.view.frame.height)
backgroundView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4)
backgroundView.alpha = 0
containerView.addSubview(backgroundView)
let viewToAnimate = toViewController.view!
containerView.addSubview(viewToAnimate)
toViewController.view.clipsToBounds = true
viewToAnimate.frame = CGRect(x: 0, y: viewToAnimate.frame.maxY, width: viewToAnimate.frame.width, height: viewToAnimate.frame.height)
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.80, initialSpringVelocity: 0.1, options: .curveEaseInOut, animations: {
self.backgroundView.alpha = 1.0
viewToAnimate.transform = CGAffineTransform(translationX: 0, y: -viewToAnimate.frame.height)
}) { _ in
transitionContext.completeTransition(true)
}
case .dismiss:
containerView.addSubview(fromViewController.view!)
let testView = fromViewController.view!
backgroundView.frame = CGRect(x: 0, y: 0, width: testView.frame.width, height: testView.frame.height)
backgroundView.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.80, initialSpringVelocity: 0.1, options: .curveEaseInOut, animations: {
self.backgroundView.alpha = 0.0
testView.transform = CGAffineTransform(translationX: 0, y: testView.frame.height)
print("backgroundView Doesn't fade out")
}) { _ in
print("backgroundView disappears here")
transitionContext.completeTransition(true)
}
}
}
Your problem is that you're dealing with two separate instances of your AnimationController and hence two separate instances of backgroundView. One instance is instantiated for presentation and the other is instantiated for dismissal. Notice in your case .present: block you are calling containerView.addSubview(backgroundView) but you don't do the same in the case .dismiss: block. As a result, the backgroundView instance in the case .dismiss: block isn't even part of the view hierarchy.
What you need to do is call self.backgroundView.removeFromSuperview() in your first animation completion block. Then in your case .dismiss: you need to call containerView.addSubview(backgroundView) once again.
Try this...
switch animationType {
case .present:
// ...
containerView.addSubview(backgroundView)
// ...
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.80, initialSpringVelocity: 0.1, options: .curveEaseInOut, animations: {
self.backgroundView.alpha = 1.0
viewToAnimate.transform = CGAffineTransform(translationX: 0, y: -viewToAnimate.frame.height)
}) { _ in
self.backgroundView.removeFromSuperview()
transitionContext.completeTransition(true)
}
case .dismiss:
// ...
containerView.addSubview(backgroundView)
containerView.addSubview(fromViewController.view!)
// ...
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.80, initialSpringVelocity: 0.1, options: .curveEaseInOut, animations: {
self.backgroundView.alpha = 0.0
testView.transform = CGAffineTransform(translationX: 0, y: testView.frame.height)
print("backgroundView Doesn't fade out")
}) { _ in
print("backgroundView disappears here")
transitionContext.completeTransition(true)
}
}
you could try to present like this
//where DevolucionVC is you ViewController
if let controller = DevolucionVC() as? DevolucionVC {
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(controller, animated: true, completion: nil)
}
}
you can dismiss it later by simply use
dismiss(animated: true, completion: nil)
Try This
for present replace this code
backgroundView.frame = CGRect(x: 0, y: 0, width: toViewController.view.frame.width, height: fromViewController.view.frame.height)
backgroundView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4)
backgroundView.alpha = 0
containerView.addSubview(backgroundView)
let transition = CATransition()
transition.duration = 0.5
transition.type = CATransitionType.moveIn
transition.subtype = CATransitionSubtype.fromTop
containerView.layer.add(transition, forKey: kCATransition)
For Dismiss Replace this code
containerView.addSubview(fromViewController.view!)
let transition = CATransition()
transition.duration = 0.5
transition.type = CATransitionType.fade
containerView.layer.add(transition, forKey: kCATransition)
backgroundView.frame = CGRect(x: 0, y: 0, width: testView.frame.width, height: testView.frame.height)
backgroundView.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
self.backgroundView.alpha = 0.0

Run two animations simultaneously

I'm trying to create an animation with two views and I've encountered some unexpected behaviors while performing them.
I want to animate both views position while doing a second animation which is transitionFlipFromBottom
Here's the code:
let initialFrame = CGRect(x: xpos, y: -310, width: 300, height: 300)
let firstView = UIView()
firstView.backgroundColor = .red
firstView.frame = initialFrame
let secondView = UIView()
secondView.backgroundColor = .white
secondView.frame = initialFrame
secondView.isHidden = false
self.view.addSubview(firstView)
self.view.addSubview(secondView)
// Here I try to move the views on screen while fliping them
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
secondView.center = self.view.center
firstView.center = self.view.center
self.flip(firstView: firstView, secondView: secondView)
}, completion: nil)
// This function flips the views vertically while it animates the transition from the first to the second view
fileprivate func flip(firstView: UIView, secondView: UIView) {
let transitionOptions: UIViewAnimationOptions = [.transitionFlipFromBottom, .showHideTransitionViews]
UIView.transition(with: firstView, duration: 0.5, options: transitionOptions, animations: {
firstView.isHidden = true
})
UIView.transition(with: secondView, duration: 0.5, options: transitionOptions, animations: {
secondView.isHidden = false
})
}
The code above fails to execute both animations at the same time.
It only works if I place the flip function call inside the completion block, after the first animation (moving frame) finishes, as the following:
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
secondView.center = self.view.center
firstView.center = self.view.center
}, completion: {(_) in
self.flip(firstView: dummyView, secondView: newGroupView)
})
I have even tried to use UIView.animateKeyframes but it still doesn't work.
Am I missing something here?
Thank you.
A couple of things:
In transition, specify .allowAnimatedContent option.
Defer the animation:
DispatchQueue.main.async {
UIView.animate(withDuration: 1, delay: 0, options: [.curveEaseOut], animations: {
secondView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
firstView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
self.flip(firstView: firstView, secondView: secondView)
}, completion: { _ in
})
}
Somewhat unrelated, you don't want:
secondView.center = self.view.center
Instead, do:
secondView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
You want to set secondView.center in the coordinate space of the bounds of view, not in view's superview.

Custom Segue, Multiple Animations Not Syncing

There are a lot of excellent answers on this topic, but this one has me confused.
There are two commented out lines which don't seem to work properly. I'd like this custom segue to both slide AND shrink so that the effect is for the fromVC to disappear up into nothing and the toVC to arrive from nothing from the bottom.
However, if I try to do both simultaneously it fails. Either individually work fine (a zoom or a slide), but not together.
class UnwindScaleSegue: UIStoryboardSegue {
override func perform() {
scaleDown()
}
func scaleDown() {
let toVC = self.destination
let fromVC = self.source
let screenHeight = UIScreen.main.bounds.size.height
toVC.view.transform = CGAffineTransform.init(translationX: 0.0, y: screenHeight)
//toVC.view.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
fromVC.view.superview?.insertSubview(toVC.view, at: 0)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
//fromVC.view.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
fromVC.view.transform = CGAffineTransform.init(translationX: 0.0, y: -screenHeight)
toVC.view.transform = CGAffineTransform.identity
}, completion: { success in
fromVC.dismiss(animated: false, completion: nil)
})
}
}
Solved! It turns out it has to do with how the transforms are combined. I'm not certain I understand, but something having to do with the vector nature of the transformations means you have to be careful when doing multiple transforms in the same animation.
By "concatenating" them carefully it works! However, for me, if I swapped the order, it didn't work.
The key lines are starred.
class UnwindScaleSegue: UIStoryboardSegue {
override func perform() {
scaleAway()
}
func scaleAway() {
let toVC = self.destination
let fromVC = self.source
let screenHeight = UIScreen.main.bounds.size.height
var translate = CGAffineTransform(translationX: 0.0, y:screenHeight) //**
let scale = CGAffineTransform(scaleX: 0.001, y: 0.001) //**
toVC.view.transform = scale.concatenating(translate) //**
fromVC.view.superview?.insertSubview(toVC.view, at: 0)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
translate = CGAffineTransform(translationX: 0.0, y: -screenHeight) //**
fromVC.view.transform = scale.concatenating(translate) //**
toVC.view.transform = CGAffineTransform.identity
}, completion: { success in
fromVC.dismiss(animated: false, completion: nil)
})
}
}

Custom UIViewController transition doesn't next animations perform

When I use the custom UIViewControllerAnimatedTransitioning to do a custom transition from AViewController to BViewController it performs as expected, using a Cubic Animation, but then when I try to use a default iOS presentation, like a modal to transition from BViewController to CViewController, it doesn't animate at all.
Here's the code of the first transition:
class CubeTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private var direction: Direction!
convenience init(direction: Direction) {
self.init()
self.direction = direction
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else {
return
}
toView.frame = fromView.frame
containerView.backgroundColor = UIColor.white
let fromViewIntialTransform = fromView.layer.transform
fromView.layer.anchorPointZ = -((fromView.frame.size.width) / 2)
var transform: CATransform3D = CATransform3DIdentity
transform.m34 = -1.0 / 1000
transform = CATransform3DTranslate(transform, 0, 0, (fromView.layer.anchorPointZ))
fromView.layer.transform = transform
containerView.addSubview(fromView)
toView.alpha = 1
fromView.alpha = 1
let toViewIntialTransform = toView.layer.transform
toView.layer.anchorPointZ = -((toView.frame.size.width) / 2)
transform = CATransform3DIdentity
transform.m34 = -1.0 / 1000
transform = CATransform3DTranslate(transform, 0, 0, (toView.layer.anchorPointZ))
toView.layer.transform = transform
containerView.insertSubview(toView, belowSubview:fromView)
// transform toView to it's begining position
switch direction {
case .left: // t angle x y z
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi/2), 0, 1, 0)
case .right:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi / 2), 0, 1, 0)
case .up:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi/2), 1, 0, 0)
case .down:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi/2), 1, 0, 0)
default:
break
}
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: UIViewAnimationOptions(), animations: {
// Animate toView to go to fromView initial position,
// And the fromView to go to a new position
switch self.direction {
case .left: // t angle x y z
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi / 2), 0, 1, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(Double.pi / 2), 0, 1, 0)
case .right:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi / 2), 0, 1, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(-Double.pi / 2), 0, 1, 0)
case .up:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi/2), 1, 0, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(Double.pi / 2), 1, 0, 0)
case .down:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi/2), 1, 0, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(-Double.pi / 2), 1, 0, 0)
default:
break
}
}, completion: {(value: Bool) in
fromView.layer.transform = fromViewIntialTransform
toView.layer.transform = toViewIntialTransform
fromView.layoutSubviews()
toView.layoutSubviews()
// This undo the transition if it's cancelled
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
The thing gets even stranger when I use another transition to perform the segue from A to B, the transition from B to C starts to working again.
Second transition:
class PopTransition: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 0.3
var originFrame = CGRect.zero
var presenting = true
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let detailView = presenting ? toView : transitionContext.view(forKey: .from)!
let initialFrame = presenting ? originFrame : detailView.frame
let finalFrame = presenting ? detailView.frame : originFrame
let xScaleFactor = presenting
? initialFrame.width / finalFrame.width
: finalFrame.width / initialFrame.width
let yScaleFactor = presenting
? initialFrame.height / finalFrame.height
: finalFrame.height / initialFrame.height
let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)
if presenting {
detailView.transform = scaleTransform
detailView.center = CGPoint(
x: initialFrame.midX,
y: initialFrame.midY)
}
containerView.addSubview(toView)
containerView.bringSubview(toFront: detailView)
UIView.animate(
withDuration: duration,
delay: 0.0,
animations: {
detailView.transform = self.presenting ? .identity : scaleTransform
detailView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
},
completion:{_ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
)
}
}
You are modifying toView.layer.anchorPointZ and fromView.layer.anchorPointZ so when the transition from B to C is about to happen, the anchorPointZ is messed up.
Try saving and reseting it like you do with fromViewIntialTransform and toViewIntialTransform.

How to animate the view with pre-existing animation?

I want to animate a view with the pre-existing animation of UIView.
I am using this code for animating the UIView -
CATransition *transition = [[CATransition alloc] init];
transition.duration = 0.1;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
[viewToAnimate.layer addAnimation:transition forKey:kCATransition];
I want to animate some other view, along with this animation.
What you need to do is to chain your animations. Here is example from Apple.
let fadeOut = CABasicAnimation(keyPath: "opacity")
fadeOut.fromValue = 1
fadeOut.toValue = 0
fadeOut.duration = 1
let expandScale = CABasicAnimation()
expandScale.keyPath = "transform"
expandScale.valueFunction = CAValueFunction(name: kCAValueFunctionScale)
expandScale.fromValue = [1, 1, 1]
expandScale.toValue = [3, 3, 3]
let fadeAndScale = CAAnimationGroup()
fadeAndScale.animations = [fadeOut, expandScale]
fadeAndScale.duration = 1
You can use basic iOS animation that is:
UIView .animate(withDuration: TimeInterval) {
code
}
In the code segment you can write to change the x positions of different views. whenever you will click on button that respective view's x corrdinate will animate to 0.
Try this
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController)
let containerView = transitionContext.containerView()
let bounds = UIScreen.mainScreen().bounds
toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.size.height)
containerView.addSubview(toViewController.view)
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
fromViewController.view.alpha = 0.5
toViewController.view.frame = finalFrameForVC
}, completion: {
finished in
transitionContext.completeTransition(true)
fromViewController.view.alpha = 1.0
})
}

Resources