I have implemented UINavigationControllerDelegate method:
public func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { }
because I had to set application state and only "event" that was able to capture Pop transition of my UINavigationController was this method. Everything is working properly but as far as that functionality is concerned, but now my swipe back action does not work (only pressing back button works).
At the end of this method I have returned nil because I had no idea what its doing but apparently it affects that swipe to back functionality.
Is there any default UIViewControllerAnimatedTransitioning that I can returned to have that swipe back functionality?
Thanks.
I switched to this delegate method:
public func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
if self.viewControllerCount - 1 == self.rootController.viewControllers.count {
// Pop action, do additional logic
}
}
and customized it so that I can know for sure if its pop action (tracking last number of view controllers.
Related
I need to animate a UIViewController which is the root of a UINavigationController. This navigation controller is presented modally.
I know I can do it with the lifecycle methods of the view controller, but I want to use a UIViewControllerAnimatedTransitioning if possible to keep the VC code clean.
I tried the following:
a) Set the UINavigationControllerDelegate to the navigation controller, so I could use this delegate function:
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
However, it only gets called during transitions within the navigation controller (from VC to VC).
b) Set a UIViewControllerTransitioningDelegate in the root view controller and use this delegate function:
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
But again, it doesn't get called as the one presenting is the navigation controller.
c) Set a UIViewControllerTransitioningDelegate in the navigation controller and use the same delegate function:
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
In this case, it gets called and I can inject the UIViewControllerAnimatedTransitioning object, but I can only think about hacks to animate the root, e.g.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) as? UINavigationController,
toVC.children.count == 1,
let myRootVC = toVC.children.first as? MyViewController
else {
transitionContext.completeTransition(false)
return
}
// Animate manually
myRootVC.view.alpha = 0
// [...]
}
Do you know any way to capture this nicely?
I solved the similar problem by making all my ViewControllers being presented as embedded ViewControllers using ViewController Containment.
I do have 1 RootContainerViewController setup when launching the the application - it's simply set as rootViewController without animations and it's just container for all other ViewControllers.
All other ViewControllers are always added as SubViewControllers and SubViews to that container.
That allows you not only to use UIViewControllerAnimatedTransitioning for every single transition in your application, but also it allows you to easily make "reset" of the app, by just removing RootContainerViewController and replacing it with fresh one.
Another advantage is that you can add as many ViewControllers as you want - compared to native presentViewController that allows you to have only 1 ViewController presented at a time.
Any other approaches I tried ended up badly - UIViewControllerAnimatedTransitioning is impossible to use directly with rootViewController changes - you will have to combine it with UIView.animate to support it.
I have a UINavigationController with custom transitions when pushing and popping using the UIViewControllerAnimatedTransitioning protocol:
class NavigationHandler: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CustomAnimator()
}
}
class CustomAnimator: UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// do animation stuff
}
}
This works fine for push and pop, but it doesn't work for the back-swipe gesture (which will show the default transition). I would like to customize the back swipe, but can't find a good way to do that.
Knowledge I gained so far:
Setting up my own gesture recognizer to detect the back swipe is possible, but UINavigationController will still perform the default animation, so now my animation fights the default animation, which seems wrong
UINavigationControllerDelegate has a method navigationController(:interactionControllerFor animationController:), which seems like it's exactly what I need, but it's only called for regular push/pop operations, not for back swipe
I have a custom subclass of UINavigationController that sets itself as the UINavigationControllerDelegate, and conditionally returns a custom animator. I want to be able to toggle between the custom animator and the system animation using a boolean flag. My code looks something like this:
class CustomNavigationController: UINavigationControllerDelegate {
var useCustomAnimation = false
private let customAnimator = CustomAnimator()
func navigationController(_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if useCustomAnimation {
return CustomAnimator()
}
return nil
}
}
However, when useCustomAnimation is false, the interactive back gesture managed by the system no longer works. Everything else related to the system animation still works.
I've tried setting the delegate of the interactive pop gesture to my custom navigation controller and returned true/false from some methods with varying levels of success.
So it seems this is a bug in UIKit. I've created a small project that reproduces the error and submitted it to Apple. Essentially the interactive pop gesture is broken whenever the animationController delegate method is implemented by the UINavigationControllerDelegate. As a workaround, I've created two delegate proxies, one that implements the method, and one that does not:
class NavigationControllerDelegateProxy: NSObject, UINavigationControllerDelegate {
weak var delegateProxy: UINavigationControllerDelegate?
init(delegateProxy: UINavigationControllerDelegate) {
self.delegateProxy = delegateProxy
}
/*
... Other Delegate Methods
*/
}
class CustomAnimationNavigationControllerDelegateProxy: NavigationControllerDelegateProxy {
func navigationController(_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return delegateProxy?.navigationController?(navigationController,
animationControllerFor: operation,
from: fromVC,
to: toVC)
}
}
I simply alternate between these classes acting as the actual UINavigationControllerDelegate depending on the state of useCustomAnimation.
I've been dabbling with some very basic custom transitions recently and I'm starting to get the hang of it. Up until this point, however, I had only made Modal transitions.
I wrote the following code for a basic "Fade" transition from my "Master View" to my "Settings View" (both of which are part of the NavigationController Stack):
import UIKit
import QuartzCore
///Transition manager for transitioning between the loginVC and the content that follows.
class FadeTransition: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let destinationVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let sourceView = transitionContext.viewForKey(UITransitionContextFromViewKey)
let destinationView = transitionContext.viewForKey(UITransitionContextToViewKey)
transitionContext.containerView().addSubview(destinationView!)
UIView.animateWithDuration(self.transitionDuration(transitionContext), animations: { () -> Void in
sourceView!.alpha = 0.0
destinationView!.alpha = 1.0
}) { (didComplete) -> Void in
println("Fade Transition: \(didComplete)")
transitionContext.completeTransition(didComplete)
}
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.5
}
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
}
I was excited. I rejoiced. All appeared to be well until I then went to a different View Controller that also has a segue from the "Master View"...and it had the same transition. Of course this is to be expected, I just hadn't thought of it.
How can I limit this transition to only occur between two specific UIViewControllers?
You need to look at the UINavigationControllerDelegate method navigationController(_:animationControllerForOperation:fromViewController:toViewController). You have implemented it in your custom transition class above (I would suggest making a more general class your nav controller delegate instead, such as the view controller that contains it or the app delegate), but you return self indisciminately, indicating that every transition for that navigation controller should use your fade transition.
Instead, look at the from/toViewController parameters to decide whether you want to use a custom transition between those particular view controllers. If you want to use the default animated transition, just return nil.
From the UINavigationControllerDelegate Protocol Reference:
Return Value
The animator object responsible for managing the transition animations,
or nil if you want to use the standard navigation controller
transitions. The object you return must conform to the
UIViewControllerAnimatorTransitioning protocol.
Using storyboard, i unwind a viewcontroller
I'm looking for:
Remove the left to right transition
And also
Set a fadeIn fadeOut between the 2 UIViewController.
I'm pretty sure the way to do it is via this:
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return nil
}
But, even is this was called and return nil, i'm still viewing the transition.