1

I've written a custom segue to get a fade effect, which i'm trying to achieve by inserting the destination view controller below the source view controller and animating the alpha of the source view controller to zero.

However, adding the destination view controller below the source seems to cancel the animation and the segue performs as if it is a regular present with animation disabled.

import UIKit

class FadeSegue: UIStoryboardSegue {

    override func perform() {
        // Get the view of the source
        let sourceViewControllerView = self.sourceViewController.view
        // Get the view of the destination
        let destinationViewControllerView = self.destinationViewController.view

        let screenWidth = UIScreen.mainScreen().bounds.size.width
        let screenHeight = UIScreen.mainScreen().bounds.size.height

        // Make the destination view the size of the screen
        destinationViewControllerView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)

        if let window = UIApplication.sharedApplication().keyWindow {
            // Insert destination below the source
            // Without this line the animation works but the transition is not smooth as it jumps from white to the new view controller
            window.insertSubview(destinationViewControllerView, belowSubview: sourceViewControllerView)

            // Animate the fade, remove the destination view on completion and present the full view controller
            UIView.animateWithDuration(0.4, animations: {
                sourceViewControllerView.alpha = 0.0
                }, completion: { (finished) in
                    destinationViewControllerView.removeFromSuperview()
                    self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: nil)
            })
        }
    }
}
Chris Byatt
  • 3,689
  • 3
  • 34
  • 61
  • I am not sure will it give the same result, but you can try to set destinationViewControllerView.alpha = 0.0 and then change its alpha to 1.0 in animation block, in this case you don't need to call window.insertSubview(destinationViewControllerView, belowSubview: sourceViewControllerView) before animation. – Alex Kosyakov May 31 '16 at 15:06

3 Answers3

4

Hello I think that what you need is this

override func perform() {
        // Get the view of the source
        let sourceViewControllerView = self.sourceViewController.view
        // Get the view of the destination
        let destinationViewControllerView = self.destinationViewController.view

        let screenWidth = UIScreen.mainScreen().bounds.size.width
        let screenHeight = UIScreen.mainScreen().bounds.size.height

        // Make the destination view the size of the screen
        destinationViewControllerView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)

            // Insert destination below the source
            // Without this line the animation works but the transition is not smooth as it jumps from white to the new view controller
            destinationViewControllerView.alpha = 0;
            sourceViewControllerView.addSubview(destinationViewControllerView);
            // Animate the fade, remove the destination view on completion and present the full view controller
            UIView.animateWithDuration(10, animations: {
                destinationViewControllerView.alpha = 1;
                }, completion: { (finished) in
                    destinationViewControllerView.removeFromSuperview()
                    self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: nil)
            })
        }
    }

I Hope this help you

Reinier Melian
  • 20,519
  • 3
  • 38
  • 55
  • 1
    I got an error "Unbalanced calls to begin/end appearance transitions" with the above answer. The solution was to not remove the destination view in the completion closure. – Johan Jun 12 '18 at 14:53
4

The slightly modified Swift 4.1 compatible solution based on the answer from @reinier-melian

/// Performs a simple fade between source and destination view controller.
class Fade: UIStoryboardSegue {

    override func perform() {

        guard let destinationView = self.destination.view else {
            // Fallback to no fading
            self.source.present(self.destination, animated: false, completion: nil)
            return
        }

        destinationView.alpha = 0
        self.source.view?.addSubview(destinationView)

        UIView.animate(withDuration: CATransaction.animationDuration(), animations: {
            destinationView.alpha = 1
        }, completion: { _ in
            self.source.present(self.destination, animated: false, completion: nil)
        })
    }
}
Johan
  • 2,472
  • 1
  • 23
  • 25
1

This is what I ended up doing for my purposes. It puts a placeholder view with basic ui elements over the top once all the on screen things have animated out and fades in the placeholder, then presents the destination view controller and removes the placeholder. The destination view controller then animates in all its elements, essentially making the transition invisible and looks like one single animation:

import UIKit

class FadeSegue: UIStoryboardSegue {

    var placeholderView: UIViewController?

    override func perform() {
        let screenWidth = UIScreen.mainScreen().bounds.size.width
        let screenHeight = UIScreen.mainScreen().bounds.size.height

        if let placeholder = placeholderView {
            placeholder.view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)

            placeholder.view.alpha = 0
            sourceViewController.view.addSubview(placeholder.view)

            UIView.animateWithDuration(0.4, animations: {
                placeholder.view.alpha = 1
                }, completion: { (finished) in
                    self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: {
                        placeholder.view.removeFromSuperview()
                    })
            })
        } else {
            self.destinationViewController.view.alpha = 0.0

            self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: {
                UIView.animateWithDuration(0.4, animations: {
                    self.destinationViewController.view.alpha = 1.0
                })
            })
        }
    }
}

and the unwind:

import UIKit

class FadeSegueUnwind: UIStoryboardSegue {

    var placeholderView: UIViewController?

    override func perform() {
        let screenWidth = UIScreen.mainScreen().bounds.size.width
        let screenHeight = UIScreen.mainScreen().bounds.size.height

        if let placeholder = placeholderView {
            placeholder.view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)

            placeholder.view.alpha = 0
            sourceViewController.view.addSubview(placeholder.view)
            UIView.animateWithDuration(0.4, animations: {
                placeholder.view.alpha = 1
                }, completion: { (finished) in
                    self.sourceViewController.dismissViewControllerAnimated(false, completion: nil)
            })
        } else {
            self.destinationViewController.view.alpha = 0.0

            self.sourceViewController.dismissViewControllerAnimated(false, completion: { 
                UIView.animateWithDuration(0.4, animations: { 
                    self.destinationViewController.view.alpha = 0.0
                })
            })
        }
    }
}
Chris Byatt
  • 3,689
  • 3
  • 34
  • 61