Custom UINavigationController Segue Swift - 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
}
}

Related

Removing Animation From UINavigationController Pushed Unwind?

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())

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)
}
}
}

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:

Swift Custom Transition Not Working (UIViewControllerTransitioningDelegate not called)

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

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