I'm trying to implement a custom transition between two viewControllers. (Swift 3.0)
Between my two viewControllers I have a UISegue with the kind show (animated = true).
So I set the delegate methods of UIViewControllerTransitioningDelegate in the extension of my first view controller :
extension DocumentsViewController : UIViewControllerTransitioningDelegate { ... }
And I also have implemented the required methods by the protocol :
animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
...
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
...
}
Now when the segue is perform, in the firstViewController I'm using the delegate method prepareForSegue to finally set the transitioningDelegate to my `secondViewController, see below :
public override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let destination = segue.destination as? DocumentDetailViewController {
destination.transitioningDelegate = self
}
}
I check with breakpoints, the delegate is well setted to my firstViewController.
But the delegate methods of transitioningDelegate in my firstViewController are never fired, I don't know why.
Any ideas ?
PS : In my storyboard, my segue have Animated to true, so this should work, but it doesn't.
Thanks.
SOLVED : A mix of MadreDeDios, Matt and Tushar answers.
1 : As I want to keep the navigation in my app, I have to make conform my first viewController to UINavigationControllerDelegate instead of UIViewControllerTransitioningDelegate. (see MadreDeDios answer's).
2 : According to this protocol, I have implemented the following delegate method :
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
...
}
3 : I set the delegate earlier at the viewDidLoad() of my firstViewController (see Matt's answer) :
override public func viewDidLoad() {
super.viewDidLoad()
//...
self.navigationController?.delegate = self
}
4 : I'm using a manual push instead of a segue to display my secondViewController (see Tushar's answer).
Now this works, thank you.
The problem is that you are setting the transitioningDelegate too late. Way too late. It needs to be set very early in the lifetime of the view controller. I advise setting this property in the view controller's initializer.
Because you are using a push segue, I assume you are using a navigation controller as well.
When you are using an UINavigationController, it becomes the reference for every transition, animation, and even your app's orientation.
My advice would be to use your navigation controller as the manager for all your animations. All you need to do is to add these few things:
extension MyNavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
//Check for the good animation
return MyAnimation()
}
}
And inside your MyNavigationController class
override func viewDidLoad() {
super.viewDidLoad()
//This is the key
self.delegate = self
//Only if you want to animate the presentation of your navigation controller itself, the first time it appears:
self.transitioningDelegate = self
}
Try removing the transition of storyboard and execute the view presentation code manually and explicitly mention 'true' for the animated parameter :
presentViewController(documentVC, animated: true, completion: nil)
Related
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 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.
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.
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.
I am currently working on a project using custom transition between view controllers. In my storyboard, I control drag a button to another view controller to do a present modally transition, and in the presenting view controller's file I override the prepareForSegue method, the code is:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.destinationViewController is RightEyeViewController {
let rightEyeVC = segue.destinationViewController as RightEyeViewController
rightEyeVC.modalPresentationStyle = .Custom
rightEyeVC.transitioningDelegate = RightEyeTransitioningDelegate()
}
}
And in the UIViewControllerTransitioningDelegate I return the UIViewControllerAnimatedTransitioning that I created, the code is:
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animationController = RightEyeAnimatedTransitioning()
animationController.isPresentation = true
return animationController
}
The problem is every time the app crashes at the line return animationController, but after I add a break point before this line, the transition works without any problems, I don't know what caused this problem, can anyone offer some help!