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