Inside UIViewControllerAnimatedTransitioning delegate, the transitionContext.fromViewController is nil - ios

I have a UINavigationController setup and am expecting to use a custom animation for pop / push of views with it. I have used custom transitions before without issue, but in this case I am actually finding nil values in my 'from' and 'to' UIViewControllers.
My setup is very similar to this SO Post
Custom DataEntryViewController
class DataEntryViewController : UIViewController, DataEntryViewDelegate, UINavigationControllerDelegate {
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = DataEntryTransitionAnimator()
animator.duration = 2
return animator
}
}
Custom BaseTransitionAnimator
class BaseTransitionAnimator : NSObject, UIViewControllerAnimatedTransitioning {
var duration : NSTimeInterval = 0.5 // default transition time of 1/2 second
var appearing : Bool = true // is the animation appearing (or disappearing)
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return duration
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
assert(true, "animateTransition MUST be implemented by child class")
}
}
Subclassed TransitionAnimator
class DataEntryTransitionAnimator : BaseTransitionAnimator {
override func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView()
let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewKey) as! DataEntryViewController
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewKey) as! DataEntryViewController
let duration = transitionDuration(transitionContext)
// do fancy animations
}
}
Using the above, both fromVC and toVC are nil
How is it possible that the transitionContext doesn't have valid pointers to the 'to' and 'from' UIViewControllers?

you are using UITransitionContextFromViewKey but you need to use UITransitionContextFromViewControllerKey
same for "to"

Related

UIViewControllerAnimatedTransitioning ... 'wrong' VC fetched via `.from` key

I'm working on my first custom animation/presentation transition.
I've configured:
UIPresentationController
NSObject conforming to UIViewControllerAnimatedTransitioning protocol.
I'm doing modal presentation of a presentedVc.
I want to display presentedVc with custom transition
I want it to be presented over presentingVc
Note that presentingVC is root view controller assigned to a UINavigationController.
The Problem:
In animator object: When I fetch .from and .to keys, .from controller is UINavigationController
(i.e. Instead of expected presentingVc)
I'm not sure what to do about that.
Not only do I need presentingVc to be recipient of presentedVc, but there are specific animations I need to perform with subviews of presentingVc.view
In presentedVc:
init(...) {
transitioningDelegate = self
modalPresentationStyle = .custom
}
From unrelated Vc:
let presentedVc = PresentedVc(originalFrame: CGRect(...))
presentingVc.present(presentedVc, animated: true, completion: nil)
In UIViewControllerAnimatedTransitioning class:
class ResizingAnimatedTransition : NSObject, UIViewControllerAnimatedTransitioning {
var originalFrame = CGRect.zero
init(originalFrame: CGRect) {
self.originalFrame = originalFrame
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 3.0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to)
else {
return
}
.
. // At this point fromVC is UINavigationController
. // but I was expecting it to be UIViewController presentedVC
}
}

How to use source (not presenting) in animationController (UIViewControllerTransitioningDelegate)?

I'm making a custom Transition Manager. It conforms to the protocols UIViewControllerAnimatedTransitioning & UIViewControllerTransitioningDelegate.
When presenting from RocketsVC to RocketDetailsVC, func is called animationController(for Presented presented: UIViewController, presenting: UIViewController, source: UIViewController), the following types are passed there:
presented: RocketDetailsViewController
presenting: MainTabBarController (Error is here)
source: RocketsViewController
This functions are declared in TransitionManager.swift:
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
type = .presentation
return self // TransitionManager
}
And then animateTransition method is called...
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = transitionContext.viewController(forKey: .from) as? TransitionManagerProtocol,
let toViewController = transitionContext.viewController(forKey: .to) as? TransitionManagerProtocol
...with zero effect, because MainTabBarController does not conform to TransitionManagerProtocol.
If I build a project without MainTabBarController (rootVC is RocketsVC), then everything works as it should.
What should I do to make the transition work? I'm sinning on MainTabBarController, but maybe there is a way to somehow pass to animationController method source instead of presenting?
Full code is in my GitHub
TransitionManager.swift
RocketsViewController.swift
MainTabBarController.swift
The solution I've found after long hours:
private var fromVC: UIViewController?
private var toVC: UIViewController?
...
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
type = .presentation
fromVC = source
toVC = presented
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
type = .dismissal
toVC = fromVC // last `fromVC` is now `toVC`
fromVC = dismissed // and now we set this to fromVC
return self
}
And:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = fromVC as? TransitionManagerProtocol,
let toViewController = toVC as? TransitionManagerProtocol
else {
transitionContext.completeTransition(false)
return
}
...

