2
votes

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!


}
2

2 Answers

1
votes

I would advise against manipulating the cell's transform. I would set up a custom subclass of UICollectionViewCell and put all the contents inside a content view and rotate THAT.

I would also give each custom cell a currentRotation property and keep that in sync with the transform's rotation. When you fetch a cell, or dequeue a cell, check it's currentRotation property and use that to see if it needs to be rotated.

0
votes

You should store the rotation state somewhere and apply it at cellForItem(at:). For further help I need more code from your view controller.