From 5f485d9421fbf1456462f85d5b21793e06c9e644 Mon Sep 17 00:00:00 2001 From: thoonk Date: Sun, 7 Jul 2024 23:17:39 +0900 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F#294:=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=B7=B0=20=EA=B0=9C=EC=84=A0=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 UITableView DataSource 에서 Diffable DataSource 로 변경 작업 - 기존 2개(드랍, 좋아요)의 UITableView(Hidden 처리)를 1개의 UICollectionView를 이용한 개선 작업 - Compositional Layout 적용 - 콘텐츠 수에 따른 CollectionView Height 업데이트를 통한 전체 스크롤뷰 높이 조정 --- .../StreetDrop.xcodeproj/project.pbxproj | 8 +- .../MyPage/Model/Entites/MyMusic.swift | 58 +-- ...ableViewCell.swift => MusicListCell.swift} | 24 +- .../View/MusicListSectionHeaderView.swift | 16 +- .../Presentation/MyPage/View/MyPageType.swift | 8 +- .../MyPage/View/MyPageViewController.swift | 366 +++++++++--------- .../MyPage/ViewModel/MyPageViewModel.swift | 66 ++-- 7 files changed, 271 insertions(+), 275 deletions(-) rename StreetDrop/StreetDrop/Presentation/MyPage/View/{MusicTableViewCell.swift => MusicListCell.swift} (94%) diff --git a/StreetDrop/StreetDrop.xcodeproj/project.pbxproj b/StreetDrop/StreetDrop.xcodeproj/project.pbxproj index cef54718..873c35e5 100644 --- a/StreetDrop/StreetDrop.xcodeproj/project.pbxproj +++ b/StreetDrop/StreetDrop.xcodeproj/project.pbxproj @@ -57,7 +57,7 @@ 1824F13A2A34A4B700A10320 /* MusicCountEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1824F1362A349F3700A10320 /* MusicCountEntity.swift */; }; 1824F13B2A34A4C900A10320 /* PoiEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18683FE02A349109005A94AC /* PoiEntity.swift */; }; 1867C8202A4DDB8C00F8EC48 /* MyPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1867C81F2A4DDB8C00F8EC48 /* MyPageViewController.swift */; }; - 1867C8242A4FFCDF00F8EC48 /* MusicTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1867C8232A4FFCDF00F8EC48 /* MusicTableViewCell.swift */; }; + 1867C8242A4FFCDF00F8EC48 /* MusicListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1867C8232A4FFCDF00F8EC48 /* MusicListCell.swift */; }; 18683FD92A2A251E005A94AC /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18683FD82A2A251E005A94AC /* ViewModel.swift */; }; 18683FDC2A348B15005A94AC /* DefaultMainRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18683FDB2A348B15005A94AC /* DefaultMainRepository.swift */; }; 18683FDF2A348B25005A94AC /* MainRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18683FDE2A348B25005A94AC /* MainRepository.swift */; }; @@ -332,7 +332,7 @@ 1816ED482A68591F005009FC /* DefaultNicknameEditRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultNicknameEditRepository.swift; sourceTree = ""; }; 1824F1362A349F3700A10320 /* MusicCountEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicCountEntity.swift; sourceTree = ""; }; 1867C81F2A4DDB8C00F8EC48 /* MyPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageViewController.swift; sourceTree = ""; }; - 1867C8232A4FFCDF00F8EC48 /* MusicTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicTableViewCell.swift; sourceTree = ""; }; + 1867C8232A4FFCDF00F8EC48 /* MusicListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicListCell.swift; sourceTree = ""; }; 18683FD82A2A251E005A94AC /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; 18683FDB2A348B15005A94AC /* DefaultMainRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultMainRepository.swift; sourceTree = ""; }; 18683FDE2A348B25005A94AC /* MainRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainRepository.swift; sourceTree = ""; }; @@ -902,7 +902,7 @@ isa = PBXGroup; children = ( 1867C81F2A4DDB8C00F8EC48 /* MyPageViewController.swift */, - 1867C8232A4FFCDF00F8EC48 /* MusicTableViewCell.swift */, + 1867C8232A4FFCDF00F8EC48 /* MusicListCell.swift */, 18EF9FC82A5C51FB00266D27 /* NicknameEditViewController.swift */, 1816ED3B2A680608005009FC /* MusicListSectionHeaderView.swift */, 0856524F2AFBB73100FD9BCB /* MyPageType.swift */, @@ -1880,7 +1880,7 @@ C44A549C2BBC099E00354F8F /* DefaultFetchingPopUpInfomationUseCase.swift in Sources */, 082F17242ADD60BB00174D98 /* UINavigation+Gesture.swift in Sources */, 415113272A165DC50051F809 /* MusicDropViewController.swift in Sources */, - 1867C8242A4FFCDF00F8EC48 /* MusicTableViewCell.swift in Sources */, + 1867C8242A4FFCDF00F8EC48 /* MusicListCell.swift in Sources */, C45BF3A12A1D133000CEDE74 /* RecentMusicQueryDTO.swift in Sources */, 040685002A01539800377094 /* AppDelegate.swift in Sources */, 18CB7BED2A359F53002B31FB /* MusicWithinAreaEntity.swift in Sources */, diff --git a/StreetDrop/StreetDrop/Presentation/MyPage/Model/Entites/MyMusic.swift b/StreetDrop/StreetDrop/Presentation/MyPage/Model/Entites/MyMusic.swift index 75296f1c..1db157c0 100644 --- a/StreetDrop/StreetDrop/Presentation/MyPage/Model/Entites/MyMusic.swift +++ b/StreetDrop/StreetDrop/Presentation/MyPage/Model/Entites/MyMusic.swift @@ -8,7 +8,17 @@ import Foundation import RxDataSources -struct MyMusic { +struct TotalMyMusics { + let musics: [MyMusics] + let totalCount: Int +} + +struct MyMusics { + let date: String + let musics: [MyMusic] +} + +struct MyMusic: Hashable { let id: Int let userId: Int var userName: String @@ -22,33 +32,33 @@ struct MyMusic { let createdAt: String let location: String let likeCount: Int -} - -struct MyMusics { - let date: String - let musics: [MyMusic] -} - -struct TotalMyMusics { - let musics: [MyMusics] - let totalCount: Int -} - -struct MyMusicsSection { - var date: String - var items: [Item] - init(date: String, items: [MyMusic]) { - self.date = date - self.items = items + private let identifier = UUID() + + func hash(into hasher: inout Hasher) { + hasher.combine(identifier) + } + + static func == (lhs: MyMusic, rhs: MyMusic) -> Bool { + return lhs.identifier == rhs.identifier } } -extension MyMusicsSection: SectionModelType { - typealias Item = MyMusic +struct MyMusicsSectionType: Hashable { + let section: MyMusicsSection + let items: [MyMusic] - init(original: MyMusicsSection, items: [MyMusic]) { - self = original - self.items = items + private let identifier = UUID() + + func hash(into hasher: inout Hasher) { + hasher.combine(identifier) } + + static func == (lhs: MyMusicsSectionType, rhs: MyMusicsSectionType) -> Bool { + return lhs.identifier == rhs.identifier + } +} + +enum MyMusicsSection: Hashable { + case musics(date: String) } diff --git a/StreetDrop/StreetDrop/Presentation/MyPage/View/MusicTableViewCell.swift b/StreetDrop/StreetDrop/Presentation/MyPage/View/MusicListCell.swift similarity index 94% rename from StreetDrop/StreetDrop/Presentation/MyPage/View/MusicTableViewCell.swift rename to StreetDrop/StreetDrop/Presentation/MyPage/View/MusicListCell.swift index 9806dd0f..116654e9 100644 --- a/StreetDrop/StreetDrop/Presentation/MyPage/View/MusicTableViewCell.swift +++ b/StreetDrop/StreetDrop/Presentation/MyPage/View/MusicListCell.swift @@ -11,13 +11,14 @@ import SnapKit import RxCocoa import RxSwift -final class MusicTableViewCell: UITableViewCell { +final class MusicListCell: UICollectionViewCell { static let identifier = "MusicTableViewCell" private var disposeBag: DisposeBag = DisposeBag() - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - self.configureUI() + override init(frame: CGRect) { + super.init(frame: frame) + + configureUI() } @available(*, unavailable) @@ -138,7 +139,9 @@ final class MusicTableViewCell: UITableViewCell { }() } -private extension MusicTableViewCell { +// MARK: - Private Methods + +private extension MusicListCell { // MARK: - UI @@ -147,7 +150,6 @@ private extension MusicTableViewCell { // MARK: - Cell self.backgroundColor = UIColor.gray900 - self.selectionStyle = .none // MARK: - Container StackView @@ -291,13 +293,3 @@ struct UIViewPreview: UIViewRepresentable { view.setContentHuggingPriority(.defaultHigh, for: .vertical) } } - -@available(iOS 13.0.0, *) -struct MusicTableViewCellPreview: PreviewProvider{ - static var previews: some View { - UIViewPreview { - return MusicTableViewCell() - } - .previewLayout(.fixed(width: 327, height: 92)) - } -} diff --git a/StreetDrop/StreetDrop/Presentation/MyPage/View/MusicListSectionHeaderView.swift b/StreetDrop/StreetDrop/Presentation/MyPage/View/MusicListSectionHeaderView.swift index 9765ebb7..48a250d5 100644 --- a/StreetDrop/StreetDrop/Presentation/MyPage/View/MusicListSectionHeaderView.swift +++ b/StreetDrop/StreetDrop/Presentation/MyPage/View/MusicListSectionHeaderView.swift @@ -9,7 +9,7 @@ import UIKit import SnapKit -final class MusicListSectionHeaderView: UITableViewHeaderFooterView { +final class MusicListSectionHeaderView: UICollectionReusableView { static let identifier = "MusicListSectionHeaderView" private lazy var dateLabel: UILabel = { @@ -19,9 +19,10 @@ final class MusicListSectionHeaderView: UITableViewHeaderFooterView { return label }() - override init(reuseIdentifier: String?) { - super.init(reuseIdentifier: reuseIdentifier) - self.configureUI() + override init(frame: CGRect) { + super.init(frame: frame) + + configureUI() } @available(*, unavailable) @@ -38,12 +39,7 @@ extension MusicListSectionHeaderView { // MARK: - UI func configureUI() { - - // MARK: - View - - let backgroundView = UIView() - backgroundView.backgroundColor = UIColor.gray900 - self.backgroundView = backgroundView + backgroundColor = UIColor.gray900 // MARK: - Date Label diff --git a/StreetDrop/StreetDrop/Presentation/MyPage/View/MyPageType.swift b/StreetDrop/StreetDrop/Presentation/MyPage/View/MyPageType.swift index d3b5b64b..6c0981e2 100644 --- a/StreetDrop/StreetDrop/Presentation/MyPage/View/MyPageType.swift +++ b/StreetDrop/StreetDrop/Presentation/MyPage/View/MyPageType.swift @@ -7,9 +7,7 @@ import Foundation -enum MyPageType: Int { - case dropMusic = 100 - case likeMusic = 101 +enum MyMusicType { + case drop + case like } - -typealias MusicInfo = (type: MyPageType, itemID: Int) diff --git a/StreetDrop/StreetDrop/Presentation/MyPage/View/MyPageViewController.swift b/StreetDrop/StreetDrop/Presentation/MyPage/View/MyPageViewController.swift index 2464afb9..f737597e 100644 --- a/StreetDrop/StreetDrop/Presentation/MyPage/View/MyPageViewController.swift +++ b/StreetDrop/StreetDrop/Presentation/MyPage/View/MyPageViewController.swift @@ -14,18 +14,20 @@ import RxSwift import SnapKit final class MyPageViewController: UIViewController, Toastable, Alertable { - typealias DataSource = RxTableViewSectionedReloadDataSource + fileprivate typealias MusicDataSource = UICollectionViewDiffableDataSource private var stickyTapListStackView: UIStackView? private var stickyTopDimmedView: UIView? - private lazy var dropMusicDataSource: DataSource = configureDataSource() - private lazy var likeMusicDataSource: DataSource = configureDataSource() + private lazy var musicDataSource: MusicDataSource = configureMusicDataSource() + private var myMusicType: MyMusicType = .drop + private var collectionViewHeightConstraint: Constraint? private var viewModel: MyPageViewModel private let viewWillAppearEvent = PublishRelay() + private let listTypeTapEvent = PublishRelay() private let levelPolicyTapEvent = PublishRelay() - private let selectedMusicEvent = PublishRelay() + private let selectedMusicEvent = PublishRelay() private let disposeBag = DisposeBag() init(viewModel: MyPageViewModel = MyPageViewModel()) { @@ -44,6 +46,7 @@ final class MyPageViewController: UIViewController, Toastable, Alertable { self.configureUI() self.bindAction() self.bindViewModel() + self.configureSupplementaryViewRegistration() } override func viewWillAppear(_ animated: Bool) { @@ -188,38 +191,17 @@ final class MyPageViewController: UIViewController, Toastable, Alertable { label.isHidden = true return label }() - - private lazy var dropMusicListTableView: MusicListTableView = { - let tableView = MusicListTableView() - tableView.backgroundColor = .gray900 - tableView.isScrollEnabled = false - tableView.rowHeight = UITableView.automaticDimension - tableView.estimatedRowHeight = 100 - tableView.register(MusicTableViewCell.self, forCellReuseIdentifier: MusicTableViewCell.identifier) - tableView.register(MusicListSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: MusicListSectionHeaderView.identifier) - tableView.sectionFooterHeight = 0 - tableView.tableFooterView = UIView() - tableView.tag = MyPageType.dropMusic.rawValue - tableView.rx.setDelegate(self) - .disposed(by: disposeBag) - return tableView - }() - - private lazy var likeMusicListTableView: MusicListTableView = { - let tableView = MusicListTableView() - tableView.backgroundColor = .gray900 - tableView.isHidden = true - tableView.isScrollEnabled = false - tableView.rowHeight = UITableView.automaticDimension - tableView.estimatedRowHeight = 100 - tableView.register(MusicTableViewCell.self, forCellReuseIdentifier: MusicTableViewCell.identifier) - tableView.register(MusicListSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: MusicListSectionHeaderView.identifier) - tableView.sectionFooterHeight = 0 - tableView.tableFooterView = UIView() - tableView.tag = MyPageType.likeMusic.rawValue - tableView.rx.setDelegate(self) - .disposed(by: disposeBag) - return tableView + + private lazy var musicListCollectionView: UICollectionView = { + let collectionView = UICollectionView ( + frame: .zero, + collectionViewLayout: createMusicListLayout() + ) + collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + collectionView.isScrollEnabled = false + collectionView.backgroundColor = .clear + + return collectionView }() private lazy var scrollToTopButton: UIButton = { @@ -396,20 +378,13 @@ private extension MyPageViewController { self.tapListStackView.addArrangedSubview(dropCountLabel) - // MARK: - Drop Music List TableView - - self.containerView.addSubview(dropMusicListTableView) - self.dropMusicListTableView.snp.makeConstraints { make in - make.top.equalTo(tapListStackView.snp.bottom).offset(8) - make.leading.trailing.bottom.equalToSuperview() - } - - // MARK: - Like Music List TableView + // MARK: - Music List CollectionView - self.containerView.addSubview(likeMusicListTableView) - self.likeMusicListTableView.snp.makeConstraints { make in - make.top.equalTo(tapListStackView.snp.bottom).offset(8) - make.leading.trailing.bottom.equalToSuperview() + containerView.addSubview(musicListCollectionView) + musicListCollectionView.snp.makeConstraints { + $0.top.equalTo(tapListStackView.snp.bottom).offset(8) + $0.leading.trailing.bottom.equalToSuperview() + self.collectionViewHeightConstraint = $0.height.equalTo(1).constraint } // MARK: - Scroll To Top Button @@ -459,7 +434,7 @@ private extension MyPageViewController { newLikeButton.setTitle("좋아요", for: .normal) newLikeButton.titleLabel?.font = .pretendard(size: 20, weightName: .bold) - if self.dropMusicListTableView.isHidden { + if myMusicType == .like { newDropButton.setTitleColor(UIColor.gray400, for: .normal) newDropButton.setTitleColor(UIColor(hexString: "#43464B"), for: .highlighted) @@ -476,7 +451,7 @@ private extension MyPageViewController { let newLabel = UILabel() newLabel.textColor = UIColor.gray400 newLabel.font = .pretendard(size: 14, weightName: .regular) - if self.dropMusicListTableView.isHidden { + if myMusicType == .like { newLabel.text = likeCountLabel.text } else { newLabel.text = dropCountLabel.text @@ -504,55 +479,19 @@ private extension MyPageViewController { private func bindTapButtonAction(dropTapButton: UIButton, likeTapButton: UIButton) { dropTapButton.rx.tap - .subscribe(onNext: { [weak self] in - guard let self = self else { return } - self.dropMusicListTableView.isHidden = false - self.likeMusicListTableView.isHidden = true - - self.tapListStackView.removeArrangedSubview(self.likeCountLabel) - self.likeCountLabel.isHidden = true - self.tapListStackView.addArrangedSubview(self.dropCountLabel) - self.dropCountLabel.isHidden = false - - dropTapButton.setTitleColor(.white, for: .normal) - dropTapButton.setTitleColor(.lightGray, for: .highlighted) - self.dropTapButton.setTitleColor(.white, for: .normal) - self.dropTapButton.setTitleColor(.lightGray, for: .highlighted) - likeTapButton.setTitleColor(UIColor.gray400, for: .normal) - likeTapButton.setTitleColor(UIColor(hexString: "#43464B"), for: .highlighted) - self.likeTapButton.setTitleColor(UIColor.gray400, for: .normal) - self.likeTapButton.setTitleColor(UIColor(hexString: "#43464B"), for: .highlighted) - - if self.scrollView.contentOffset.y > 343 { - self.scrollView.setContentOffset(CGPoint(x: 0, y: 343), animated: true) - } - }) + .bind(with: self) { owner, _ in + owner.listTypeTapEvent.accept(.drop) + owner.updateTapListUI(by: .drop) + owner.myMusicType = .drop + } .disposed(by: disposeBag) likeTapButton.rx.tap - .subscribe(onNext: { [weak self] in - guard let self = self else { return } - self.dropMusicListTableView.isHidden = true - self.likeMusicListTableView.isHidden = false - - self.tapListStackView.removeArrangedSubview(self.dropCountLabel) - self.dropCountLabel.isHidden = true - self.tapListStackView.addArrangedSubview(self.likeCountLabel) - self.likeCountLabel.isHidden = false - - dropTapButton.setTitleColor(UIColor.gray400, for: .normal) - dropTapButton.setTitleColor(UIColor(hexString: "#43464B"), for: .highlighted) - self.dropTapButton.setTitleColor(UIColor.gray400, for: .normal) - self.dropTapButton.setTitleColor(UIColor(hexString: "#43464B"), for: .highlighted) - likeTapButton.setTitleColor(.white, for: .normal) - likeTapButton.setTitleColor(.lightGray, for: .highlighted) - self.likeTapButton.setTitleColor(.white, for: .normal) - self.likeTapButton.setTitleColor(.lightGray, for: .highlighted) - - if self.scrollView.contentOffset.y > 343 { - self.scrollView.setContentOffset(CGPoint(x: 0, y: 343), animated: true) - } - }) + .bind(with: self) { owner, _ in + owner.listTypeTapEvent.accept(.like) + owner.updateTapListUI(by: .like) + owner.myMusicType = .like + } .disposed(by: disposeBag) } @@ -591,19 +530,11 @@ private extension MyPageViewController { .bind(to: levelPolicyTapEvent) .disposed(by: disposeBag) - dropMusicListTableView.rx.itemSelected + musicListCollectionView.rx.itemSelected .throttle(.seconds(2), scheduler: MainScheduler.instance) .bind(with: self) { owner, indexPath in - let itemID = owner.viewModel.myDropMusicList[indexPath.section][indexPath.row].id - owner.selectedMusicEvent.accept((MyPageType.dropMusic ,itemID)) - } - .disposed(by: disposeBag) - - likeMusicListTableView.rx.itemSelected - .throttle(.seconds(2), scheduler: MainScheduler.instance) - .bind(with: self) { owner, indexPath in - let itemID = owner.viewModel.myLikeMusicList[indexPath.section][indexPath.row].id - owner.selectedMusicEvent.accept((MyPageType.likeMusic ,itemID)) + guard let item = owner.musicDataSource.itemIdentifier(for: indexPath) else { return } + owner.selectedMusicEvent.accept(item.id) } .disposed(by: disposeBag) } @@ -612,10 +543,12 @@ private extension MyPageViewController { private func bindViewModel() { let input = MyPageViewModel.Input( - viewWillAppearEvent: self.viewWillAppearEvent, + viewWillAppearEvent: viewWillAppearEvent, + listTypeTapEvent: listTypeTapEvent, levelPolicyTapEvent: levelPolicyTapEvent, selectedMusicEvent: selectedMusicEvent ) + let output = viewModel.convert(input: input, disposedBag: disposeBag) output.levelName @@ -637,12 +570,10 @@ private extension MyPageViewController { }) .disposed(by: disposeBag) - output.myDropMusicsSections - .bind(to: dropMusicListTableView.rx.items(dataSource: dropMusicDataSource)) - .disposed(by: disposeBag) - - output.myLikeMusicsSections - .bind(to: likeMusicListTableView.rx.items(dataSource: likeMusicDataSource)) + output.myMusicsSections + .bind(with: self) { owner, sections in + owner.displayMusicList(sections) + } .disposed(by: disposeBag) output.totalDropMusicsCount @@ -761,58 +692,111 @@ extension MyPageViewController: UIScrollViewDelegate { } } -// MARK: - TableView Delegate +// MARK: - Private Methods + +private extension MyPageViewController { + func configureMusicDataSource() -> MusicDataSource { + typealias MusicListCellRegistration = UICollectionView.CellRegistration -extension MyPageViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return 32 + let musicCellRegistration = MusicListCellRegistration { cell, _ , item in + cell.setData(item: item) + } + + return MusicDataSource(collectionView: musicListCollectionView) { collectionView, indexPath, item in + + return collectionView.dequeueConfiguredReusableCell( + using: musicCellRegistration, + for: indexPath, + item: item + ) + } + } + + func configureSupplementaryViewRegistration() { + typealias MusicListHeaderRegistration = UICollectionView.SupplementaryRegistration + + let headerRegistration = MusicListHeaderRegistration(elementKind: UICollectionView.elementKindSectionHeader) { [weak self] headerView, elementKind, indexPath in + guard let self else { return } + + let section = self.musicDataSource.snapshot().sectionIdentifiers[safe: indexPath.section] + if case let .musics(date) = section { + headerView.setData(date: date) + } + } + + musicDataSource.supplementaryViewProvider = { [weak self] _, kind, index in + switch kind { + case UICollectionView.elementKindSectionHeader: + return self?.musicListCollectionView.dequeueConfiguredReusableSupplementary( + using: headerRegistration, + for: index + ) + default: + return UICollectionReusableView() + } + } } - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - if let type = MyPageType(rawValue: tableView.tag) { - return configureHeaderView( - type: type, - tableView: tableView, - section: section + func createMusicListLayout() -> UICollectionViewLayout { + return UICollectionViewCompositionalLayout { [weak self] section, env -> NSCollectionLayoutSection? in + guard let self else { return nil } + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .estimated(100) ) - } else { - return nil + + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let groupSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .estimated(100) + ) + + let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item]) + + let section = NSCollectionLayoutSection(group: group) + let sectionHeader = self.configureSectionHeader() + section.boundarySupplementaryItems = [sectionHeader] + + return section } } -} - -// MARK: - Private Methods - -private extension MyPageViewController { - func configureDataSource() -> DataSource { - return DataSource( - configureCell: { _, tableView, indexPath, item in - guard let cell = tableView.dequeueReusableCell(withIdentifier: MusicTableViewCell.identifier, for: indexPath) as? MusicTableViewCell else { return UITableViewCell() } - cell.setData(item: item) - return cell - }) + + func configureSectionHeader() -> NSCollectionLayoutBoundarySupplementaryItem { + return NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .estimated(32) + ), + elementKind: UICollectionView.elementKindSectionHeader, + alignment: .top + ) } - func configureHeaderView( - type: MyPageType, - tableView: UITableView, - section: Int - ) -> UIView? { - var dataSource: DataSource + func displayMusicList(_ sectionTypes: [MyMusicsSectionType]) { + var snapshot = NSDiffableDataSourceSnapshot() - switch type { - case .dropMusic: - dataSource = dropMusicDataSource - case .likeMusic: - dataSource = likeMusicDataSource + sectionTypes.forEach { sectionType in + snapshot.appendSections([sectionType.section]) + snapshot.appendItems(sectionType.items, toSection: sectionType.section) } - guard let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: MusicListSectionHeaderView.identifier) as? MusicListSectionHeaderView - else { return UIView() } - - let sectionData = dataSource[section] - headerView.setData(date: sectionData.date) - return headerView + musicDataSource.apply(snapshot, animatingDifferences: true) { [weak self] in + self?.updateCollectionViewHeight() + } + } + + func updateCollectionViewHeight() { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.musicListCollectionView.layoutIfNeeded() + let contentHeight = self.musicListCollectionView.contentSize.height + 50 + self.collectionViewHeightConstraint?.update(offset: contentHeight) + + UIView.animate(withDuration: 0.3) { + self.view.layoutIfNeeded() + } + } } func updateLevelupGuideViewConstraints(by isShow: Bool) { @@ -827,40 +811,42 @@ private extension MyPageViewController { } } } -} - -// MARK: - 커스텀 테이블뷰 - -private final class MusicListTableView: UITableView { - override var contentSize:CGSize { - didSet { - invalidateIntrinsicContentSize() - } - } - - override var intrinsicContentSize: CGSize { - layoutIfNeeded() - return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height) - } -} - - -//MARK: - for canvas -import SwiftUI -struct MyPageViewControllerRepresentable: UIViewControllerRepresentable { - typealias UIViewControllerType = MyPageViewController - - func makeUIViewController(context: Context) -> MyPageViewController { - return MyPageViewController() - } - func updateUIViewController(_ uiViewController: MyPageViewController, context: Context) { - } -} - -@available(iOS 13.0.0, *) -struct MyPageViewControllerPreview: PreviewProvider { - static var previews: some View { - MyPageViewControllerRepresentable() + func updateTapListUI(by type: MyMusicType) { + switch type { + case .drop: + tapListStackView.removeArrangedSubview(self.likeCountLabel) + likeCountLabel.isHidden = true + tapListStackView.addArrangedSubview(self.dropCountLabel) + dropCountLabel.isHidden = false + + dropTapButton.setTitleColor(.white, for: .normal) + dropTapButton.setTitleColor(.lightGray, for: .highlighted) + dropTapButton.setTitleColor(.white, for: .normal) + dropTapButton.setTitleColor(.lightGray, for: .highlighted) + likeTapButton.setTitleColor(UIColor.gray400, for: .normal) + likeTapButton.setTitleColor(UIColor(hexString: "#43464B"), for: .highlighted) + likeTapButton.setTitleColor(UIColor.gray400, for: .normal) + likeTapButton.setTitleColor(UIColor(hexString: "#43464B"), for: .highlighted) + + case.like: + tapListStackView.removeArrangedSubview(self.dropCountLabel) + dropCountLabel.isHidden = true + tapListStackView.addArrangedSubview(self.likeCountLabel) + likeCountLabel.isHidden = false + + dropTapButton.setTitleColor(UIColor.gray400, for: .normal) + dropTapButton.setTitleColor(UIColor(hexString: "#43464B"), for: .highlighted) + dropTapButton.setTitleColor(UIColor.gray400, for: .normal) + dropTapButton.setTitleColor(UIColor(hexString: "#43464B"), for: .highlighted) + likeTapButton.setTitleColor(.white, for: .normal) + likeTapButton.setTitleColor(.lightGray, for: .highlighted) + likeTapButton.setTitleColor(.white, for: .normal) + likeTapButton.setTitleColor(.lightGray, for: .highlighted) + } + + if scrollView.contentOffset.y > 343 { + scrollView.setContentOffset(CGPoint(x: 0, y: 343), animated: true) + } } } diff --git a/StreetDrop/StreetDrop/Presentation/MyPage/ViewModel/MyPageViewModel.swift b/StreetDrop/StreetDrop/Presentation/MyPage/ViewModel/MyPageViewModel.swift index 382d76b5..2cdec98f 100644 --- a/StreetDrop/StreetDrop/Presentation/MyPage/ViewModel/MyPageViewModel.swift +++ b/StreetDrop/StreetDrop/Presentation/MyPage/ViewModel/MyPageViewModel.swift @@ -18,23 +18,23 @@ final class MyPageViewModel { ) ) - var myDropMusicList: [[MyMusic]] = [] - var myLikeMusicList: [[MyMusic]] = [] + private var myDropMusicSections: [MyMusicsSectionType] = .init() + private var myLikeMusicSections: [MyMusicsSectionType] = .init() } extension MyPageViewModel: ViewModel { struct Input { let viewWillAppearEvent: PublishRelay + let listTypeTapEvent: PublishRelay let levelPolicyTapEvent: PublishRelay - let selectedMusicEvent: PublishRelay + let selectedMusicEvent: PublishRelay } struct Output { let levelImageURL = PublishRelay() let levelName = PublishRelay() let nickName = PublishRelay() - let myDropMusicsSections = PublishRelay<[MyMusicsSection]>() - let myLikeMusicsSections = PublishRelay<[MyMusicsSection]>() + let myMusicsSections = PublishRelay<[MyMusicsSectionType]>() let totalDropMusicsCount = PublishRelay() let totalLikeMusicsCount = PublishRelay() let pushCommunityView = PublishRelay() @@ -61,6 +61,17 @@ extension MyPageViewModel: ViewModel { } .disposed(by: disposedBag) + input.listTypeTapEvent + .bind(with: self) { owner, type in + switch type { + case .drop: + output.myMusicsSections.accept(owner.myDropMusicSections) + case .like: + output.myMusicsSections.accept(owner.myLikeMusicSections) + } + } + .disposed(by: disposedBag) + input.levelPolicyTapEvent .bind(with: self) { owner, _ in owner.fetchLevelPolicy(output: output, disposeBag: disposedBag) @@ -68,9 +79,9 @@ extension MyPageViewModel: ViewModel { .disposed(by: disposedBag) input.selectedMusicEvent - .bind(with: self) { owner, musicInfo in + .bind(with: self) { owner, itemID in owner.fetchMyDropMusic( - itemID: musicInfo.itemID, + itemID: itemID, output: output, disposedBag: disposedBag ) @@ -81,6 +92,8 @@ extension MyPageViewModel: ViewModel { } } +// MARK: - Private Methods + private extension MyPageViewModel { func fetchLevelItems(output: Output, disposedBag: DisposeBag) { model.fetchMyLevel() @@ -121,19 +134,17 @@ private extension MyPageViewModel { .disposed(by: disposeBag) } - func fetchMyDropMusicsSections(output: Output, disposedBag: DisposeBag) { + func fetchMyDropMusicsSections( + output: Output, + disposedBag: DisposeBag + ) { model.fetchMyDropList() .subscribe(with: self, onSuccess: { owner, totalMusics in output.totalDropMusicsCount.accept(totalMusics.totalCount) - output.myDropMusicsSections.accept( - totalMusics.musics.map { - .init(date: $0.date, items: $0.musics) - } - ) - - owner.myDropMusicList = totalMusics.musics.map { $0.musics } - }, onFailure: { _, _ in - output.myDropMusicsSections.accept([]) + owner.myDropMusicSections = owner.convertToSectionTypes(from: totalMusics) + output.myMusicsSections.accept(owner.myDropMusicSections) + }, onFailure: { _, error in + print("❌Fetching My Drop List Error: \(error.localizedDescription)") }) .disposed(by: disposedBag) } @@ -142,15 +153,9 @@ private extension MyPageViewModel { model.fetchMyLikeList() .subscribe(with: self, onSuccess: { owner, totalMusics in output.totalLikeMusicsCount.accept(totalMusics.totalCount) - output.myLikeMusicsSections.accept( - totalMusics.musics.map { - .init(date: $0.date, items: $0.musics) - } - ) - - owner.myLikeMusicList = totalMusics.musics.map { $0.musics } - }, onFailure: { _, _ in - output.myLikeMusicsSections.accept([]) + owner.myLikeMusicSections = owner.convertToSectionTypes(from: totalMusics) + }, onFailure: { _, error in + print("❌Fetching My Like List Error: \(error.localizedDescription)") }) .disposed(by: disposedBag) } @@ -173,4 +178,13 @@ private extension MyPageViewModel { }) .disposed(by: disposedBag) } + + func convertToSectionTypes(from totalMusics: TotalMyMusics) -> [MyMusicsSectionType] { + return totalMusics.musics.map { myMusics in + MyMusicsSectionType( + section: .musics(date: myMusics.date), + items: myMusics.musics + ) + } + } }