How to animate transitions in a UITabBarController using more than 5 ViewControllers?

I have a UITabBarController which has 9 view controllers and I am custom handling the navigation with a forward and back button. I have created a UIViewControllerAnimatedTransitioning object to handle the animation (simple left to right) and it gets returned in the delegate method:
tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
However, on and after selecting the fifth index, this function is no longer called (as it is now handled by the UIMoreNavigationController. Is there an animation delegate or some way I should be handling it for the UIMoreNavigationController instance?
You would need to specify the delegate of your moreNavigationController. So in your UITabBarController class you would need to:
self.moreNavigationController.delegate = strongDelegate // where strongDelegate is a local instance of MyControllerDelegate as defined below
The delegate should then implement the navigationController function which will call the applicable UIViewControllerAnimatedTransitioning instance:
class MyControllerDelegate: NSObject, UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
return MyPresentationAnimationController(isPresenting: true)
case .pop:
return MyPresentationAnimationController(isPresenting: false)
default:
return nil
}
}
}
Your animateTransition function contains the logic where the magic happens:
class MyPresentationAnimationController : NSObject, UIViewControllerAnimatedTransitioning {
private let isPresenting: Bool
init(isPresenting: Bool) {
self.isPresenting = isPresenting
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return TimeInterval(UINavigationController.hideShowBarDuration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let toView = transitionContext.view(forKey: .to),
let fromView = transitionContext.view(forKey: .from)
else {
return
}
let containerView = transitionContext.containerView
// TODO: Implement the logic with UIView.animateKeyframes here ...
}
}

Custom transition

I have a navigation controller, and inside of that navigation controller I have a home screen, from the home screen I click a button which goes to another screen.
But the standard show animation when using a navigation controller is that it slides from the side, but what I want to do is that the view controller slides up from bottom of the screen and creates a sort of bouncing animation when it reaches the top.
Anyone who wanted to use custom transition two things to remember UIViewControllerAnimatedTransitioning and UIViewControllerTransitioningDelegate protocols. Now conform UIViewControllerAnimatedTransitioning inside your customclass inheriting from NSObject
import UIKit
class CustomPushAnimation: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.2
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerVw = transitionContext.containerView
let fromViewController = transitionContext.viewController(forKey: .from)
let toViewController = transitionContext.viewController(forKey: .to)
guard let fromVc = fromViewController, let toVc = toViewController else { return }
let finalFrame = transitionContext.finalFrame(for: toVc)
//For different animation you can play around this line by changing frame
toVc.view.frame = finalFrame.offsetBy(dx: 0, dy: finalFrame.size.height/2)
containerVw.addSubview(toVc.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
toVc.view.frame = finalFrame
fromVc.view.alpha = 0.5
}, completion: {(finished) in
transitionContext.completeTransition(finished)
fromVc.view.alpha = 1.0
})
}
}
The above method will take care of the animation. After that create
the above object and use inside yourViewController class
import UIKit
class YourViewController: UIViewController {
lazy var customPushAnimation: CustomPushAnimation = {
return CustomPushAnimation()
}()
func openViewControler() {
}let vc =//Assuming your view controller which you want to open
let navigationController = UINavigationController(rootViewController: vc)
//Set transitioningDelegate to invoke protocol method
navigationController.transitioningDelegate = self
present(navigationController, animated: true, completion: nil)
}
Note: In order to see the animation. Never set animation flag to false
while presenting the ViewController. Otherwise your animation will
never work.
Lastly implement the UIViewControllerTransitioningDelegate protocol method inside YourViewcontroller class
extension YourViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return customPushAnimation
}
Whenever you present the viewcontroller above protocol method will
called and your animation magic will appear.

