Skip to content

Commit

Permalink
[refactor] NoteDetailListViewController와 ViewModel 구현 #291
Browse files Browse the repository at this point in the history
  • Loading branch information
skkimeo committed Jun 24, 2023
1 parent 754572f commit fa2e5f0
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 2 deletions.
8 changes: 8 additions & 0 deletions Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -241,6 +243,8 @@
A46B2E7629B493D7006A7870 /* LabeledCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabeledCollectionView.swift; sourceTree = "<group>"; };
A46B2E7C29B5EB14006A7870 /* NoteDetailCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailCell.swift; sourceTree = "<group>"; };
A46B2E7E29B5EB74006A7870 /* NoteDetailCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailCellViewModel.swift; sourceTree = "<group>"; };
A46B2E8029B5EBDA006A7870 /* NoteDetailListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailListViewModel.swift; sourceTree = "<group>"; };
A46B2E8229B5EBE6006A7870 /* NoteDetailListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailListViewController.swift; sourceTree = "<group>"; };
A46BC1EE2800626A00C2E5B4 /* TabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = "<group>"; };
A47D83472966D3870028AA1D /* FontSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSelectionView.swift; sourceTree = "<group>"; };
A47D8349296716BF0028AA1D /* FontCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontCell.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -460,6 +464,7 @@
isa = PBXGroup;
children = (
A46B2E7E29B5EB74006A7870 /* NoteDetailCellViewModel.swift */,
A46B2E8029B5EBDA006A7870 /* NoteDetailListViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
Expand Down Expand Up @@ -626,6 +631,7 @@
isa = PBXGroup;
children = (
A4396B1A29325D70005D9D3A /* PhotoViewController.swift */,
A46B2E8229B5EBE6006A7870 /* NoteDetailListViewController.swift */,
);
path = Controller;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<NoteDetailCell, NoteDetailCellViewModel>
private typealias DataSource = UICollectionViewDiffableDataSource<Section, UUID>
private typealias Snapshot = NSDiffableDataSourceSnapshot<Section, UUID>


// 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
}
}
Original file line number Diff line number Diff line change
@@ -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
)
}()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down

0 comments on commit fa2e5f0

Please sign in to comment.