Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

홈화면 책커버 Drag & Drop 구현 #103

Merged
merged 10 commits into from
Nov 30, 2024
Merged

Conversation

Kyxxn
Copy link
Collaborator

@Kyxxn Kyxxn commented Nov 30, 2024

#️⃣ 연관된 이슈


⏰ 작업 시간

예상 시간 실제 걸린 시간
3 3

📝 작업 내용

  • 홈화면 책커버 Drag & Drop 구현
  • BookCover 모델 3개에 order: Int 프로퍼티 추가

📸 스크린샷

iPhone SE iPhone 16 Pro
드래그앤드랍-SE 드래그앤드랍 구현
image

📒 리뷰 노트

드래그 앤 드랍 개요

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

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

  • UICollectionViewDragDelegate
  • UICollectionViewDropDelegate

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

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

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

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

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

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

**끝: collectionView(_:performDropWith:**)

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

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


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

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

image

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을 배열에 담아서 보내주는데, 이는 다른 앱과 상호작용할 때도 쓰일 수 있다고 함..!

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

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 또한 삭제와 삽입을 해주어 처리하였다.


⚽️ 트러블 슈팅

드래그 앤 드랍 시 카테고리가 변경되어도 일관성 유지하기

지난 편에서 CollectionView Drag Drop 델리게이트를 통해서 드래그 앤 드랍을 구현했었다.

근데 우리는 책 커버를 한 번에 모아볼 수 있는 “전체”도 있고, 특정 카테고리에 대한 책 커버만 보여줄 수도 있다.

이것에 대한 일관성 처리를 어떻게 할 지 알아보자.


시나리오

가족, 친구 두 카테고리와 책들이 다음과 같이 있다고 가정해보겠다.

image

이 상황에서 “가족” 카테고리에서 책1을 책3으로 옮겼다고 하자.

나는 그러면 “전체”에서 볼 때도 책1은 책3 뒤에 있어야 한다고 생각한다.

image

위처럼 말이다.

또한, 반대로 “전체”에서 책1을 책3으로 옮겨도,

“가족” 카테고리에서 보면 책1은 책3 뒤에 있어야 한다고 생각한다.

나는 위 두 상황에 대한 일관성을 유지해주기 위해서 BookCover 모델들에게 order라는 프로퍼티를 추가했다.

위 사진에서 빨간색 숫자가 order 값이다..!

HomeViewModel 처리

HomeViewModel에는 아래의 프로퍼티가 있다.

**private**(**set**) **var** bookCovers = [BookCover]()

**private**(**set**) **var** currentBookCovers = [BookCover]()

currentBookCovers는 현재 화면을 그리기 위해 필요한 프로퍼티이다.

private func dragAndDropBookCover(from currentIndex: Int, to destinationIndex: Int) {
    let currentBookCover = currentBookCovers[currentIndex]
    let targetBookCover = currentBookCovers[destinationIndex]
    bookCovers.remove(at: currentBookCover.order)
    bookCovers.insert(currentBookCover, at: targetBookCover.order)
    currentBookCovers.remove(at: currentBookCover.order)
    currentBookCovers.insert(currentBookCover, at: targetBookCover.order)
}

뷰모델이 input을 받으면 위 메소드로 인해 위의 두 개의 프로퍼티가 갱신된다.

그런데, 다음과 같이 인덱스 에러가 발생했음..!

전체 카테고리에서 드래그 앤 드랍이 발생하면 아무 문제가 없는데,

특정 카테고리에서는 인덱스 접근 문제가 생김..

열심히 끄적끄적..

image
private func dragAndDropBookCover(from currentIndex: Int, to destinationIndex: Int) {
    let currentBookCover = currentBookCovers[currentIndex]
    let targetBookCover = currentBookCovers[destinationIndex]
    bookCovers.remove(at: currentBookCover.order)
    bookCovers.insert(currentBookCover, at: targetBookCover.order)
    currentBookCovers.remove(at: currentIndex)
    currentBookCovers.insert(currentBookCover, at: destinationIndex)
    output.send(.dragAndDropFinished)
}

현재 카테고리를 나타내는 currentBookCovers에서는 컬렉션 뷰의 인덱스, 즉 파라미터로 넘어온 것들로만 삽입 삭제를 하면 되고

전체 bookCovers에서는 order로 연산을 해주면 일관성을 유지할 수 있었다 !!!

근데 코어데이터까지 이거 되는지는 NSSortDescriptor 써봐야 알아서 더 살펴봐야함..

@Kyxxn Kyxxn added the ✨ Feature 기능 관련 작업 label Nov 30, 2024
@Kyxxn Kyxxn added this to the 0.4 milestone Nov 30, 2024
@Kyxxn Kyxxn self-assigned this Nov 30, 2024
@Kyxxn Kyxxn linked an issue Nov 30, 2024 that may be closed by this pull request
Copy link
Collaborator

@k2645 k2645 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정말 수고많으셨습니다 ~ .ᐟ.ᐟ 👍👍

Comment on lines +82 to +83
collectionView.dragDelegate = self
collectionView.dropDelegate = self
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: delegate가 두 줄이네용

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하나는 drag 하나는 Drop 입니당 !!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

호곡 헷갈렸네요 ㅎㅎ;; 좋습니다 .ᐟ.ᐟ

Comment on lines 104 to 105
case .dragAndDropFinished:
self.collectionView.reloadData()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: 위 filteredBooks와 결과가 같으니 두 개를 같이 묶어줘도 좋을 것 같네용

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 인정합니다 수정할게요 ~~

Copy link
Collaborator

@iceHood iceHood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....효이데아준....

Copy link
Collaborator

@yuncheol-AHN yuncheol-AHN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

재밌는 거 하셨군요 !
고생하셨습니다 ~~!!

@Kyxxn Kyxxn merged commit 181f6aa into develop Nov 30, 2024
2 checks passed
@Kyxxn Kyxxn deleted the feature/dragdrop-home-bookcover branch November 30, 2024 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ Feature 기능 관련 작업
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Drag & Drop
4 participants