0

I'm following this to perform drag and drop table view cell but I don't know why I have buggy weird animation while dragging cell to other position in the table. I don't know what actually causes this type of issue, any help or suggestion would be appreciated. You can check the code and result below:

Code:

var longGesture: UILongPressGestureRecognizer!
var scrollRate: CGFloat = 0
var dragInitialIndexPath: IndexPath?
var dragCellSnapshot: UIView?
var scrollDisplayLink: CADisplayLink?

self.longGesture = UILongPressGestureRecognizer(target: self, action: #selector(onLongPressGesture(sender:)))
self.longGesture.minimumPressDuration = 0.3
self.tblEditProject.addGestureRecognizer(self.longGesture)

 //MARK: tableCell reorder / long press
 @objc func onLongPressGesture(sender: UILongPressGestureRecognizer) {
 self.longGesture = sender
 let state = sender.state
 let locationInView = self.longGesture.location(in: self.tblEditProject)
 let indexPath = self.tblEditProject.indexPathForRow(at: locationInView)
    
    switch state {
    case .began:
        if indexPath != nil {
            guard let currentIndexPath = indexPath else { return }
            dragInitialIndexPath = currentIndexPath
            
            self.scrollDisplayLink = CADisplayLink(target: self, selector: #selector(scrollTableWithCell))
            self.scrollDisplayLink?.add(to: .main, forMode: .default)
           
            guard let cell = self.tblEditProject.cellForRow(at: currentIndexPath) else { return }
            dragCellSnapshot = self.snapshotOfCell(inputView: cell)
            var center = cell.center
            dragCellSnapshot?.center = center
            dragCellSnapshot?.alpha = 0.0
            self.tblEditProject.addSubview(dragCellSnapshot!)
            
            UIView.animate(withDuration: 0.25, animations: { () -> Void in
                center.y = locationInView.y
                self.dragCellSnapshot?.center = center
                self.dragCellSnapshot?.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
                self.dragCellSnapshot?.alpha = 0.98
                cell.alpha = 0.0
            }, completion: { _ in
                cell.isHidden = true
            })
        }
    case .changed:
        if indexPath != nil {
            
            self.calculateScroll(gestureRecognizer: self.longGesture)
            
            var center = dragCellSnapshot?.center
            center?.y = locationInView.y
            dragCellSnapshot?.center = center!
            
            if indexPath != nil && indexPath != dragInitialIndexPath {
                // update your data model
                let dataToMove = self.arrEditProject[0].section[dragInitialIndexPath!.row]
                self.arrEditProject[0].section.remove(at: dragInitialIndexPath!.row)
                self.arrEditProject[0].section.insert(dataToMove, at: indexPath!.row)
                
                self.tblEditProject.moveRow(at: dragInitialIndexPath!, to: indexPath!)
                dragInitialIndexPath = indexPath
            }
        }
        
    case .ended:
        guard let persistedIndexPath = dragInitialIndexPath else { return }
        guard let cell = self.tblEditProject.cellForRow(at: persistedIndexPath) else { return }
        cell.isHidden = false
        cell.alpha = 0.0
        
        UIView.animate(withDuration: 0.25, animations: { () -> Void in
            self.dragCellSnapshot?.center = cell.center
            self.dragCellSnapshot?.transform = CGAffineTransform.identity
            self.dragCellSnapshot?.alpha = 0.0
            cell.alpha = 1.0
        }, completion: { _ in
            self.dragInitialIndexPath = nil
            self.dragCellSnapshot?.removeFromSuperview()
            self.dragCellSnapshot = nil
            self.isUpdateContent = true
            self.tblEditProject.reloadData()
            /// For scrolling while dragging
            self.scrollDisplayLink?.invalidate()
            self.scrollDisplayLink = nil
            self.scrollRate = 0
        })
    default:
        guard let persistedIndexPath = dragInitialIndexPath else { return }
        guard let cell = self.tblEditProject.cellForRow(at: persistedIndexPath) else { return }
        cell.isHidden = false
        cell.alpha = 0.0
        UIView.animate(withDuration: 0.25) {
            self.dragCellSnapshot?.center = cell.center
            self.dragCellSnapshot?.transform = CGAffineTransform.identity
            self.dragCellSnapshot?.alpha = 0.0
            cell.alpha = 1.0
            
        } completion: { _ in
            self.dragInitialIndexPath = nil
            self.dragCellSnapshot?.removeFromSuperview()
            self.dragCellSnapshot = nil
        }
    }
}

func snapshotOfCell(inputView: UIView) -> UIView {
        UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0.0)
        inputView.layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        let cellSnapshot = UIImageView(image: image)
        cellSnapshot.layer.masksToBounds = false
        cellSnapshot.layer.cornerRadius = 0.0
        cellSnapshot.layer.shadowOffset = CGSize(width: -5.0, height: 0.0)
        cellSnapshot.layer.shadowRadius = 5.0
        cellSnapshot.layer.shadowOpacity = 0.4
        return cellSnapshot
    }

func calculateScroll(gestureRecognizer: UIGestureRecognizer) {
    print("Call scroll table with cell")
    let location = gestureRecognizer.location(in: self.tblEditProject)

    var rect: CGRect = self.tblEditProject.bounds
    /// adjust rect for content inset as we will use it below for calculating scroll zones
    rect.size.height -= self.tblEditProject.contentInset.top
    /// tell us if we should scroll and which direction
    let scrollZoneHeight = rect.size.height / 6
    let bottomScrollBeginning = self.tblEditProject.contentOffset.y + self.tblEditProject.contentInset.top + rect.size.height - scrollZoneHeight
    let topScrollBeginning = self.tblEditProject.contentOffset.y + self.tblEditProject.contentInset.top  + scrollZoneHeight

    /// we're in the bottom zone
    if (location.y >= bottomScrollBeginning)
    {
        self.scrollRate = (location.y - bottomScrollBeginning) / scrollZoneHeight
    }
    /// we're in the top zone
    else if (location.y <= topScrollBeginning)
    {
        self.scrollRate = (location.y - topScrollBeginning) / scrollZoneHeight
    }
    else
    {
        self.scrollRate = 0
    }
}

@objc func scrollTableWithCell() {
    print("Call scroll table with cell")
    let gestureLong = self.longGesture
    let location = gestureLong?.location(in: self.tblEditProject)
    
    let currentOffset = self.tblEditProject.contentOffset
    var newOffset = CGPoint(x: currentOffset.x, y: currentOffset.y + self.scrollRate * 10)
    
    if (newOffset.y < -self.tblEditProject.contentInset.top) {
        newOffset.y = -self.tblEditProject.contentInset.top
    } else if (self.tblEditProject.contentSize.height + self.tblEditProject.contentInset.bottom < self.tblEditProject.frame.size.height) {
        newOffset = currentOffset
    } else if (newOffset.y > (self.tblEditProject.contentSize.height + self.tblEditProject.contentInset.bottom) - self.tblEditProject.frame.size.height) {
        newOffset.y = (self.tblEditProject.contentSize.height + self.tblEditProject.contentInset.bottom) - self.tblEditProject.frame.size.height;
    }
    
    self.tblEditProject.setContentOffset(newOffset, animated: false)
    
    if let lction = location {
        if (lction.y >= 0 && lction.y <= self.tblEditProject.contentSize.height + 50) {
            var center = dragCellSnapshot?.center
            center?.y = lction.y
            dragCellSnapshot?.center = center ?? CGPoint.zero
            
            var indexPath = self.tblEditProject.indexPathForRow(at: lction)
            /// Check if the pointer is bigger than the table height to set indexPath as the last cell
            if (self.tblEditProject.contentSize.height < lction.y) {
                indexPath = IndexPath(row: (self.tblEditProject.numberOfRows(inSection: 0)) - 1, section: 0)
            }
            if let pathIndex = indexPath {
                if !pathIndex.isEmpty && pathIndex != dragInitialIndexPath {
                    // update your data model
                    let dataToMove = self.arrEditProject[0].section[dragInitialIndexPath!.row]
                    self.arrEditProject[0].section.remove(at: dragInitialIndexPath!.row)
                    self.arrEditProject[0].section.insert(dataToMove, at: pathIndex.row)
                    
                    self.tblEditProject.moveRow(at: dragInitialIndexPath!, to: pathIndex)
                    dragInitialIndexPath = pathIndex
                }
            }
        }
    }
}

Result:

enter image description here

Kishan Bhatiya
  • 2,175
  • 8
  • 14

1 Answers1

0

Apple's already performed Drag&Drop mechanism for UITableView and UICollectionView (Apple Documentation, How to use it). You don't need to develop it from scratch.