Improved Wiggling iOS 9 UICollectionView (Swift)

With iOS 9 UICollectionView now has built-in drag and drop reorder. This is very easy to enable specially when using a UICollectionViewController.

However there is no animation showing that the move operation started or finished, and worse there is no delegate call or notification to be informed of move start/end.

So here is my UICollectionView subclass which adds:

  • Wiggle animation while moving (taken from this library)
  • isWiggling read-only property to check if collection view is currently moving a cell
  • Notifications at start and end of move

Here is the code

//
//  WigglingCollectionView.swift
//
//  Created by Ary Tebeka on 19/07/15.
//  Copyright © 2015 Ary Tebeka. All rights reserved.
//

import UIKit

let WigglingCollectionViewStartedMovingNotification = "WigglingCollectionView.StartedMoving"
let WigglingCollectionViewFinishedMovingNotification = "WigglingCollectionView.FinishedMoving"

class WigglingCollectionView: UICollectionView {

    var isWiggling: Bool { return _isWiggling }
    
    private var _isWiggling = false

    override func beginInteractiveMovementForItemAtIndexPath(indexPath: NSIndexPath) -> Bool {
        startWiggle()
        NSNotificationCenter.defaultCenter().postNotificationName(WigglingCollectionViewStartedMovingNotification, object: self)
        return super.beginInteractiveMovementForItemAtIndexPath(indexPath)
    }
    
    override func endInteractiveMovement() {
        super.endInteractiveMovement()
        stopWiggle()
        NSNotificationCenter.defaultCenter().postNotificationName(WigglingCollectionViewFinishedMovingNotification, object: self)
    }
    
    // Wiggle code below from https://github.com/LiorNn/DragDropCollectionView
    
    private func startWiggle() {
        for cell in self.visibleCells() {
            addWiggleAnimationToCell(cell as UICollectionViewCell)
        }
        _isWiggling = true
    }
    
    private func stopWiggle() {
        for cell in self.visibleCells() {
            cell.layer.removeAllAnimations()
        }
        _isWiggling = false
    }
    
    private func addWiggleAnimationToCell(cell: UICollectionViewCell) {
        CATransaction.begin()
        CATransaction.setDisableActions(false)
        cell.layer.addAnimation(rotationAnimation(), forKey: "rotation")
        cell.layer.addAnimation(bounceAnimation(), forKey: "bounce")
        CATransaction.commit()
    }
    
    private func rotationAnimation() -> CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath: "transform.rotation.z")
        let angle = CGFloat(0.04)
        let duration = NSTimeInterval(0.1)
        let variance = Double(0.025)
        animation.values = [angle, -angle]
        animation.autoreverses = true
        animation.duration = self.randomizeInterval(duration, withVariance: variance)
        animation.repeatCount = Float.infinity
        return animation
    }
    
    private func bounceAnimation() -> CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath: "transform.translation.y")
        let bounce = CGFloat(3.0)
        let duration = NSTimeInterval(0.12)
        let variance = Double(0.025)
        animation.values = [bounce, -bounce]
        animation.autoreverses = true
        animation.duration = self.randomizeInterval(duration, withVariance: variance)
        animation.repeatCount = Float.infinity
        return animation
    }
    
    private func randomizeInterval(interval: NSTimeInterval, withVariance variance:Double) -> NSTimeInterval {
        let random = (Double(arc4random_uniform(1000)) - 500.0) / 500.0
        return interval + variance * random;
    }

}