iOS provides a few transitions like Modal, Push and a few more. But if you want to make your app looks different and want to have light weight transitions within your app then you are in for a treat with Custom transitions.In this tutorial we will learn how to build a custom transitions in Swift.

Lets try to implement the same in our How to make a photography inspiration appand Part 2 Swift iOS Tutorial: Taming UITableView Visual Blur and Autolayout.

There are a few things which we need to make custom transitions work.

Our view controller which will begin the transition should implement the UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning

Lets create a DetailViewController which has an imageview within that and setup the autolayout constraints with value 0 to superview on all the sides. If we want to go to a detailViewController on selecting a tableView cell we could implement our tableView didSelectRowAtIndexPath as below

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let dvc = DetailViewController(nibName: "DetailViewController", bundle: nil) dvc.photo = self.photos[indexPath.row] dvc.transitioningDelegate = self dvc.modalPresentationStyle = UIModalPresentationStyle.Custom let currRect = self.tableView.rectForRowAtIndexPath(indexPath) self.point = CGPointMake(currRect.midX, currRect.midY) self.presentViewController(dvc, animated: true, completion: nil) }

The key thing to note here are the setting up of transitioningDelegate to self and the modalPresentationStyle to UIModalPresentationStyle.Custom . Once we have set this up we can present the view controller.

Next we need to implement the functions which will perform the actual transitions.

func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { isPresenting = true return self } func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { isPresenting = false; return self }

Lets maintain a state variable like isPresenting which will be true if we are presenting and false if we are not. Then for both Presenting and Dismiss action we will set the variable appropriately and return ourself( self ). The next two function deals with how much is the transition duration as well as an entry point for us to perform the transitions with animations.

func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { return animationDuration } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let containerView = transitionContext.containerView(); let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) toViewController!.view.frame = fromViewController!.view.frame if(self.isPresenting == true) { toViewController!.view.alpha = 0; toViewController!.view.transform = CGAffineTransformMakeScale(0, 0); UIView.animateWithDuration(animationDuration, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.3, options: nil, animations: { () -> Void in toViewController!.view.alpha = 1; toViewController!.view.transform = CGAffineTransformMakeScale(1, 1); containerView.addSubview(toViewController!.view) }, completion: { (completed) -> Void in transitionContext.completeTransition(completed) }) } else { UIView.animateWithDuration(animationDuration, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.3, options: nil, animations: { () -> Void in fromViewController!.view.alpha = 0; fromViewController!.view.transform = CGAffineTransformMakeScale(0.001, 0.0001); }, completion: { (completed) -> Void in fromViewController?.view.removeFromSuperview() transitionContext.completeTransition(completed) }) } }

Now in the animateTransition we will get a transitionContext which has many necessary objects to help us with performing the transition. It has a containerView which will hold both the fromViewController and the toViewController. As well as the reference to both the controllers. We set the frame of the toViewController to that of the fromViewController.

In the presentiong mode we want the toViewController to fade in with the scaling effect. The code for the same is presented in the isPresenting block. Similarly we remove the view from the superview in the dismiss action.

One key important thing to note here is that in both the actions we need to call transitionContext.completeTransition(completed) otherwise the behaviour is unpredictable.

// // PhotoListController.swift // PicInspire // // Created by Shrikar Archak on 3/14/15. // Copyright (c) 2015 Shrikar Archak. All rights reserved. // import UIKit class PhotoListController: UITableViewController, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning { let manager : PhotoManager = PhotoManager() var photos: [Photo]! var isPresenting: Bool! var point: CGPoint! let animationDuration = 0.3 override func viewDidLoad() { super.viewDidLoad() manager.getPhotos(["tag":"sunset","only":"Nature","image_size":"4","rpp":"100"], completion: { (photos, error) -> () in self.photos = photos self.tableView.reloadData() for photo in photos { println("\(photo.name!)") println("\(photo.lens)") println("\(photo.shutterSpeed)") println("\(photo.camera)") println("\(photo.focalLength)") println("\(photo.iso)") } }) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1; } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let tphotos = self.photos{ return tphotos.count; } return 0; } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("PhotoCell", forIndexPath: indexPath) as PhotoCell cell.posterImageView.image = nil; let photo = self.photos[indexPath.row] cell.camera.text = photo.camera cell.lens.text = photo.lens cell.shutterSpeed.text = photo.shutterSpeed cell.iso.text = photo.iso cell.focalLength.text = photo.focalLength /* AFNetworking extension for loading images async */ cell.posterImageView.setImageWithURL(NSURL(string: photo.imageurl!)); return cell } override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let dvc = DetailViewController(nibName: "DetailViewController", bundle: nil) dvc.photo = self.photos[indexPath.row] dvc.transitioningDelegate = self dvc.modalPresentationStyle = UIModalPresentationStyle.Custom let currRect = self.tableView.rectForRowAtIndexPath(indexPath) self.point = CGPointMake(currRect.midX, currRect.midY) self.presentViewController(dvc, animated: true, completion: nil) } func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { isPresenting = true return self } func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { isPresenting = false; return self } func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { return animationDuration } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let containerView = transitionContext.containerView(); let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) toViewController!.view.frame = fromViewController!.view.frame if(self.isPresenting == true) { toViewController!.view.alpha = 0; toViewController!.view.transform = CGAffineTransformMakeScale(0, 0); UIView.animateWithDuration(animationDuration, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.3, options: nil, animations: { () -> Void in toViewController!.view.alpha = 1; toViewController!.view.transform = CGAffineTransformMakeScale(1, 1); containerView.addSubview(toViewController!.view) }, completion: { (completed) -> Void in transitionContext.completeTransition(completed) }) } else { UIView.animateWithDuration(animationDuration, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.3, options: nil, animations: { () -> Void in fromViewController!.view.alpha = 0; fromViewController!.view.transform = CGAffineTransformMakeScale(0.001, 0.0001); }, completion: { (completed) -> Void in fromViewController?.view.removeFromSuperview() transitionContext.completeTransition(completed) }) } } }

Below is the code for DetailViewController

// // DetailViewController.swift // PicInspire // // Created by Shrikar Archak on 3/14/15. // Copyright (c) 2015 Shrikar Archak. All rights reserved. // import UIKit class DetailViewController: UIViewController { var photo: Photo! @IBOutlet weak var posterImageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() self.posterImageView.setImageWithURL(NSURL(string: photo.imageurl!)) self.posterImageView.contentMode = UIViewContentMode.ScaleAspectFill let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: Selector("handlePan:")) self.posterImageView.addGestureRecognizer(panGestureRecognizer) } func handlePan(sender: UIPanGestureRecognizer){ if(sender.state == UIGestureRecognizerState.Ended){ self.dismissViewControllerAnimated(true , completion: nil); } } override func viewWillAppear(animated: Bool) { } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ }