Swift Custom Transition Not Working (UIViewControllerTransitioningDelegate not called) - swift

I am just trying to switch to a second view controller. I have my HomeViewController with this extension:
extension HomeViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController)
-> UIViewControllerAnimatedTransitioning? {
return transition
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return nil
}
}
and transition defined in the class
let transition = PopAnimator()
I have a button that when it is tapped should switch view controllers and use custom transition (PopAnimator):
#objc func handleTap() {
let viewController = ViewController()
viewController.transitioningDelegate = self
self.present(viewController, animated: false, completion: nil)
}
PopAnimator class (really just a fadeIn for now):
class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 1.0
var presenting = true
var originFrame = CGRect.zero
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
containerView.addSubview(toView)
toView.alpha = 0.0
UIView.animate(withDuration: duration,
animations: {
toView.alpha = 1.0
},
completion: { _ in
transitionContext.completeTransition(true)
}
)
}
}
When I click the button, it switches viewControllers but doesn't use my custom transition. If I put a breakpoint in my animationController(forPresented:presenting:source:) function or my PopAnimator class it is not reached.

self.present(viewController, animated: false, completion: nil)
try setting animated: true if you want animated transition

Related

Swift constraints issue when using custom segue

I am using custom segue to present a view controller, it works fine when I don't use any constraint, once I add a constraint to the label on the destination view controller, then it pops up an error saying:
"negative sizes are not supported in the flow layout"
This is my custom segue code
class PresentTimeMachineSegue: UIStoryboardSegue {
override func perform() {
destination.transitioningDelegate = self
destination.modalPresentationStyle = .fullScreen
source.present(destination, animated: true, completion: nil)
}
}
extension PresentTimeMachineSegue: UIViewControllerTransitioningDelegate {
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return TimeMachineViewPresentAnimationController()
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return TimeMachineViewPresentAnimationController()
}
}
class TimeMachineViewPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
CalendarViewController
let containerView = transitionContext.containerView
let toViewController = transitionContext.viewController(forKey: .to) as! TimeMachineViewController
print("transition")
if toViewController.calendarView != nil {
//toViewController.view.addSubview(toViewController.calendarView!)
containerView.addSubview(toViewController.view)
toViewController.inlitializeUI()
}
toViewController.view.backgroundColor = UIColor.white.withAlphaComponent(0)
UIView.animate(withDuration: 0.8, animations: {
toViewController.view.backgroundColor = UIColor.white.withAlphaComponent(1)
}) { _ in
transitionContext.completeTransition(true)
}
}
}

How to enable tab bar transition animation to work without initial view controller

Essentially I have the following custom transition animation for Tab Bar Controller:
MyFadeTransition.swift
import UIKit
class MyFadeTransition: NSObject, UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if let fromVC = transitionContext.viewController(forKey: .from), let toVC = transitionContext.viewController(forKey: .to) {
toVC.view.frame = fromVC.view.frame
toVC.view.alpha = 0
fromVC.view.alpha = 1
transitionContext.containerView.addSubview(fromVC.view)
transitionContext.containerView.addSubview(toVC.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
toVC.view.alpha = 1
}) { (finished) in
transitionContext.completeTransition(finished)
}
}
}
func animationEnded(_ transitionCompleted: Bool) {
// no-op
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.12
}
}
The issue is the code also requires the Tab Bar Controller to be the initial view controller during launch and the code below in the AppDelegate
let tab = window!.rootViewController as! UITabBarController
tab.delegate = self
The following must also be added to AppDelegate.swift
extension AppDelegate: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let fade = MyFadeTransition()
return fade
}
}
I am using a different ViewController as the Initial ViewController, how can I make the code continue to work without having it as the Initial Controller?
You need to subclass UITabBarController and use
class CustomTab:UITabBarController,UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return MyFadeTransition()
}
}
Then assign CustomTab as class name to the tabBar in IB

Why does my custom transition not work with my navigation controller?

