diff --git a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj index a89b7b0a..933fb29f 100644 --- a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj +++ b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj @@ -62,6 +62,8 @@ A46B2E7729B493D7006A7870 /* LabeledCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46B2E7629B493D7006A7870 /* LabeledCollectionView.swift */; }; A46B2E7D29B5EB14006A7870 /* NoteDetailCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46B2E7C29B5EB14006A7870 /* NoteDetailCell.swift */; }; A46B2E7F29B5EB74006A7870 /* NoteDetailCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46B2E7E29B5EB74006A7870 /* NoteDetailCellViewModel.swift */; }; + A46B2E8129B5EBDA006A7870 /* NoteDetailListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46B2E8029B5EBDA006A7870 /* NoteDetailListViewModel.swift */; }; + A46B2E8329B5EBE6006A7870 /* NoteDetailListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46B2E8229B5EBE6006A7870 /* NoteDetailListViewController.swift */; }; A46BC1EF2800626A00C2E5B4 /* TabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46BC1EE2800626A00C2E5B4 /* TabItem.swift */; }; A47D83462966C0C60028AA1D /* NotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22ADF7E27F72F7300ECB77B /* NotificationSettingsViewModel.swift */; }; A47D83482966D3870028AA1D /* FontSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47D83472966D3870028AA1D /* FontSelectionView.swift */; }; @@ -241,6 +243,8 @@ A46B2E7629B493D7006A7870 /* LabeledCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabeledCollectionView.swift; sourceTree = ""; }; A46B2E7C29B5EB14006A7870 /* NoteDetailCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailCell.swift; sourceTree = ""; }; A46B2E7E29B5EB74006A7870 /* NoteDetailCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailCellViewModel.swift; sourceTree = ""; }; + A46B2E8029B5EBDA006A7870 /* NoteDetailListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailListViewModel.swift; sourceTree = ""; }; + A46B2E8229B5EBE6006A7870 /* NoteDetailListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailListViewController.swift; sourceTree = ""; }; A46BC1EE2800626A00C2E5B4 /* TabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = ""; }; A47D83472966D3870028AA1D /* FontSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSelectionView.swift; sourceTree = ""; }; A47D8349296716BF0028AA1D /* FontCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontCell.swift; sourceTree = ""; }; @@ -460,6 +464,7 @@ isa = PBXGroup; children = ( A46B2E7E29B5EB74006A7870 /* NoteDetailCellViewModel.swift */, + A46B2E8029B5EBDA006A7870 /* NoteDetailListViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -626,6 +631,7 @@ isa = PBXGroup; children = ( A4396B1A29325D70005D9D3A /* PhotoViewController.swift */, + A46B2E8229B5EBE6006A7870 /* NoteDetailListViewController.swift */, ); path = Controller; sourceTree = ""; @@ -1395,6 +1401,7 @@ A4569CCA28113F0F001E3FD6 /* NSMutableAttributedString+Hyperlink.swift in Sources */, A490AC4E27DD945E00B04CE1 /* NotePreviewListController.swift in Sources */, A47D83542973A4860028AA1D /* FontPublishing.swift in Sources */, + A46B2E8129B5EBDA006A7870 /* NoteDetailListViewModel.swift in Sources */, A4D357E0283A43C7007819E3 /* UIViewController+VersionUpdating.swift in Sources */, A47D834A296716BF0028AA1D /* FontCell.swift in Sources */, A8BD834727BE337900E0DE41 /* HomeView.swift in Sources */, @@ -1434,6 +1441,7 @@ A484A31C295D75F900A58312 /* NetworkError.swift in Sources */, A49AC5E52917C6E2009315BC /* UILabel+ChangeFontSize.swift in Sources */, A4A55A0928FFC675004ABE00 /* NewNoteInputView.swift in Sources */, + A46B2E8329B5EBE6006A7870 /* NoteDetailListViewController.swift in Sources */, A4B2860927D9A56A008769EB /* NewNoteTextViewController.swift in Sources */, A843332127DA026D00A12A54 /* NewBottleDatePickerViewController.swift in Sources */, A4D6EB75282E2E6700553E43 /* VersionChecking.swift in Sources */, diff --git a/Happiggy-bank/Happiggy-bank/ListTab/NoteList/DetailList/UI/Controller/NoteDetailListViewController.swift b/Happiggy-bank/Happiggy-bank/ListTab/NoteList/DetailList/UI/Controller/NoteDetailListViewController.swift new file mode 100644 index 00000000..f56a867c --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/ListTab/NoteList/DetailList/UI/Controller/NoteDetailListViewController.swift @@ -0,0 +1,148 @@ +// +// NoteDetailListViewController.swift +// Happiggy-bank +// +// Created by sun on 2023/03/06. +// + +import UIKit + +/// 쪽지의 자세한 내용을 볼 수 있는 목록을 관리하는 컨트롤러 +final class NoteDetailListViewController: UIViewController { + private typealias CellRegistration = UICollectionView.CellRegistration + private typealias DataSource = UICollectionViewDiffableDataSource + private typealias Snapshot = NSDiffableDataSourceSnapshot + + + // MARK: - Enums + + private enum Section: Int { + case main + } + + + // MARK: - Properties + + private let listView = UICollectionView(frame: .zero, collectionViewLayout: .init()) + private let viewModel: NoteDetailListViewModel + private lazy var dataSource = self.configureDatasource() + + + // MARK: - Init(s) + + init(viewModel: NoteDetailListViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + + self.view.backgroundColor = .systemBackground + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + // MARK: - Life Cycles + + override func viewDidLoad() { + super.viewDidLoad() + + self.configureViews() + self.configureSnapshot() + self.scrollToSelectedNote() + } + + + // MARK: - Configuration Functions + + private func configureViews() { + self.configureNavigationBar() + self.configureCollectionView() + } + + private func configureCollectionView() { + self.view.addSubview(self.listView) + self.listView.snp.makeConstraints { + $0.top.horizontalEdges.equalTo(self.view.safeAreaLayoutGuide) + $0.bottom.equalTo(self.view) + } + self.configureLayout() + } + + private func configureLayout() { + let height = navigationController?.view.window?.windowScene?.screen.bounds.height ?? Metric.itemHeight + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(CGFloat.one), + heightDimension: .estimated(height) + ) + + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let groupSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(CGFloat.one), + heightDimension: .estimated(height) + ) + + let group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitem: item, + count: 1 + ) + + let section = NSCollectionLayoutSection(group: group).then { + $0.interGroupSpacing = Metric.spacing16 + } + + self.listView.collectionViewLayout = UICollectionViewCompositionalLayout(section: section) + /// 없으면 초기화 시 일부 셀의 레이블이 찌그러짐 + self.view.layoutIfNeeded() + } + + private func configureNavigationBar() { + self.navigationItem.title = self.viewModel.bottleTitle + self.navigationItem.backButtonTitle = .empty + } + + private func configureDatasource() -> DataSource { + let registration = CellRegistration { [weak self] cell, _, noteViewModel in + let photoTapHandler: (UIImage) -> Void = { [weak self] image in + self?.show(PhotoViewController(photo: image), sender: self) + } + noteViewModel.photoDidTap = noteViewModel.hasPhoto ? photoTapHandler : nil + cell.viewModel = noteViewModel + } + + return DataSource(collectionView: self.listView) { collectionView, indexPath, itemIdentifier in + collectionView.dequeueConfiguredReusableCell( + using: registration, + for: indexPath, + item: self.viewModel.noteViewModel(forRow: indexPath.row, id: itemIdentifier) + ) + } + } + + private func configureSnapshot() { + var snapshot = Snapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(self.viewModel.noteViewModels.map { $0.id }) + self.dataSource.apply(snapshot) + } + + private func scrollToSelectedNote() { + /// 원하는 위치에 제대로 스크롤하기 위해서 필요 + self.view.layoutIfNeeded() + let indexPath = IndexPath(row: self.viewModel.selectedIndex, section: Section.main.rawValue) + self.listView.selectItem(at: indexPath, animated: false, scrollPosition: .top) + } +} + + +// MARK: - Constants +fileprivate extension NoteDetailListViewController { + + enum Metric { + static let spacing16: CGFloat = 16 + static let itemHeight: CGFloat = 3000 + } +} diff --git a/Happiggy-bank/Happiggy-bank/ListTab/NoteList/DetailList/ViewModel/NoteDetailListViewModel.swift b/Happiggy-bank/Happiggy-bank/ListTab/NoteList/DetailList/ViewModel/NoteDetailListViewModel.swift new file mode 100644 index 00000000..93f30e3f --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/ListTab/NoteList/DetailList/ViewModel/NoteDetailListViewModel.swift @@ -0,0 +1,70 @@ +// +// NoteDetailListViewModel.swift +// Happiggy-bank +// +// Created by sun on 2023/03/06. +// + +import Foundation + +/// NoteDetailListViewContoller의 뷰모델 +final class NoteDetailListViewModel { + + // MARK: - Properties + + /// 유저가 처음 선택한 쪽지의 인덱스 + let selectedIndex: Int + + /// 쪽지 뷰 모델 배열 + let noteViewModels: [NoteDetailCellViewModel] + + /// 저금통 제목 + let bottleTitle: String + + + // MARK: - Init + + init(notes: [Note], selectedIndex: Int, bottleTitle: String) { + let count = notes.count + let imageManager = ImageManager() + self.noteViewModels = notes.enumerated().map { + NoteDetailCellViewModel( + note: $0.element, + index: $0.offset, + numberOfTotalNotes: count, + imageManager: imageManager + ) + } + self.selectedIndex = selectedIndex + self.bottleTitle = bottleTitle + } + + + // MARK: - Funtions + + func noteViewModel(forRow row: Int, id: UUID) -> NoteDetailCellViewModel? { + guard let noteViewModel = self.noteViewModels[safe: row], + noteViewModel.id == id + else { + return nil + } + + return noteViewModel + } +} + + +// MARK: - Mock Data +extension NoteDetailListViewModel { + + static let foo = { + let bottle = Bottle.fooOpened() + let selectedIndex = bottle.notes.indices.randomElement()! + + return NoteDetailListViewModel( + notes: bottle.notes, + selectedIndex: selectedIndex, + bottleTitle: bottle.title + ) + }() +} diff --git a/Happiggy-bank/Happiggy-bank/ListTab/NoteList/PreviewList/UI/Controller/NotePreviewListController.swift b/Happiggy-bank/Happiggy-bank/ListTab/NoteList/PreviewList/UI/Controller/NotePreviewListController.swift index 38f43374..cc1fcb0b 100644 --- a/Happiggy-bank/Happiggy-bank/ListTab/NoteList/PreviewList/UI/Controller/NotePreviewListController.swift +++ b/Happiggy-bank/Happiggy-bank/ListTab/NoteList/PreviewList/UI/Controller/NotePreviewListController.swift @@ -170,8 +170,13 @@ extension NotePreviewListController: UICollectionViewDelegate { guard !self.viewModel.notes.isEmpty else { return } - // TODO: - DetailList와 연결 - print(row + 1, ":", self.viewModel.notes[row].content.split(separator: "\n").first ?? "none") + let viewModel = NoteDetailListViewModel( + notes: self.viewModel.notes, + selectedIndex: row, + bottleTitle: self.viewModel.bottleTitle + ) + + self.show(NoteDetailListViewController(viewModel: viewModel), sender: self) } }