[UIKit] CollectionViewCell 드래그 앤 드랍 구현하기

2024. 12. 1. 19:53·📱 iOS/UIKit

문제 상황

컬렉션 뷰의 셀들을 드래그 앤 드랍해야 할 일이 생겨서

이에 대한 공부를 하고 정리해보려 한다 !


문제 해결

주요 메소드

우리는 아래의 두 델리게이트 프로토콜을 사용할 것이고,

델리게이트에서 총 3개의 메소드를 사용하여 드래그 앤 드랍을 구현할 거다.

  • UICollectionViewDragDelegate
  • UICollectionViewDropDelegate

시작: collectionView(_:itemsForBeginning:at:)

먼저 Drag 델리게이트의 메소드이다.

처음 드래그가 시작될 때 호출되는 메소드로 말 그대로 “드래그”를 시작할 때 동작한다.

중간: collectionView(_:dropSessionDidUpdate:withDestinationIndexPath:)

Drop 델리게이트의 메소드이다.

드래그 동작 중에 호출되는 메소드로 드래그가 잘 되고 있는지 등에 대한 유효성 검사?를 할 수 있다.

끝: `collectionView(_:performDropWith:)`

Drop 델리게이트의 메소드이다.

해당 메소드는 Drag를 마치고 Drop이 발생했을 때 호출된다.


우리 프로젝트에 적용해보기

이 책 4권을 드래그 앤 드랍으로 이리저리 움직여서 위치를 옮겨봅시다.

1. 드래그 준비

extension HomeViewController: UICollectionViewDragDelegate {
    public func collectionView(
        _ collectionView: UICollectionView,
        itemsForBeginning session: any UIDragSession,
        at indexPath: IndexPath
    ) -> [UIDragItem] {
        let dragItem = UIDragItem(itemProvider: NSItemProvider())
        return [dragItem]
    }
}

Drag 델리게이트를 채택하고 itemsForBeginning 메소드를 구현한다.

UIDragItem을 배열에 담아서 보내주는데, 이는 다른 앱과 상호작용할 때도 쓰일 수 있다고 함..!

그래서 아래처럼 사용 가능하다. 아마 사진 앱도 얘를 사용한 게 아닐까 ?

 

2. 드래그 중

extension HomeViewController: UICollectionViewDropDelegate {
    public func collectionView(
        _ collectionView: UICollectionView,
        dropSessionDidUpdate session: UIDropSession,
        withDestinationIndexPath destinationIndexPath: IndexPath?
    ) -> UICollectionViewDropProposal {
        guard collectionView.hasActiveDrag else { return UICollectionViewDropProposal(operation: .forbidden) }
        return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
    }

드래그 중에는 유효성 검증을 한다.

collectionView.hasActiveDrag을 통해서 컬렉션 뷰가 드래그 중이면 operation 을 move로 주고,

그렇지 않으면 forbidden으로 준다. forbidden으로 처리되면 드래그를 하고 드랍을 했을 때 아무일도 발생하지 않음

이외에도 Cancel, copy가 있다

 

3. 드래그 끝, 드랍 시점

public func collectionView(
    _ collectionView: UICollectionView,
    performDropWith coordinator: UICollectionViewDropCoordinator
) {
    var destinationIndexPath: IndexPath
    if let indexPath = coordinator.destinationIndexPath {
        destinationIndexPath = indexPath
    } else {
        let row = collectionView.numberOfItems(inSection: 0)
        destinationIndexPath = IndexPath(item: row - 1, section: 0)
    }

    moveItems(
        coordinator: coordinator,
        destinationIndexPath: destinationIndexPath,
        collectionView: collectionView
    )
}

private func moveItems(
    coordinator: UICollectionViewDropCoordinator,
    destinationIndexPath: IndexPath,
    collectionView: UICollectionView
) {
    guard
        coordinator.proposal.operation == .move,
        let item = coordinator.items.first,
        let sourceIndexPath = item.sourceIndexPath
    else { return }

    collectionView.performBatchUpdates { [weak self] in
        guard let self else { return }
        input.send(
            .dragAndDropBookCover(
                currentIndex: sourceIndexPath.item,
                destinationIndex: destinationIndexPath.item
            )
        )

        collectionView.deleteItems(at: [sourceIndexPath])
        collectionView.insertItems(at: [destinationIndexPath])
    }
}

드래그가 끝나면 내가 놓은 셀의 위치 destinationIndexPath 를 받아서 해당 위치로 옮겨 주면 된다.

이를 구현하기 위해서 remove로 현재 자신의 위치를 제거한다. 그러면 이때 자신을 기준으로 뒤에있던 요소들은 한 칸씩 앞으로 땡겨질 거고,

destinationIndexPath을 통해서 insert를 해주면 원하는 대로 결과를 받을 수 있다.

나는 이때, viewModel에게 input을 보내어 처리를 하게 하였고, collectionView의 Item 또한 삭제와 삽입을 해주어 처리하였다.

 


배운 점

