Ok, I have looked through https://developer.apple.com/videos/play/wwdc2013/218/ and SO questions similarly trying to make custom transitions with a tab bar controller and its viewcontrollers but Im running into confusion after figuring out the main steps here.
I need to know how (meaning example code would be really helpful) to call a custom UIView.transition OR just have a custom option in UIView.transition. I need to use this to make a sliding/modal -mimicking transition between tabs in my tab bar controller.
The only way I can get a transition to happen is using the below function (this makes them dissolve):
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if selectedViewController == nil || viewController == selectedViewController {
return false
}
let fromView = selectedViewController!.view
let toView = viewController.view
UIView.transition(from: fromView!, to: toView!, duration: 0.3, options: [.transitionCrossDissolve], completion: nil)
return true
}
And calling it manually here where I programmatically chnage the *selectedIndex* for my tab controller:
//SWITCHES BUTTON -------------------------------------------
func switchTab(index: Int)
{
//transition
self.tabBarController(self, shouldSelect: (viewControllers?[index])!)
I have read about and tried making a custom class UIViewControllerAnimatedTransitioning but don't know how this would fit in programmatically here -
my attempts passing my tab bar controller and the toView and fromViewinto the custom class result in nothing happening/animating. That is why Ive resorted to UIView.transition here.
How can I make a custom UIView.transition? What can I do here?
You should conform to UITabBarControllerDelegate
and Create a customTransition class and pass it as follows:
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = TabAnimationManager()
return animator
}
And Class TabAnimationManager should be a subclass of UIPercentDrivenInteractiveTransition and conform UIViewControllerAnimatedTransitioning protocol. You can add your custom animations in that class.
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 need to display the UITabBarController, but I don't need it to switch me to the Controller from the viewControllers array. Can I reassign the event or would it be better to create my own such TabBarController?
You can create a subclass for UITabBarController and confirm to UITabBarControllerDelegate
Then you can perform custom tab section actions in shouldSelect method.
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool
{
if viewController == secondViewController {
//Do your actions
return false
}
return true
}
I've a project which contains a tab controller, as the main 'page'.
I want to add a UITabBar button item, which presents a view controller modaly, and within that view controller, add a dismiss button that dismisses that view controller and returns to the previous tab selection.
To clarify, it's something that the Medium app for iOS uses, when you click on the create post item, it presents it modaly and dismisses it when you want to.
I can present the view controller, but I can't dismiss it.
Hope I made myself understandable.
Example:
So I've managed to solve this as I wanted to, hope the answer will help someone in the future.
First of all, I set on my first loaded view controller, lets say my 'Home' view controller the tabBarController delegate to be my AppDelegate.swift file (this is personal preference), as:
self.tabBarController?.delegate = UIApplication.shared.delegate as? UITabBarControllerDelegate
Then in my AppDelegate.swift file i added to the class properties the delegate UITabBarControllerDelegate so I could access the tabBarController delegate functions, such as
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {}
Then edited the function to interact with my specific view controller.
The final version of the function:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController is ViewControllerToPresentModaly {
if let newVC = tabBarController.storyboard?.instantiateViewController(withIdentifier: "modalVC") {
tabBarController.present(newVC, animated: true)
return false
}
}
return true
}
Here if you want the regular tab behaviour, return true or your own return false
;)
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)
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.