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:
Related
I have a simple push segue from one view controller to another. I want both segues (original and unwind to be unanimated).
In the attached playground, specifying false for the segue does, indeed remove the PUSH animation, but not the UNWIND animation.
Is there a way to remove the implicit animation in the UNWIND segue?
import UIKit
import PlaygroundSupport
class SourceViewController : UIViewController {
#objc func goDestination(_: Any) {
navigationController?.pushViewController(DestinationViewController(), animated: false)
}
override func loadView() {
view = UIView()
navigationItem.title = "SOURCE"
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(goDestination(_:)))
}
}
class DestinationViewController : UIViewController {
override func loadView() {
view = UIView()
view.backgroundColor = .yellow
navigationItem.title = "DESTINATION"
}
}
PlaygroundPage.current.liveView = UINavigationController(rootViewController: SourceViewController())
Those animations are fully customizable from the UINavigationControllerDelegate, check out the first "Supporting Custom Transition Animations" method
https://developer.apple.com/documentation/uikit/uinavigationcontrollerdelegate
Some details here https://www.youtube.com/watch?v=jWckfDNUJVY
at 23 minutes.
I'm greenchecking #glotcha's answer. It led me to this Medium post, which led me to this solution (I really wanted a fade transition):
import UIKit
import PlaygroundSupport
class TransitioningAnimatorInOut: NSObject, UIViewControllerAnimatedTransitioning {
var presenting: Bool = false
func transitionDuration(using: UIViewControllerContextTransitioning?) -> TimeInterval { 0.75 }
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else { return }
let container = transitionContext.containerView
if presenting {
container.addSubview(toView)
toView.alpha = 0.0
} else {
container.insertSubview(toView, belowSubview: fromView)
}
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
if self.presenting {
toView.alpha = 1.0
} else {
fromView.alpha = 0.0
}
}) { _ in
let success = !transitionContext.transitionWasCancelled
if !success {
toView.removeFromSuperview()
}
transitionContext.completeTransition(success)
}
}
init(presenting inPresenting: Bool) { presenting = inPresenting }
}
extension UINavigationController: UINavigationControllerDelegate {
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .push {
return TransitioningAnimatorInOut(presenting: true)
} else {
return TransitioningAnimatorInOut(presenting: false)
}
}
}
class SourceViewController : UIViewController {
#objc func goDestination(_: Any) {
navigationController?.pushViewController(DestinationViewController(), animated: true)
}
override func loadView() {
view = UIView()
view?.backgroundColor = .yellow
navigationItem.title = "SOURCE"
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(goDestination(_:)))
}
}
class DestinationViewController : UIViewController {
override func loadView() {
view = UIView()
view?.backgroundColor = .red
navigationItem.title = "DESTINATION"
}
}
PlaygroundPage.current.liveView = UINavigationController(rootViewController: SourceViewController())
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)
}
}
}
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
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
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
}
}