Xử lý kéo thả cell trên table view
1. Tại sao phải xử lý kéo thả cell trên table view UITableView mặc định của ios đã hỗ trợ việc kéo thả cell, tuy nhiên khi chúng ta muốn tạo thêm hiệu ứng cho việc di chuyển, costume lại giao diện cell khi di chuyển hoặc thêm các hành động khác thì ios chưa hỗ trợ việc này, cho nên để dễ dàng xử ...
1. Tại sao phải xử lý kéo thả cell trên table view
UITableView mặc định của ios đã hỗ trợ việc kéo thả cell, tuy nhiên khi chúng ta muốn tạo thêm hiệu ứng cho việc di chuyển, costume lại giao diện cell khi di chuyển hoặc thêm các hành động khác thì ios chưa hỗ trợ việc này, cho nên để dễ dàng xử lý các việc trên chúng ta phải tiến hành xử lý kéo thả cell trên table view
2. Các bước xử lý kéo thả cell trên table view
2.1 Xây dựng 1 class table view hỗ trợ việc kéo thả
Đầu tiên chúng ta sẽ tiến hành tạo 1 class DragDropTableView kế thừa từ class UITableView
class DragDropTableView: UITableView { private var snapshotView: UIView? private var beginIndexPath: IndexPath? private var endIndexPath: IndexPath? }
trong đó snapshotView sẽ là clone view của cell khi chúng ta ấn giữ, beginIndexPath là indexPath đầu tiên khi chúng ta kéo và endIndexPath sẽ là indexPath chúng ta di chuyển cell đến và nhả tay ra.
Tiếp theo chúng ta sẽ tạo 1 UILongPressGestureRecognizer và gán vào view của viewController chứa tableView
func addGestureToView(mainView: UIView?) { let longpress = UILongPressGestureRecognizer(target: self, action: #selector(longPressGestureRecognized(gestureRecognizer:))) longpress.minimumPressDuration = 0.3 mainView?.addGestureRecognizer(longpress) self.mainView = mainView }
UILongPressGesture sẽ có 3 trạng thái khi chúng ta nhấn giữ, di chuyển và kết thúc khi thả tay, chúng ta sẽ viết 1 hàm để xử lý việc này
@objc private func longPressGestureRecognized(gestureRecognizer: UILongPressGestureRecognizer) { let state = gestureRecognizer.state let locationInView = gestureRecognizer.location(in: self.mainView) switch state { case .began: self.startMovingCell(at: locationInView) break case .changed: self.movingCell(at: locationInView) break case .ended: self.endedMovingCell(at: locationInView) break default: break } }
3. Xử lý các hàm kéo của UILongPressGestureRecognizer
3.1 Hàm startMovingCell
Hàm này được gọi ngay khi chúng ta nhấn vào cell và giữ 1 thời gian đã setup trước
private func startMovingCell(at position: CGPoint) { guard let visibleCell = self.visibleCell(at: position) else { return } guard let indexPath = self.indexPath(for: visibleCell) else { return } let canMove = self.dataSource?.tableView?(self, canMoveRowAt: indexPath) self.refreshCell(at: indexPath) if canMove == true { self.endIndexPath = indexPath self.beginIndexPath = indexPath self.snapshotView = snapshopOfCell(inputView: visibleCell) self.snapshotView?.alpha = 0.0 if let snapshotView = self.snapshotView { self.mainView?.addSubview(snapshotView) } UIView.animate(withDuration: 0.3, animations: { () -> Void in let scale = CGAffineTransform.identity.scaledBy(x: 1.05, y: 1.05) let rotate = CGAffineTransform.identity.rotated(by: .pi / 36) self.snapshotView?.transform = scale.concatenating(rotate) self.snapshotView?.alpha = 0.8 visibleCell.alpha = 0.0 }, completion: { (finished) -> Void in if finished { visibleCell.isHidden = true } }) } }
trong hàm này chúng ta phải xử lý 1 số việc như:
- gán lại các indexPath
- tạo snapshot cho cell chúng ta đang kích (nếu có)
- tạo animation cho snapshot (nếu muốn)
- ẩn cell cũ đi
- add snapshotView vào viewController hiện tại
3.2 Hàm movingCell
Hàm này được gọi khi chúng ta di chuyển cell trong viewController
private func movingCell(at position: CGPoint) { if let snapshotView = self.snapshotView { var center = snapshotView.center center.y += self.translationY(position) self.lastLocation = position snapshotView.center = center self.isMoving = true } guard let visibleCell = self.visibleCell(at: position) else { return } guard let indexPath = self.indexPath(for: visibleCell) else { return } let canMove = self.dataSource?.tableView?(self, canMoveRowAt: indexPath) if canMove == true { if let endIndexPath = self.endIndexPath, indexPath != endIndexPath { let cell = self.cellForRow(at: endIndexPath) cell?.isHidden = true self.dataSource?.tableView?(self, moveRowAt: endIndexPath, to: indexPath) if endIndexPath.section != indexPath.section { self.beginUpdates() self.deleteRows(at: [endIndexPath], with: .none) self.insertRows(at: [indexPath], with: .top) self.endUpdates() } else { self.moveRow(at: endIndexPath, to: indexPath) } self.endIndexPath = indexPath } } }
Trong hàm này chúng ta phải xử lý các việc như sau:
- Update lại vị trí của snapshotView trong viewController
- Kiểm tra xem vị trí mà chúng ta move đến có tồn tại cell nào không nếu không có thì break luôn ko xử lý gì thêm nữa
- Tiếp đó chúng ta kiểm tra xem cell tại indexPath mới có thể di chuyển đc hay không, nếu ko thể di chuyển thì không xử lý thêm gì nữa
- Khi kiểm tra cell ở vị trí mới đã có thể di chuyển, chúng ta mới tiến hành xử lý tiếp, nếu indexPath tại vị trí mới không trùng với vị trí cũ (hay nói nôm na là move sang cell mới) thì tiến hành update lại tableView
- Khi update tableView thì chúng ta remove cell tại indexPath cũ và insert cell tại indexPath mới
3.3 Hàm endedMovingCell
Hàm này được gọi khi chúng ta thả ngón tay ra
private func endedMovingCell(at position: CGPoint) { self.isMoving = false self.lastLocation = nil guard let endIndexPath = self.endIndexPath else { self.removeData() return } guard let cell = self.cellForRow(at: endIndexPath) else { self.removeData() return } cell.alpha = 0.0 cell.isHidden = false UIView.animate(withDuration: 0.4, animations: { () -> Void in var center = self.snapshotView?.center let rect = cell.convert(cell.bounds, to: self.mainView) center?.y = rect.origin.y + rect.height / 2 if let ct = center { self.snapshotView?.center = ct } self.snapshotView?.transform = CGAffineTransform.identity }, completion: { (finished) -> Void in if finished { self.reloadData() cell.alpha = 1.0 self.snapshotView?.alpha = 0.0 self.removeData() } }) }
Trong hàm này chúng ta cần xử lý các việc sau:
- kiểm tra xem cell tại vị trí kết thúc có cell ko, nếu ko có thì break luôn, ko xử lý gì thêm nữa
- Nếu tồn tại cell tại vị trí kết thúc thì chúng ta có thể tạo animation cho việc kết thúc này
- Update lại vị trí của snapshotView tại center của cell cuối cùng
- Xoá snapshotView đồng thời hiện tại cell mà chúng ta đã ẩn trước đó.