Swift - UIViewControllerAnimatedTransitioning not transform as expected with Swift4 - ios

I just upgraded to XCode9 with Swift4 today.
And I found that my UIViewControllerAnimatedTransitioning doesn't work as expected anymore.
The effect of this animation is that the fromView will scale down to 0.95 and the toView will slide in from the right side. The pop operation will do it reversely.
But now when I hit the back button of the NavigationBar, the start position of the toView isn't right. It displayed a original size toView and then scale up to 1.05.
Here's how I implement the transition animator.
// animate a change from one viewcontroller to another
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// get reference to our fromView, toView and the container view that we should perform the transition in
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
// set up from 2D transforms that we'll use in the animation
let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)
let offScreenDepth = CGAffineTransform(scaleX: 0.95, y: 0.95)
// start the toView to the right of the screen
if( presenting ){
toView.transform = offScreenRight
container.addSubview(fromView)
container.addSubview(toView)
}
else{
toView.transform = offScreenDepth
container.addSubview(toView)
container.addSubview(fromView)
}
// get the duration of the animation
// DON'T just type '0.5s' -- the reason why won't make sense until the next post
// but for now it's important to just follow this approach
let duration = self.transitionDuration(using: transitionContext)
// perform the animation!
// for this example, just slid both fromView and toView to the left at the same time
// meaning fromView is pushed off the screen and toView slides into view
// we also use the block animation usingSpringWithDamping for a little bounce
UIView.animate(withDuration: duration, delay: 0.0, options: .curveEaseOut, animations: {
if( self.presenting ){
fromView.transform = offScreenDepth
}
else{
fromView.transform = offScreenRight
}
toView.transform = .identity
}, completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
I didn't found anything special in this migration guide page.
https://swift.org/migration-guide-swift4/
What should I do to make the transition works again?

Before setting anything else, try to reset transformations of the fromView to identity:
// animate a change from one viewcontroller to another
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
...
UIView.animate(withDuration: duration, delay: 0.0, options: .curveEaseOut, animations: {
if( self.presenting ){
fromView.transform = offScreenDepth
}
else{
fromView.transform = offScreenRight
}
toView.transform = .identity
}, completion: { finished in
fromView.transform = .identity
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
....
}

Related

Programmatically letting my transition manager know where im coming from or where im going? Swift

So i have this code in my animateTransition func:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// Getting the reference to container, toView & fromView
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: .from)! // Force unpacked
let toView = transitionContext.view(forKey: .to)! // Force unpacked
// Setup for the 2d animation
let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)
let offScreenLeft = CGAffineTransform(translationX: -container.frame.width, y: 0)
// Start the toView to the right of the screen
container.addSubview(toView)
container.addSubview(fromView)
// Get the duration of the animation
let duration = self.transitionDuration(using: transitionContext)
// Perform the animation
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, animations: {
fromView.transform = offScreenLeft
toView.transform = .identity
}, completion: { finished in
// Tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
And if im trying to present a viewcontroller with the help of the transitionManager, which is containing this func, it can't find the from view. If i'm trying to dismiss, it can't find the to view. How can I tell it where it comes from in the present, and how can i tell it where it has to go when dismissing?
Okay, so I found the solution. The reason i wasn't able to reach the fromView or the toView depending on dismiss or present was because i called:
let fromView = transitionContext.view(forKey: .from)!
let toView = transitionContext.view(forKey: .to)!
Instead i should call:
let fromView = transitionContext.viewController(forkey: .from)!.view!
let toView = transitionContext.viewController(forkey: .to)!.view!

UIView snapshots on custom UIViewController transitions

I am creating a custom transition for my app but when I try to create a snapshot of the destination view it always appears as a blank white rectangle. It is important I note that this is a custom push transition and not a modal presentation of the view. When presenting modally the snapshot appears to work. Is this the normal behaviour for custom push/pop transitions?
The code I've written is as follows:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to) as? CultureViewController else {
return
}
let containerView = transitionContext.containerView
let finalFrame = transitionContext.finalFrame(for: toViewController)
let snapshot = toViewController.view.snapshotView(afterScreenUpdates: true)
snapshot?.frame = UIScreen.main.bounds
containerView.addSubview(snapshot!)
toViewController.view.transform = CGAffineTransform(translationX: 0, y: toViewController.view.bounds.height)
//containerView.addSubview(toViewController.view)
let duration = transitionDuration(using: transitionContext)
UIView.animateKeyframes(withDuration: duration, delay: 0, options: .calculationModeCubic, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: {
toViewController.view.frame = finalFrame
})
}, completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
Taking a snapshot of a view before it is rendered, such as is the case with a transition before you call containerView.addSubview(toViewController.view), returns a blank view, per UIView's documentation.
If the current view is not yet rendered, perhaps because it is not yet onscreen, the snapshot view has no visible content.

Custom ViewController Transition