I'm using the coordinator pattern in my code to transition from the RootNavigationController to a SplashScreenViewController
class AppCoordinator {
let window: UIWindow
let rootViewController: RootNavigationController
init(window: UIWindow) {
self.window = window
rootViewController = RootNavigationController()
let splashScreenViewController = SplashScreenViewController()
rootViewController.pushViewController(splashScreenViewController, animated: false)
}
}
extension AppCoordinator: Coordinator {
func start() {
window.rootViewController = rootViewController
window.makeKeyAndVisible()
}
}
I'm also using a custom transition to handle the transition from RootNavigationController to SplashScreenNavigationController.
class FadeInAnimator: NSObject {
var duration: TimeInterval = 1.0
}
extension FadeInAnimator: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toViewController = transitionContext.viewController(forKey: .to) else { return }
containerView.addSubview(toViewController.view)
toViewController.view.alpha = 0
let durationOfTransition = transitionDuration(using: transitionContext)
UIView.animate(withDuration: durationOfTransition, delay: 0, options: [.curveEaseIn], animations: {
toViewController.view.alpha = 1
}) { (finished) in
transitionContext.completeTransition(finished)
}
}
}
I've set the delegate of the RootNavigationController to it's self and implemented the animation transitioning however, when I start the application it seems to just ignore everything I've done and just use the systems default transition.
This is the code in the RootNavigationController
class RootNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.shadowImage = UIImage()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
extension RootNavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push: return FadeInAnimator()
case .pop: return nil
case .none: return nil
}
}
}
Remove open & close bracket
UIView.animate(withDuration: durationOfTransition, delay: 0, options: .curveEaseIn, animations:

UIViewControllerTransitioningDelegate on tvOS not being called

So I'm trying to do an animated transition between viewcontrollers on tvOS 10.
The UIViewControllerTransitioningDelegate protocol is available on tvOS so I assumed I could animate it as well. But for some reason none of the functions are ever called when presenting the new viewcontroller.
I have no idea what I'm doing wrong since I'm doing basically the exact same on iOS.
Here is the code I'm using:
Presenting code
func showStuff() {
let viewController = ResultViewController()
viewController.transitioningDelegate = ResultViewControllerTransitionManager()
viewController.modalPresentationStyle = .custom
navigationController?.present(viewController, animated: true, completion: nil)
}
Transition Delegate
class ResultViewControllerTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
var duration = 0.5
var isPresenting = false
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {return}
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {return}
(...)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresenting = false
return self
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresenting = true
return self
}
}
You can implement UIViewControllerAnimatedTransitioning and UIViewControllerTransitioningDelegate protocols inside your first view controller. Your first UIViewController may have the following code:
class ViewController: UIViewController, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning {
func showStuff() {
let viewController = ResultViewController()
viewController.transitioningDelegate = self
viewController.modalPresentationStyle = .custom
self.present(viewController, animated: true, completion: nil)
}
var duration = 0.5
var isPresenting = false
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 2.0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {return}
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {return}
}
}
Also, I present a new controller from the first one (not from the navigationController).
So apparently initialising the animationmanager in the showStuff() function was the problem. Storing it as a property in the Main viewcontroller and passing it along did work.
Wouldn't the issue be that you are using a navigationController, so that navigationController?.delegate needs to equal your ResultViewControllerTransitionManager()?
We have one at work using a push animation (should be the same issue), but we set
navigationController?.delegate = transitionDelegate

Custom UINavigationController Segue Swift

There is a great tutorial for custom animated segues in Swift here:
http://mathewsanders.com/animated-transitions-in-swift/
I am a beginner and cannot figure how to use this same technique to create a custom segue for a UINavigationController
VC code:
class FlashCardView: UIViewController, UINavigationControllerDelegate {
let transitionManager = TransitionManager()
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let toViewController = segue.destinationViewController as UIViewController
toViewController.transitioningDelegate = self.transitionManager
}
/* ... */
}
TransitionManager class code:
import UIKit
class TransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
private var presenting = true
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
println("test1")
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
let offScreenRight = CGAffineTransformMakeTranslation(container.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(-container.frame.width, 0)
if (self.presenting){
toView.transform = offScreenRight
}
else {
toView.transform = offScreenLeft
}
container.addSubview(toView)
container.addSubview(fromView)
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 5, initialSpringVelocity: 0.8, options: nil, animations: {
if (self.presenting){
fromView.transform = offScreenLeft
}
else {
fromView.transform = offScreenRight
}
toView.transform = CGAffineTransformIdentity
}, completion: { finished in
transitionContext.completeTransition(true)
})
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
println("test2")
return 0.5
}
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
println("test3")
self.presenting = true
return self
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
println("test4")
self.presenting = false
return self
}
}
Luckily i learned transitions from that web site, it is an amazing blog.
It seems like you don't want the bouncing effect a the end of the transition. Use a normal animatewithduration.
Here is a working solution for your need:
class TransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
func animateTransition(transitionContext: UIViewControllerContextTransitioning)
{
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
let offScreenRight = CGAffineTransformMakeTranslation(container.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(-container.frame.width, 0)
toView.transform = offScreenRight
// add the both views to our view controller
container.addSubview(toView)
container.addSubview(fromView)
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(0.8, delay: 0.0, options: nil, animations: {
fromView.transform = offScreenLeft
toView.transform = CGAffineTransformIdentity
}, completion: { finished in
transitionContext.completeTransition(true)
})
}
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
}
}