How do you create a UICollectionView Transition Animation in Swift without Storyboards?
Asked Answered
C

0

8

I'm trying to achieve literally this effect shown on Facebook's photo collectionview:

Video: http://d.pr/v/YCDB

My goal is:

  • Animating to and from the indexpath of the cell
  • Pan up and down to fade the view out and dismiss it
  • Pretty much what's in the video.


I've got the pan to fade out sort of working, but the animations are poor at best.

Zip to XCodeProject:

http://d.pr/f/19uw5


Or take a look at the code below

My CollectionView:

import UIKit

class Feed: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

var collectionView: UICollectionView?
var myArray = ["cool", "neat", "fun", "cool", "neat", "fun", "cool", "neat", "fun", "cool", "neat", "fun", "cool", "neat", "fun", "cool", "neat", "fun", "cool", "neat", "fun", "cool", "neat", "fun"]


override func viewDidLoad() {
    super.viewDidLoad()

    self.view.backgroundColor = UIColor.yellowColor()
    self.title = "Feed"

    let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
    layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    layout.itemSize = CGSize(width: 100, height: 100)

    collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
    collectionView!.dataSource = self
    collectionView!.delegate = self
    collectionView!.registerClass(Cell.self, forCellWithReuseIdentifier: "Cell")
    collectionView!.backgroundColor = UIColor.whiteColor()

    self.view.addSubview(collectionView!)

}


func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return myArray.count
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as Cell
    cell.textLabel.text = myArray[indexPath.row]
    return cell

}

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as Cell

    cell.textLabel.text = myArray[indexPath.row]

    let vc:Detail = Detail()
    let navController:CustomNavigationController = CustomNavigationController(rootViewController: vc)

    println(cell.textLabel.text)
    vc.myLabel.text = cell.textLabel.text
    presentViewController(navController, animated: true, completion: nil)
}

}

My Transition

import UIKit

@objc protocol CustomNavigationControllerDelegate {
    func pushToNextScene()
}

class CustomNavigationController: UINavigationController, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        transitioningDelegate = self   // for presenting the original navigation controller
        delegate = self                // for navigation controller custom transitions

        let top = UIPanGestureRecognizer(target: self, action: "handleSwipeFromTop:")
        self.view.addGestureRecognizer(top);

    }

    var interactionController: UIPercentDrivenInteractiveTransition?

    func handleSwipeFromTop(gesture: UIPanGestureRecognizer) {

        let percent = gesture.translationInView(gesture.view!).y / gesture.view!.bounds.size.height

        if gesture.state == .Began {
            interactionController = UIPercentDrivenInteractiveTransition()
            if viewControllers.count > 1 {
                popViewControllerAnimated(true)
            } else {
                dismissViewControllerAnimated(true, completion: nil)
            }
        } else if gesture.state == .Changed {
            interactionController?.updateInteractiveTransition(percent)
        } else if gesture.state == .Ended {
            if percent > 0.2 {
                interactionController?.finishInteractiveTransition()
            } else {
                interactionController?.cancelInteractiveTransition()
            }
            interactionController = nil
        }
    }



    // MARK: - UIViewControllerTransitioningDelegate

    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return ForwardAnimator()
    }

    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return BackAnimator()
    }

    func interactionControllerForPresentation(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactionController
    }

    func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactionController
    }

    // MARK: - UINavigationControllerDelegate

    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {

        if operation == .Push {
            return ForwardAnimator()
        } else if operation == .Pop {
            return BackAnimator()
        }
        return nil
    }

    func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactionController
    }

}

class ForwardAnimator : NSObject, UIViewControllerAnimatedTransitioning {

    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return 0.3
    }

    func animateTransition(context: UIViewControllerContextTransitioning) {
        let toView = context.viewControllerForKey(UITransitionContextToViewControllerKey)?.view
        let fromView = context.viewControllerForKey(UITransitionContextFromViewControllerKey)?.view

        context.containerView().addSubview(toView!)

        toView?.alpha = 0.0

        UIView.animateWithDuration(transitionDuration(context), animations: {
            toView?.alpha = 1.0
            return
            }, completion: { finished in
                context.completeTransition(!context.transitionWasCancelled())
        })
    }

}

class BackAnimator : NSObject, UIViewControllerAnimatedTransitioning {

    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return 0.2
    }

    func animateTransition(context: UIViewControllerContextTransitioning) {
        let toView = context.viewControllerForKey(UITransitionContextToViewControllerKey)?.view
        let fromView = context.viewControllerForKey(UITransitionContextFromViewControllerKey)?.view

        context.containerView().insertSubview(toView!, belowSubview: fromView!)

        UIView.animateWithDuration(transitionDuration(context), animations: {
            fromView?.alpha = 0.0
            return
            }, completion: { finished in
                context.completeTransition(!context.transitionWasCancelled())
        })
    }
}

And if you want it/ease to recreate:

My Cell

import UIKit

class Cell: UICollectionViewCell {

    let textLabel: UILabel!

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.backgroundColor = UIColor.greenColor()

        let textFrame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)
        textLabel = UILabel(frame: textFrame)
        textLabel.font = UIFont.systemFontOfSize(UIFont.smallSystemFontSize())
        textLabel.textAlignment = .Center
        contentView.addSubview(textLabel)


    }

}

My Detail View

import UIKit

class Detail: UIViewController, CustomNavigationControllerDelegate {

    var myLabel : UILabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor(white: 0, alpha: 0.7)

        let myBtn: UIButton = UIButton()
        myBtn.frame = CGRectMake(100, 100, 100, 100)
        myBtn.backgroundColor = UIColor.greenColor()
        myBtn.addTarget(self, action: "goNext", forControlEvents: UIControlEvents.TouchUpInside)
        self.view.addSubview(myBtn)

        myLabel.textColor = UIColor.whiteColor()
        myLabel.frame = self.view.bounds
        self.view.addSubview(myLabel)

    }

    func pushToNextScene() {
        self.dismissViewControllerAnimated(true, completion: nil)
    }

    func goNext () {
        println("button")
        self.dismissViewControllerAnimated(true, completion: nil)
    }
}
Clovis answered 26/1, 2015 at 0:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.