일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- discardableResult
- SOPT
- Swift
- BFS
- Til
- dfs
- SwiftUI 튜토리얼
- 0이끝이아니길
- 동적계획법
- 다이나믹프로그래밍
- APPJAM
- IOS
- duno
- 고득점kit
- concurrency
- 연속펄스부분수열의합
- 이진탐색
- HAVIT
- 프로그래머스
- binarySearch
- algoritm
- GCD
- URLSession
- SwiftUI Tutorials
- SQL
- DynamicProgramming
- 기초문법
- GroupBy
- algorithm
- SwiftUI
- Today
- Total
suvera-dev 🥦
iOS) CollectionView Cell - Drag & Drop 본문
CollectionViewCell의 Drag & Drop 기능을 구현하여 Cell의 위치를 변경하는 방법을 알아보겠습니다 🍎
열심히 구글링을 한 결과 2가지 방법이 있었습니다 🧐
1. UICollectionViewDragDelegate , UICollectionViewDropDelegate 사용
2. LongPressGesture를 추가
어떤 방법으로 할까 하다가 저는 2가지 방법 모두 해봤습니다 !
1. Drag & Drop Delegate
collectionView.dragDelegate = self
collectionView.dropDelegate = self
- DragDelegate
extension ViewController: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
return [UIDragItem(itemProvider: NSItemProvider())]
}
}
- DropDelegate : CollectionView의 performBatchUpdates를 사용하여 순서 변경 구현
extension ViewController: UICollectionViewDropDelegate {
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
if session.localDragSession != nil {
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
return UICollectionViewDropProposal(operation: .cancel, intent: .unspecified)
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
guard let destinationIndexPath = coordinator.destinationIndexPath else {
return
}
coordinator.items.forEach { dropItem in
guard let sourceIndexPath = dropItem.sourceIndexPath else { return }
let categoryCell = self.categoryList[sourceIndexPath.row]
collectionView.performBatchUpdates({
// reorder data list
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
self.categoryList.remove(at: sourceIndexPath.row)
self.categoryList.insert(categoryCell, at: destinationIndexPath.row)
}, completion: { _ in
coordinator.drop(dropItem.dragItem, toItemAt: destinationIndexPath)
})
}
}
- CollectionViewDataSource
1) canHandle은 true로 설정
2) moveItemAt 에서 move를 시작한 인덱스(sourceIndex)에 해당하는 셀을 저장한 뒤 기존 List에서 제거해주고, 저장해둔 셀은 move해서 도착한 인덱스(destintaionIndex)에 추가 해준다.
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let categoryCell = self.categoryList[sourceIndexPath.row]
self.categoryList.remove(at: sourceIndexPath.row)
self.categoryList.insert(categoryCell, at: destinationIndexPath.row)
}
아래 사이트를 참고하였습니다 !
2. LongPressGesture 추가
- LongPressGestureRecognizer를 추가
private func setGesture() {
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
categoryCollectionView.addGestureRecognizer(longPressRecognizer)
}
- 제스처가 인식되었을 때 실행할 메서드 handleLongPressGesture 정의
@objc
private func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
let startIndexPath = categoryCollectionView.indexPathForItem(at: gesture.location(in: categoryCollectionView))
let cell = cellForItemAt(at: startIndexPath)
switch gesture.state {
case .began:
guard let startIndexPath = startIndexPath else {
break
}
cell?.backgroundColor = .purple002
categoryCollectionView.beginInteractiveMovementForItem(at: startIndexPath)
case .changed:
categoryCollectionView.updateInteractiveMovementTargetPosition(gesture.location(in: categoryCollectionView))
case .ended:
cell?.backgroundColor = .purpleCategory
categoryCollectionView.endInteractiveMovement()
default:
categoryCollectionView.cancelInteractiveMovement()
}
}
private func cellForItemAt(at indexPath: IndexPath?) -> UICollectionViewCell? {
guard let indexPath = indexPath else {
return nil
}
return categoryCollectionView.cellForItem(at: indexPath)
}
1) 제스처가 일어난 셀의 인덱스를 시작 인덱스로 설정
2) 제스처의 state에 따라 cell의 배경색 및 interactiveMovement 설정
- CollectionView DataSource
1) canMoveItemAt : true
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
2) moveItemAt : Drag를 시작한 인덱스(sourceIndexPath)에 해당하는 아이템을 기존 List에서 제거하고, Drop을 해서 도착한 인덱스 (destinationIndexPath)에 해당하는 아이템을 기존 List에 추가 (위의 delegate 와 방법 동일 !)
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let cell = categoryCollectionView.cellForItem(at: destinationIndexPath)
cell?.backgroundColor = .purpleCategory
let categoryItem = categories.remove(at: sourceIndexPath.row)
categories.insert(categoryItem, at: destinationIndexPath.row)
}
제가 했던 프로젝트에서는 결론적으로 2번을 사용했는데,
직접 여러가지를 테스트해본 결과 각각의 장단점이 존재하였습니다 🥸
1번째 방법은 기본으로 내장되어있는 효과를 더 쉽게 사용가능하는 점이 장점이 었습니다.
또한, 드래그 앤 드랍 인터랙션이 조금 더 자연스러웠습니다 ! 또한, 다른 제스처를 추가했을 경우 겹칠 위험이 없습니다.
하지만, 드래그를 시작하였을 때 배경색을 바꾸는 방법을 찾지 못해 2번 방법으로 구현하게 되었습니다 !
2번째 방법은 제스처 변화 상태에 맞게 배경색을 커스텀 할수 있다는 장점이 있었습니다.
하지만, 드래그를하면서 양쪽 옆으로 과하게 벗어날 경우 indexPath가 nil로 감지되어 셀이 움직이다가 이상한 위치에서 멈추는 버그가 발생하였습니다. 따라서 아래의 기존 코드를 수정하여 indexPath가 nil일 때도 계속 flow 가 흘러가게 하고, cell을 optional로 반환해 사용하였습니다 !
// 기존 코드
guard let startIndexPath = categoryCollectionView.indexPathForItem(at: gesture.location(in: categoryCollectionView)) else {
return
}
// 수정한 코드
let startIndexPath = categoryCollectionView.indexPathForItem(at: gesture.location(in: categoryCollectionView))
let cell = cellForItemAt(at: startIndexPath)
참고한 사이트
참고한 Youtube 영상
'iOS' 카테고리의 다른 글
iOS) WKWebView (0) | 2022.03.28 |
---|---|
iOS) Modal dismiss한 후 CollectionView reload 하기 (0) | 2022.03.28 |
iOS ) URLSession 알아보기 (2) | 2022.02.28 |
iOS ) GCD 사용시 주의해야할 사항 (0) | 2022.02.06 |
iOS ) iOS 15 UIButton.ConfigurationUpdateHandler (0) | 2022.02.05 |