I saw a tutorial on Appcoda Transition ViewControllers transition a menu from up to bottom and I implemented it. Then, I tried to transition from bottom up using UIViewControllerContextTransitioning. But, doing it wrong cause I was setting the wrong values I think. Below is the code
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
//Get reference to our fromView, toView and the container view
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)
//Setup the transform for sliding
let container = transitionContext.containerView()
let height = container?.frame.height
let moveDown = CGAffineTransformMakeTranslation(0, height! - 150)
let moveUp = CGAffineTransformMakeTranslation(0, -50)
//Add both views to the container view
if isPresenting {
toView?.transform = moveUp
snapShot = fromView?.snapshotViewAfterScreenUpdates(true)
container?.addSubview(toView!)
container?.addSubview(snapShot!)
}
//Perform the animation
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.3, options: UIViewAnimationOptions(rawValue: 0), animations: {
if self.isPresenting {
self.snapShot?.transform = moveDown
toView?.transform = CGAffineTransformIdentity
} else {
self.snapShot?.transform = CGAffineTransformIdentity
fromView?.transform = moveUp
}
}, completion: {finished in
transitionContext.completeTransition(true)
if !self.isPresenting {
self.snapShot?.removeFromSuperview()
}
})
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return duration
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
// Get reference to our fromView, toView and the container view
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
// Set up the transform we'll use in the animation
guard let container = transitionContext.containerView() else {
return
}
let moveUp = CGAffineTransformMakeTranslation(0, container.frame.height + 50)
let moveDown = CGAffineTransformMakeTranslation(0, -250)
// Add both views to the container view
if isPresenting {
toView.transform = moveUp
snapshot = fromView.snapshotViewAfterScreenUpdates(true)
container.addSubview(toView)
container.addSubview(snapshot!)
}
// Perform the animation
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: {
if self.isPresenting {
self.snapshot?.transform = moveDown
toView.transform = CGAffineTransformIdentity
} else {
self.snapshot?.transform = CGAffineTransformIdentity
fromView.transform = moveUp
}
}, completion: { finished in
transitionContext.completeTransition(true)
if !self.isPresenting {
self.snapshot?.removeFromSuperview()
}
})
}
This Should work. I checked out the tutorial you shared and you probably can't see the menu on the bottom because the way the MenuTableViewController.swift is set up on storyboard it is made so that the menu is always started from the top, so change that up and it should work perfectly fine. Let me know if you have any questions.

Custom animated transition - view in/view out

I have a custom transition written in Swift where the dismissed view goes out as the presented view comes in from the side.
Now I want this same effect, but I want the presented view to come in from the top and the dismissed view go out at the bottom.
My code looks like this:
func animateTransition(transitionContext: UIViewControllerContextTransitioning){
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)
let offScreenRight = CGAffineTransformMakeTranslation(container.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(-container.frame.width, 0)
if self.presenting == true{
toView?.transform = offScreenLeft
}else{
toView?.transform = offScreenRight
}
container.addSubview(toView!)
container.addSubview(fromView!)
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.8, options: nil, animations: {
if self.presenting == true{
fromView?.transform = offScreenRight
}else{
fromView?.transform = offScreenLeft
}
toView?.transform = CGAffineTransformIdentity
}, completion: { finished in
// tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
I thought this would be easy, as my logic tells the way to do this is to change from "width" to "height" in the toView, and fromView, but this does not work, and just creates the same effect as before, but it seems to skip one empty (black) view.
Any suggestions on how to achieve the desired effect would be appreciated.
The buggy code:
func animateTransition(transitionContext: UIViewControllerContextTransitioning){
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)
let offScreenUp = CGAffineTransformMakeTranslation(container.frame.height, 0)
let offScreenDown = CGAffineTransformMakeTranslation(-container.frame.height, 0)
if self.presenting == true{
toView?.transform = offScreenDown
}else{
toView?.transform = offScreenUp
}
container.addSubview(toView!)
container.addSubview(fromView!)
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.8, options: nil, animations: {
if self.presenting == true{
fromView?.transform = offScreenUp
}else{
fromView?.transform = offScreenDown
}
toView?.transform = CGAffineTransformIdentity
}, completion: { finished in
// tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
You are still trying to translate in the X Coordinate. Try doing the translation in the Y coordinate.
let offScreenUp = CGAffineTransformMakeTranslation(0,container.frame.height)
let offScreenDown = CGAffineTransformMakeTranslation(0,-container.frame.height)

Keyboard view gets messed up with Modally presented view using UIViewControllerTransitioningDelegate

I'm presenting a view from another using a UIViewControllerTransitioningDelegate instance as transitioning delegate and modalPresentationStyle = Custom.
I'm using a TableViewController using static table view cells with UITextfields inside. Now when tapping over a text field whose borders are close to the the keyboard's frame, part of the tableView beneath the keyboard shows up. I also removed a UITapGestureRecognizer that I added to the semi-transparent background to make sure it's not part of the problem but the issue it's still there. Any ideas? Below is the animateTransition() method
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
let containerView = transitionContext.containerView()
if let presentedViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) {
let presentedView = presentedViewController.view
let fromView = transitionContext.viewForKey(UITransitionContextFromViewControllerKey)
let centre = presentedView.center
if isPresenting {
presentedView.center = containerView.center
presentedView.frame = presentedView.bounds.rectByInsetting(dx: 30, dy: 150)
transitionContext.containerView().addSubview(presentedView)
dimmingView.frame = containerView.bounds
dimmingView.alpha = 0.0
containerView.insertSubview(dimmingView, atIndex: 0)
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({
context in
self.dimmingView.alpha = 1.0
}, completion: nil)
}
else {
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({
context in
self.dimmingView.alpha = 0.0
}, completion: {
context in
self.dimmingView.removeFromSuperview()
})
}
UIView.animateWithDuration(self.transitionDuration(transitionContext),
delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10.0, options: nil,
animations: {
presentedView.center = centre
}, completion: {
_ in
transitionContext.completeTransition(true)
})
}
}

Resources