-
CollectionView Cell 이동 시키기 (1) - 기존 방식과 custom InteractiveMovementiOS 2022. 6. 9. 20:13
보통 CollectoinView의 cell을 이동하기 위해선 gesture을 붙이고 cell의 location을 이용하여 위치를 바꾼 후 해당 model의 데이터를 스왑해준다.
@objc func handleLongPressGesture() { let location = longPress.location(in: collectionView) switch longPress.state { case .began: if let indexPath = collectionView.indexPathForItem(at: location) { collectionView.beginInteractiveMovementForItem(at: indexPath) } case .changed: collectionView.updateInteractiveMovementTargetPosition(location) case .ended: collectionView.endInteractiveMovement() default: collectionView.cancelInteractiveMovement() } }
이때 CollectionView에서 제공 해주는 InteractiveMovement 함수를 사용하여 location을 전달하고 이에 따른 Interaction을 기대한다.
위와 같은 방식을 사용하면 필연적으로 드는 의문은, "왜 location이 이동할 때 cell의 center가 중심이 되지?" 이었다.
cell을 이동 할 때 cell의 center로 location 을 고정하여 이동한다. 위와 같은 문제를 해결하기 위해 InteractiveMovement를 custom하여 사용해보자.
Custom 함수를 이용하여 cell 이동 시키기
gestureRecognizer를 붙이고 state에 따라 상황에 맞는 함수를 이용하여 cell을 이동시켜보자.
@objc func handleLongPress() { longPressGestureRecognizer.minimumPressDuration = 0.2 let location = longPressGestureRecognizer.location(in: collectionView) switch longPressGestureRecognizer.state { case .began: if collectionView.indexPathForItem(at: location) != nil { startDragAtLocation(location: location) } case .changed: updateDragAtLocation(location: location) case .ended: endDragAtLocation(location: location) default: endDragAtLocation(location: location) } }
각 state에 맞게 start - update - end로 나뉜다.
StartDragAtLocation(location: CGPoint)
func startDragAtLocation(location: CGPoint) { guard let indexPath = collectionView.indexPathForItem(at: location) else { return } guard collectionView.dataSource!.collectionView!(collectionView, canMoveItemAt: indexPath) == true else { return } guard let cell = collectionView.cellForItem(at: indexPath) else { return } originalIndexPath = indexPath draggingView = cell.snapshotView(afterScreenUpdates: true) draggingView!.frame = cell.frame collectionView.addSubview(draggingView!) dragOffset = CGPoint(x: cell.center.x - location.x, y: cell.center.y - location.y) cell.isHidden = true collectionView.collectionViewLayout.invalidateLayout() UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0, options: [], animations: { self.draggingView!.transform = CGAffineTransform(scaleX: 1.2, y: 1.2) self.collectionView.collectionViewLayout.invalidateLayout() }) UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0, options: [], animations: { self.draggingView!.transform = CGAffineTransform.identity self.collectionView.collectionViewLayout.invalidateLayout() }) }
originIndexPath를 전역으로 선언후 할당해준다.
핵심은 cell을 그대로 옮기는것이 아닌 cell을 복사한 draggingView를 생성하여 collectionView에 붙히고 이동 시키는 것이다.
"왜 직접 cell을 옮기지 않지?" 라는 의문이 들 수도 있다. 이는 updateDragAtLocation() 파트에서 설명하겠다.
dragOffset을 통해 현재 사용자의 touch 위치를 저장하는 것이 이 customInteractiveMovement를 만드는 이유이다. 이 값을 통해 cell의 center가 아닌 현재 사용자의 touch 위치를 기억하고 이를 중심으로 cell을 이동시킨다.
UpdateDragAtLocation(location: CGPoint)
func updateDragAtLocation(location: CGPoint) { guard draggingView != nil else { return } draggingView!.center = CGPoint(x: location.x + dragOffset.x, y: location.y + dragOffset.y) if let newIndexPath = collectionView.indexPathForItem(at: location) { collectionView.moveItem(at: originalIndexPath!, to: newIndexPath) originalIndexPath = newIndexPath } }
if문 안에서 실행되는 moveItem(at:, to:) 함수는 cell의 위치를 이동 시켜주는 함수이다.
cell을 직접 이동시에 location이 indexpath가 인지할 수 있는 범위 내에 들어온다면 위의 moveItem이 실행 되고 Animation 중복이 일어난다.
EndDragAtLoction(location: CGPoint)
func endDragAtLocation(location: CGPoint) { guard let indexPath = originalIndexPath else { return } let cv = collectionView guard let datasource = cv.dataSource else { return } if indexPath != (self.originalIndexPath!) { datasource.collectionView?(cv, moveItemAt: self.originalIndexPath!, to: indexPath) } if let newIndexPath = collectionView.indexPathForItem(at: location) { let cell = collectionView.cellForItem(at: newIndexPath) cell!.isHidden = false } else { let cell = collectionView.cellForItem(at: originalIndexPath!) cell!.isHidden = false } draggingView!.removeFromSuperview() self.draggingView = nil collectionView.collectionViewLayout.invalidateLayout() }
마지막으로 draggingView 메모리를 해제 해주어서 새로운 cell에 이동에 대비해준다.
'iOS' 카테고리의 다른 글
AVAudioFile과 AVAudioBuffer (0) 2022.06.30 CollectionView Cell 이동 시키기 (2) - UICollectionView Drag and Drop Delegate (0) 2022.06.10 WWDC) Animation hitch와 render loop (0) 2022.04.05 Cocoapods) custom 라이브러리에 이미지 파일 embedded 하기 (0) 2021.08.04 iOS) Dynamic TextView의 Underline - TextView에 밑줄 추가하기 (0) 2021.03.05