I am using below delegate method to show animation in my app.
func navigationController(_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
But, above method is not getting call for rootViewController of navigationController.
Thanks for help in advance.
The method you're referring to is called to set up the transition animation. However, the rootViewController is added to the navigationController without any animation since there is no previous view controller to transition from. As a result, the delegate method is not invoked.
This is true even if you don't use the convenience initializer that specifies the rootViewController and instead push the rootViewController manually, specifying true for the animated: parameter. You'll notice that even in this case, the navigationController pushes the rootViewController without animation (as evidenced from the navigationController(:,willShow:,animated:) call having the animated parameter set to false.
AFAIK, it is not possible to transition-animate the rootViewController.
An alternative solution might be to implement navigationController(:, willShow:, animated:), do a check to see if self.viewControllers.first == viewController and then manually animate the viewController.view.
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 am implementing the navigationControllerDelegate func:
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
print("callin the Navigation Controller Delgate")
if viewController === self {
print("Calling the Navigation Controller delegate because is self and going to call tapButton")
//want to know who was previously on top of navigation.
}
}
I want to know here which was the viewController that is being removed form the stack since apple docs says that the
viewController
The view controller whose view and navigation item properties are being shown.
This means that this assumption is true:
viewController == navigationController.topViewController
or this one:
viewController == navigationController.visibleViewController
If not then one of this are the the viewControllers that is going to be removed.
It's not cleat for me since the func parameter name is willShow viewController, or is just a fancy name and the will show is the already shown.
So if not how from the delegate I may know which VC is being removed from the Navigation Stack.
It would have been nice if the delegate provided the viewController being removed, but I found it's just straight forward just to keep a reference.
weak var lastRemovedViewController: UIViewController? = nil
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
defer { lastRemovedViewController = viewController }
print("Navigation Removed \(lastRemovedViewController.debugDescription)")
}
yes is the answer, the viewController passed in the delegate func is the same as the topViewController, also is equal to navigationController.visibleViewController. so there is no way to know which was the removed viewController from the stack.
In my app I have a custom interactive transition for some view controllers. I am talking about UINavigationController interactive transitions for push/pop. For all other view controllers, I want to use the default iOS interactive transition (swiping from left to right to go back).
To activate my custom interactive transition I have to implement a UINavigationControllerDelegate and implement the animationControllerFor callback. This works fine! But if I try to return nil for some cases, meaning I don't want my custom transition and instead use the default one, it disables the back swipes completely!
Code to reproduce the issue:
class MainNavigationController: UINavigationController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func navigationController(_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationControllerOperation,
from fromController: UIViewController,
to toController: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return nil
}
}
Use this MainNavigationController and all your back swipes will be disabled inside this navigation controller! I wasn't expecting this. Because the documentation says about the return value:
The animator object responsible for managing the transition animations, or nil if you want to use the standard navigation controller transitions.
https://developer.apple.com/documentation/uikit/uinavigationcontrollerdelegate/1621846-navigationcontroller
Does anyone have any idea whether I am doing something wrong or if this is a bug? I tried this on both iOS 11 and iOS 12 beta.
I need help with custom segue transition from top to bottom. Its easy transition but I don't know how to do it. Is it done in a storyboard or must I do it in code? If it must do programmatically how I do it?
You are trying to write a custom transition animation for a push segue. So this is what you are going to do:
Set a delegate for your navigation controller.
In the delegate, implement func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? to return an object implementing UIViewControllerAnimatedTransitioning (this is often self in real life)
In that object, implement func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval and func animateTransition(using ctx: UIViewControllerContextTransitioning).
In animateTransition, get the info from the context (ctx). Put the to view into the containerView and animate it to its finalFrame. In your case, you'll start with the view above the final frame and animate it downwards, as you've specified. When you are all done (i.e. in the animation's completion handler), be sure to call completeTransition on the context.
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.