'Drag & Drop Table view cell has weired buggy animation while dragging cell (Using long press gesture)

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



Solution 1:[1]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Shlykov Danylo