ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CollectionView Cell 이동 시키기 (1) - 기존 방식과 custom InteractiveMovement
    iOS 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에 이동에 대비해준다.

    댓글

Designed by Tistory.