[英]Custom Cell Reorder Behavior in CollectionView
我設法通過創建UICollectionView
的子類並向交互式移動添加自定義處理來實現此UICollectionView
。 在查看如何解決問題的可能提示時,我發現了本教程: http : //nshint.io/blog/2015/07/16/uicollectionviews-now-have-easy-reordering/ 。 最重要的部分是交互式重新排序不僅可以在UICollectionViewController
上UICollectionViewController
。 相關代碼如下所示:
var longPressGesture : UILongPressGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
// rest of setup
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.handleLongGesture(_:)))
self.collectionView?.addGestureRecognizer(longPressGesture)
}
func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case UIGestureRecognizerState.Began:
guard let selectedIndexPath = self.collectionView?.indexPathForItemAtPoint(gesture.locationInView(self.collectionView)) else {
break
}
collectionView?.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)
case UIGestureRecognizerState.Changed:
collectionView?.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!))
case UIGestureRecognizerState.Ended:
collectionView?.endInteractiveMovement()
default:
collectionView?.cancelInteractiveMovement()
}
}
這需要位於放置集合視圖的視圖控制器中。 我不知道這是否適用於UICollectionViewController
,可能還需要一些額外的修補。 是什么導致我UICollectionView
,實現所有其他相關的類/委托方法只被告知第一個和最后一個索引路徑(即源和目標),並且沒有關於重新排列的所有其他單元格的信息,所以它需要停在核心位置。
SwappingCollectionView.swift :
import UIKit
extension UIView {
func snapshot() -> UIImage {
UIGraphicsBeginImageContext(self.bounds.size)
self.layer.renderInContext(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 interactiveIndexPath : NSIndexPath?
var interactiveView : UIView?
var interactiveCell : UICollectionViewCell?
var swapSet : Set<SwapDescription> = Set()
var previousPoint : CGPoint?
static let distanceDelta:CGFloat = 2 // adjust as needed
override func beginInteractiveMovementForItemAtIndexPath(indexPath: NSIndexPath) -> Bool {
self.interactiveIndexPath = indexPath
self.interactiveCell = self.cellForItemAtIndexPath(indexPath)
self.interactiveView = UIImageView(image: self.interactiveCell?.snapshot())
self.interactiveView?.frame = self.interactiveCell!.frame
self.addSubview(self.interactiveView!)
self.bringSubviewToFront(self.interactiveView!)
self.interactiveCell?.hidden = true
return true
}
override func updateInteractiveMovementTargetPosition(targetPosition: CGPoint) {
if (self.shouldSwap(targetPosition)) {
if let hoverIndexPath = self.indexPathForItemAtPoint(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.moveItemAtIndexPath(interactiveIndexPath, toIndexPath: hoverIndexPath)
self.moveItemAtIndexPath(hoverIndexPath, toIndexPath: interactiveIndexPath)
}, completion: {(finished) in
self.swapSet.remove(swapDescription)
self.dataSource?.collectionView(self, moveItemAtIndexPath: interactiveIndexPath, toIndexPath: hoverIndexPath)
self.interactiveIndexPath = hoverIndexPath
})
}
}
}
self.interactiveView?.center = targetPosition
self.previousPoint = targetPosition
}
override func endInteractiveMovement() {
self.cleanup()
}
override func cancelInteractiveMovement() {
self.cleanup()
}
func cleanup() {
self.interactiveCell?.hidden = false
self.interactiveView?.removeFromSuperview()
self.interactiveView = nil
self.interactiveCell = nil
self.interactiveIndexPath = nil
self.previousPoint = nil
self.swapSet.removeAll()
}
func shouldSwap(newPoint: CGPoint) -> Bool {
if let previousPoint = self.previousPoint {
let distance = previousPoint.distanceToPoint(newPoint)
return distance < SwappingCollectionView.distanceDelta
}
return false
}
}
我確實意識到那里有很多事情,但我希望一切都會在一分鍾內清楚。
UIView
以獲取單元格的快照。 CGPoint
以計算兩點之間的距離。 SwapDescription
幫助程序結構 - 需要防止同一對項目的多次交換,這會導致錯誤的動畫。 它的hashValue
方法可以改進,但是為了這個概念證明就足夠了。 beginInteractiveMovementForItemAtIndexPath(indexPath: NSIndexPath) -> Bool
- 移動開始時調用的方法。 一切都在這里設置。 我們獲取單元格的快照並將其添加為子視圖 - 此快照將是用戶實際在屏幕上拖動的內容。 細胞本身被隱藏起來。 如果從此方法返回false
,則不會開始交互式移動。 updateInteractiveMovementTargetPosition(targetPosition: CGPoint)
- 每次用戶移動后調用的方法,這很多。 我們檢查前一點的距離是否足夠小以交換項目 - 這可以防止用戶在屏幕上快速拖動並且多個項目與非顯而易見的結果交換時出現問題。 如果交換可能發生,我們檢查它是否已經發生,如果不是,我們交換兩個項目。 endInteractiveMovement()
, cancelInteractiveMovement()
, cleanup()
- 在移動結束后,我們需要將助手恢復到默認狀態。 shouldSwap(newPoint: CGPoint) -> Bool
- 幫助方法檢查運動是否足夠小,以便我們可以交換單元格。 這是它給出的結果:
請告訴我這是否是您需要的和/或您是否需要我澄清一些事情。
這是一個演示項目 。
@Losiowaty解決方案的Swift 5解決方案:
var longPressGesture : UILongPressGestureRecognizer!
override func viewDidLoad()
{
super.viewDidLoad()
// rest of setup
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongGesture))
self.collectionView?.addGestureRecognizer(longPressGesture)
}
@objc 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()
}
}
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 interactiveIndexPath : IndexPath?
var interactiveView : UIView?
var interactiveCell : UICollectionViewCell?
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
self.interactiveCell = self.cellForItem(at: indexPath)
self.interactiveView = UIImageView(image: self.interactiveCell?.snapshot())
self.interactiveView?.frame = self.interactiveCell!.frame
self.addSubview(self.interactiveView!)
self.bringSubviewToFront(self.interactiveView!)
self.interactiveCell?.isHidden = true
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)
}, completion: {(finished) in
self.swapSet.remove(swapDescription)
self.dataSource?.collectionView?(self, moveItemAt: interactiveIndexPath, to: hoverIndexPath)
self.interactiveIndexPath = hoverIndexPath
})
}
}
}
self.interactiveView?.center = targetPosition
self.previousPoint = targetPosition
}
override func endInteractiveMovement() {
self.cleanup()
}
override func cancelInteractiveMovement() {
self.cleanup()
}
func cleanup() {
self.interactiveCell?.isHidden = false
self.interactiveView?.removeFromSuperview()
self.interactiveView = nil
self.interactiveCell = nil
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
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.