  • 아래 두 델리게이트 메소드들 덕에 쉽게 드래그 앤 드랍을 구현할 수 있어따
    • UICollectionViewDragDelegate
    • UICollectionViewDropDelegate

참조 링크

https://mini-min-dev.tistory.com/235

https://ios-development.tistory.com/1422

https://koggy.tistory.com/54

 
저작자표시 (새창열림)

'📱 iOS > UIKit' 카테고리의 다른 글

[UIKit] 뷰의 Corner Radius 각각 다르게 처리하기 (with CACornerMask, UIBezierPath, CAShapeLayer)  (0) 2025.02.28
[UIKit] UIMenu 사용기  (1) 2024.12.10
[UIKit] iOS 15.0 이상에서 UIButton 안에 있는 이미지 사이즈 조절하기  (4) 2024.11.15
[UIKit] 런타임 시점에 Constraint를 조절하여 애니메이션 구현하기  (2) 2024.11.14
[UIKit] Cell Identifier 지정할 때, 휴먼 에러를 방지하기  (0) 2024.10.06
'📱 iOS/UIKit' 카테고리의 다른 글
  • [UIKit] 뷰의 Corner Radius 각각 다르게 처리하기 (with CACornerMask, UIBezierPath, CAShapeLayer)
  • [UIKit] UIMenu 사용기
  • [UIKit] iOS 15.0 이상에서 UIButton 안에 있는 이미지 사이즈 조절하기
  • [UIKit] 런타임 시점에 Constraint를 조절하여 애니메이션 구현하기
kyxxn
kyxxn
컴퓨터공학을 좋아하는 대학생의 공부 일기
  • kyxxn
    컴공 학부생의 공부 일기
    kyxxn
  • 전체
    오늘
    어제
    • 분류 전체보기 (156)
      • 📱 iOS (64)
        • Xcode (10)
        • Swift (17)
        • Swift Concurrency (12)
        • UIKit (21)
        • SwiftUI (0)
      • 🖥️ Computer Science (57)
        • 🏛️ Software Architecture Pa.. (2)
        • 👨🏻‍🎨 Design Pattern (3)
        • Data Structure (4)
        • Algorithm (10)
        • Computer Architecture (4)
        • Operating System (19)
        • Network (15)
      • ✍🏻 회고록 (9)
      • 🎸 기타 (25)
        • 해커톤 (1)
        • git (6)
        • 세미나 (1)
        • 책을 읽고 (1)
        • AOS, Kotlin (6)
        • Reinforcement Learning (9)
  • 블로그 메뉴

    • 링크

      • 깃허브
      • 일상 블로그
    • 공지사항

    • 인기 글

    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.0
    kyxxn
    [UIKit] CollectionViewCell 드래그 앤 드랍 구현하기
    상단으로

    티스토리툴바