I have a collection view with 9 cells in a 3 x 3 grid. I’m able to swap the cells using the answer code to this question - Custom Cell Reorder Behavior in CollectionView and I’m also able to rotate cells using
if let indexPath = self.collectionView?.indexPathForItem(at: sender.location(in: self.collectionView)) {
let cell = self.collectionView?.cellForItem(at: indexPath)
cell?.transform = (cell?.transform.rotated(by: _direction))!
}
The problem comes in when I rotate a cell then swap it with another cell. The rotation resets and the cell reverts back to its original position (0 degrees) after the swap.
I’d like to be able to rotate cells then swap them without them losing their rotation.
EDIT: Here's my rotation function in View Controller as well as the handleLongGesture function for swapping the cells:
@IBAction func userSwippedRight(_ sender: UISwipeGestureRecognizer) {
rotate(_direction: CGFloat(Double.pi / 2), sender: sender)
}
@IBAction func userSwippedLeft(_ sender: UISwipeGestureRecognizer) {
rotate(_direction: CGFloat(-Double.pi / 2), sender: sender)
}
func rotate(_direction: CGFloat, sender: UISwipeGestureRecognizer ) {
UIView.animate(withDuration: 0.25) {
if let indexPath = self.collectionView?.indexPathForItem(at: sender.location(in: self.collectionView)) {
let cell = self.collectionView?.cellForItem(at: indexPath)
cell?.transform = (cell?.transform.rotated(by: _direction))!
let radians:Float = atan2f(Float(cell!.transform.b), Float(cell!.transform.a))
var degrees:Int = Int(radians * Float(180 / Double.pi))
if(degrees == -180)
{
degrees = 180
}
print(degrees)
} else {
print("Swipe Registered")
}
}
}
func handleLongGesture(_ gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case UIGestureRecognizerState.began:
guard let selectedIndexPath = self.collectionView?.indexPathForItem(at: gesture.location(in: self.collectionView)) else {
break
}
collectionView?.beginInteractiveMovementForItem(at: selectedIndexPath)
case UIGestureRecognizerState.changed:
collectionView?.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case UIGestureRecognizerState.ended:
collectionView?.endInteractiveMovement()
default:
collectionView?.cancelInteractiveMovement()
}
}
Then below is the subclass of the UICollectionView where I added custom handling for the interactive movement of the cells.
import UIKit
extension UIView {
func snapshot() -> UIImage {
UIGraphicsBeginImageContext(self.bounds.size)
self.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
extension CGPoint {
func distanceToPoint(p:CGPoint) -> CGFloat {
return sqrt(pow((p.x - x), 2) + pow((p.y - y), 2))
}
}
struct SwapDescription : Hashable {
var firstItem : Int
var secondItem : Int
var hashValue: Int {
get {
return (firstItem * 10) + secondItem
}
}
}
func ==(lhs: SwapDescription, rhs: SwapDescription) -> Bool {
return lhs.firstItem == rhs.firstItem && lhs.secondItem == rhs.secondItem
}
class SwappingCollectionView: UICollectionView {
var interactiveCell = UICollectionViewCell()
var myView : UICollectionView?
var interactiveIndexPath : NSIndexPath?
var interactiveView : UIView?
var swapSet : Set<SwapDescription> = Set()
var previousPoint : CGPoint?
static let distanceDelta:CGFloat = 2 // adjust as needed
override func beginInteractiveMovementForItem(at indexPath: IndexPath) -> Bool {
self.interactiveIndexPath = indexPath as NSIndexPath?
self.interactiveCell = self.cellForItem(at: indexPath as IndexPath) as! CustomCellCollectionViewCell
self.interactiveCell.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
self.interactiveView = UIImageView(image: self.interactiveCell.snapshot())
self.interactiveView?.frame = self.interactiveCell.frame
self.addSubview(self.interactiveView!)
self.bringSubview(toFront: self.interactiveView!)
self.interactiveCell.isHidden = true
self.interactiveCell.transform = CGAffineTransform.identity
return true
}
override func updateInteractiveMovementTargetPosition(_ targetPosition: CGPoint) {
if (self.shouldSwap(newPoint: targetPosition)) {
if let hoverIndexPath = self.indexPathForItem(at: targetPosition), let interactiveIndexPath = self.interactiveIndexPath {
let swapDescription = SwapDescription(firstItem: interactiveIndexPath.item, secondItem: hoverIndexPath.item)
if (!self.swapSet.contains(swapDescription)) {
self.swapSet.insert(swapDescription)
self.performBatchUpdates({
self.moveItem(at: interactiveIndexPath as IndexPath, to: hoverIndexPath)
self.moveItem(at: hoverIndexPath, to: interactiveIndexPath as IndexPath)
}, completion: {(finished) in
self.swapSet.remove(swapDescription)
self.dataSource?.collectionView!(self, moveItemAt: interactiveIndexPath as IndexPath, to: hoverIndexPath as IndexPath)
self.interactiveIndexPath = hoverIndexPath as NSIndexPath?
})
}
}
}
self.interactiveView?.center = targetPosition
self.previousPoint = targetPosition
}
override func endInteractiveMovement() {
// Save the last rotation
self.cleanup()
}
override func cancelInteractiveMovement() {
self.cleanup()
}
func cleanup() {
self.interactiveCell.isHidden = false
self.interactiveView?.removeFromSuperview()
self.interactiveView = nil
self.interactiveCell.transform = CGAffineTransform.identity
self.interactiveIndexPath = nil
self.previousPoint = nil
self.swapSet.removeAll()
}
func shouldSwap(newPoint: CGPoint) -> Bool {
if let previousPoint = self.previousPoint {
let distance = previousPoint.distanceToPoint(p: newPoint)
return distance < SwappingCollectionView.distanceDelta
}
return false
}
}
My collectionViewCell subclass
import UIKit
import Firebase
import FirebaseDatabase
class CustomCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var imageView: UIImageView!
}