Skip to content

Commit

Permalink
Merge pull request #103 from boostcampwm-2024/feature/dragdrop-home-b…
Browse files Browse the repository at this point in the history
…ookcover

홈화면 책커버 Drag & Drop 구현
  • Loading branch information
Kyxxn authored Nov 30, 2024
2 parents fa83564 + 66638c9 commit 181f6aa
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 4 deletions.
4 changes: 4 additions & 0 deletions MemorialHouse/MHData/MHData/DTO/BookCoverDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import MHDomain

public struct BookCoverDTO {
let id: UUID
let order: Int
let title: String
let imageURL: String?
let color: String
Expand All @@ -11,13 +12,15 @@ public struct BookCoverDTO {

public init(
id: UUID,
order: Int,
title: String,
imageURL: String?,
color: String,
category: String?,
favorite: Bool
) {
self.id = id
self.order = order
self.title = title
self.imageURL = imageURL
self.color = color
Expand All @@ -30,6 +33,7 @@ public struct BookCoverDTO {

return BookCover(
id: self.id,
order: self.order,
title: self.title,
imageURL: self.imageURL,
color: color,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ extension CoreDataBookCoverStorage {

return BookCoverDTO(
id: id,
order: Int(bookCover.order),
title: title,
imageURL: bookCover.imageURL,
color: color,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23507" systemVersion="24B2082" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23507" systemVersion="24B2091" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
<entity name="BookCategoryEntity" representedClassName="BookCategoryEntity" syncable="YES" codeGenerationType="class">
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="order" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
Expand All @@ -10,6 +10,7 @@
<attribute name="favorite" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="imageURL" optional="YES" attributeType="String"/>
<attribute name="order" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="title" optional="YES" attributeType="String"/>
</entity>
<entity name="BookEntity" representedClassName="BookEntity" syncable="YES" codeGenerationType="class">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public struct LocalBookCoverRepository: BookCoverRepository {
public func create(bookCover: BookCover) async {
let bookCoverDTO = BookCoverDTO(
id: bookCover.id,
order: bookCover.order,
title: bookCover.title,
imageURL: bookCover.imageURL,
color: bookCover.color.rawValue,
Expand Down Expand Up @@ -51,6 +52,7 @@ public struct LocalBookCoverRepository: BookCoverRepository {
public func update(id: UUID, bookCover: BookCover) async {
let bookCoverDTO = BookCoverDTO(
id: bookCover.id,
order: bookCover.order,
title: bookCover.title,
imageURL: bookCover.imageURL,
color: bookCover.color.rawValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@ struct CoreDataBookCoverStorageTests {
private static let bookCovers = [
BookCoverDTO(
id: UUID(),
order: 1,
title: "test1",
imageURL: nil,
color: "pink",
category: nil,
favorite: true),
BookCoverDTO(
id: UUID(),
order: 2,
title: "test2",
imageURL: nil,
color: "blue",
category: nil,
favorite: false),
BookCoverDTO(
id: UUID(),
order: 3,
title: "test3",
imageURL: nil,
color: "beige",
Expand All @@ -41,6 +44,7 @@ struct CoreDataBookCoverStorageTests {
// Arrange
let newBookCover = BookCoverDTO(
id: UUID(),
order: 4,
title: "test4",
imageURL: nil,
color: "green",
Expand Down Expand Up @@ -83,6 +87,7 @@ struct CoreDataBookCoverStorageTests {
let oldBookCover = CoreDataBookCoverStorageTests.bookCovers[0]
let newBookCover = BookCoverDTO(
id: oldBookCover.id,
order: 4,
title: "test4",
imageURL: nil,
color: "green",
Expand Down
6 changes: 5 additions & 1 deletion MemorialHouse/MHData/MHDataTests/MHFileManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import Testing
#expect(false, "\(#function) 실패함: \(error)")
}
}

@Test func test파일읽기_성공() async {
// Arrange
let path = testDirectory
Expand All @@ -46,6 +47,7 @@ import Testing
#expect(false, "\(#function) 실패함: \(error)")
}
}

@Test func test없는_파일읽기_실패() async {
// Arrange
let path = testDirectory
Expand All @@ -61,6 +63,7 @@ import Testing
#expect(error == .fileNotExists)
}
}

@Test func test파일삭제_성공() async {
// Arrange
let path = testDirectory
Expand All @@ -79,6 +82,7 @@ import Testing
#expect(false, "\(#function) 실패함: \(error)")
}
}

@Test func test없는_파일삭제_실패() async {
// Arrange
let path = testDirectory
Expand All @@ -94,6 +98,7 @@ import Testing
#expect(error == .fileDeletionFailure)
}
}

@Test func test파일이동_성공() async {
// Arrange
let path = testDirectory
Expand All @@ -114,7 +119,6 @@ import Testing
}
}


deinit {
// 테스트 디렉토리와 관련된 파일 정리
let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
Expand Down
3 changes: 3 additions & 0 deletions MemorialHouse/MHDomain/MHDomain/Entity/BookCover.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import MHFoundation

public struct BookCover: Identifiable, Equatable, Sendable {
public let id: UUID
public let order: Int
public let title: String
public let imageURL: String?
public let color: BookColor
Expand All @@ -10,13 +11,15 @@ public struct BookCover: Identifiable, Equatable, Sendable {

public init(
id: UUID = .init(),
order: Int,
title: String,
imageURL: String?,
color: BookColor,
category: String?,
favorite: Bool = false
) {
self.id = id
self.order = order
self.title = title
self.imageURL = imageURL
self.color = color
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public final class HomeViewController: UIViewController {
view.backgroundColor = .baseBackground
collectionView.delegate = self
collectionView.dataSource = self
collectionView.dragDelegate = self
collectionView.dropDelegate = self
collectionView.register(
BookCollectionViewCell.self,
forCellWithReuseIdentifier: BookCollectionViewCell.identifier
Expand All @@ -95,7 +97,7 @@ public final class HomeViewController: UIViewController {
switch event {
case .fetchedMemorialHouseAndCategory:
self.updateMemorialHouse()
case .filteredBooks:
case .filteredBooks, .dragAndDropFinished:
self.collectionView.reloadData()
case .fetchedFailure(let errorMessage):
self.handleError(with: errorMessage)
Expand Down Expand Up @@ -285,8 +287,80 @@ extension HomeViewController: UICollectionViewDataSource {
}
}

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

// MARK: - UICollectionViewDropDelegate
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)
}

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])
}
}
}

// MARK: - BookCategoryViewControllerDelegate
extension HomeViewController: BookCategoryViewControllerDelegate {
func categoryViewController(_ categoryViewController: BookCategoryViewController, didSelectCategory category: String) {
func categoryViewController(
_ categoryViewController: BookCategoryViewController,
didSelectCategory category: String
) {
currentCategory = category
input.send(.selectedCategory(category: category))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ public final class HomeViewModel: ViewModelType {
public enum Input {
case viewDidLoad
case selectedCategory(category: String)
case dragAndDropBookCover(currentIndex: Int, destinationIndex: Int)
}

public enum Output {
case fetchedMemorialHouseAndCategory
case filteredBooks
case fetchedFailure(String)
case dragAndDropFinished
}

private let output = PassthroughSubject<Output, Never>()
Expand Down Expand Up @@ -41,6 +43,8 @@ public final class HomeViewModel: ViewModelType {
}
case .selectedCategory(let category):
self?.filterBooks(by: category)
case .dragAndDropBookCover(let currentIndex, let destinationIndex):
self?.dragAndDropBookCover(from: currentIndex, to: destinationIndex)
}
}.store(in: &cancellables)

Expand All @@ -66,4 +70,14 @@ public final class HomeViewModel: ViewModelType {

output.send(.filteredBooks)
}

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)
}
}

0 comments on commit 181f6aa

Please sign in to comment.