Custom push transition isn't working whereas the same custom present transition is

I have followed this tutorial and watched the WWDC video on this subject but I couldn't find my answer.
I have almost the same transition in my code. It is working pretty well when I am doing it as a presented view, but not as a pushed view.
It is supposed to animate a snapshot of the pushed view from a CGRect to the full screen and vice versa when popped.
Here is the code of my UIViewControllerAnimatedTransitioning class:
class ZoomingTransitionController: NSObject, UIViewControllerAnimatedTransitioning {
let originFrame: CGRect
let isDismissing: Bool
init(originFrame: CGRect, isDismissing: Bool) {
self.originFrame = originFrame
self.isDismissing = isDismissing
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return Constant.Animation.VeryShort
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else {
return
}
let finalFrame = transitionContext.finalFrame(for: toVC)
toVC.view.frame = finalFrame
let snapshot = self.isDismissing ? fromVC.view.snapshotView(afterScreenUpdates: true) : toVC.view.snapshotView(afterScreenUpdates: true)
snapshot?.frame = self.isDismissing ? finalFrame : self.originFrame
snapshot?.layer.cornerRadius = Constant.FakeButton.CornerRadius
snapshot?.layer.masksToBounds = true
containerView.addSubview(toVC.view)
containerView.addSubview(snapshot!)
if self.isDismissing {
fromVC.view.isHidden = true
} else {
toVC.view.isHidden = true
}
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration,
animations: {
snapshot?.frame = self.isDismissing ? self.originFrame : finalFrame
},
completion: { _ in
if self.isDismissing {
fromVC.view.isHidden = false
} else {
toVC.view.isHidden = false
}
snapshot?.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
Then, I tried to show a new View Controller using 2 ways: by presenting it and by pushing it.
My FromViewController is subclassing both UINavigationControllerDelegate and UIViewControllerTransitioningDelegate.
FromViewController class presenting the ToViewController (which works fine):
func buttonAction(_ sender: AnyObject) {
self.tappedButtonFrame = sender.frame
let toVC = self.storyboard!.instantiateViewController(withIdentifier: "ToViewController")
toVC.transitioningDelegate = self
self.present(toVC, animated: true, completion: nil)
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transitionController = ZoomingTransitionController(originFrame: self.tappedButtonFrame, isDismissing: false)
return transitionController
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transitionController = ZoomingTransitionController(originFrame: self.tappedButtonFrame, isDismissing: true)
return transitionController
}
FromViewController class pushing the ToViewController (which doesn't work):
func buttonAction(_ sender: AnyObject) {
self.tappedButtonFrame = sender.frame
let toVC = self.storyboard!.instantiateViewController(withIdentifier: "ToViewController")
self.navigationController?.pushViewController(toVC, animated: true)
}
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
return ZoomingTransitionController(originFrame: self.tappedButtonFrame, isDismissing: false)
case .pop:
return ZoomingTransitionController(originFrame: self.tappedButtonFrame, isDismissing: true)
default:
return nil
}
}
When pushing, the delegate method is called and the ZoomingTransitionController performs its code fine (going in animateTransition until the end without notable issue). But on the screen, the snapshot view isn't display at any moment. The ToVC appears after the transition duration, but without anything else meanwhile.
I am running out of idea on how to debug this... Do you have any idea?
Thanks!!
I found my answer by replacing the snapshot element (which was causing the problem) by a CGAffineTransform of the toVC.
Code is almost the same than here.

Resources