diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Home/ViewControllers/MyPageViewController.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Home/ViewControllers/MyPageViewController.swift new file mode 100644 index 00000000..546396e4 --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Home/ViewControllers/MyPageViewController.swift @@ -0,0 +1,8 @@ +// +// MyPageViewController.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import Foundation diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/InfoCollectionViewCell.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/InfoCollecitonViewCell.swift similarity index 92% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/InfoCollectionViewCell.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/InfoCollecitonViewCell.swift index b667cb6d..60aad595 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/InfoCollectionViewCell.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/InfoCollecitonViewCell.swift @@ -1,8 +1,8 @@ // -// InfoCollectionViewCell.swift +// InfoCollecitonViewCell.swift // iOS-NOTTODO // -// Created by JEONGEUN KIM on 2023/03/08. +// Created by JEONGEUN KIM on 3/15/24. // import UIKit @@ -81,7 +81,7 @@ extension InfoCollectionViewCell { } } - func configureWithIcon(with model: InfoModel) { + func configureWithIcon(with model: MyPageRowData) { iconImage.image = model.image titleLabel.text = model.title @@ -91,7 +91,7 @@ extension InfoCollectionViewCell { } } - func configure(with model: InfoModel, isHidden: Bool) { + func configure(with model: MyPageRowData, isHidden: Bool) { horizontalStackView.removeArrangedSubview(iconImage) iconImage.removeFromSuperview() titleLabel.text = model.title diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyInfoHeaderView.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyPageHeaderView.swift similarity index 84% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyInfoHeaderView.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyPageHeaderView.swift index aecc8d6d..30035638 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyInfoHeaderView.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyPageHeaderView.swift @@ -1,8 +1,8 @@ // -// MyInfoHeaderCollectionReusableView.swift +// MyPageHeaderView.swift // iOS-NOTTODO // -// Created by JEONGEUN KIM on 2023/03/08. +// Created by JEONGEUN KIM on 3/15/24. // import UIKit @@ -10,7 +10,7 @@ import UIKit import Then import SnapKit -final class MyInfoHeaderView: UICollectionReusableView { +final class MyPageHeaderView: UICollectionReusableView { // MARK: - Properties @@ -36,7 +36,7 @@ final class MyInfoHeaderView: UICollectionReusableView { // MARK: - Methods -extension MyInfoHeaderView { +extension MyPageHeaderView { private func setUI() { myInfoLabel.do { diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyProfileCollectionViewCell.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyProfileCollectionViewCell.swift similarity index 94% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyProfileCollectionViewCell.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyProfileCollectionViewCell.swift index 2bdefe65..49a80b41 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyProfileCollectionViewCell.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyProfileCollectionViewCell.swift @@ -1,8 +1,8 @@ // -// MyInfoCollectionViewCell.swift +// MyProfileCollectionViewCell.swift // iOS-NOTTODO // -// Created by JEONGEUN KIM on 2023/03/08. +// Created by JEONGEUN KIM on 3/15/24. // import UIKit @@ -82,7 +82,7 @@ extension MyProfileCollectionViewCell { } } - func configure(model: InfoModel) { + func configure(model: MyPageRowData) { logoImage.image = model.image userLabel.text = model.user emailLabel.text = model.email diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Model/MyPageModel.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Model/MyPageModel.swift new file mode 100644 index 00000000..8b32b08d --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Model/MyPageModel.swift @@ -0,0 +1,53 @@ +// +// MyPageModel.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import UIKit + +struct MyPageModel: Hashable { + let sections: [Section] + + enum Section: CaseIterable { + case profile, support, info, version + + var rows: [MyPageRowData] { + switch self { + case .profile: + return MyPageRowData.profile + case .support: + return MyPageRowData.support + case .info: + return MyPageRowData.info + case .version: + return MyPageRowData.version() + } + } + } +} + +struct MyPageRowData: Hashable { + + var image: UIImage? + var user: String? + var email: String? + var title: String? + + static var profile: [MyPageRowData] = [MyPageRowData(image: .imgUser, + user: UserDefaults.standard.bool(forKey: DefaultKeys.isAppleLogin) ? KeychainUtil.getAppleUsername() : KeychainUtil.getKakaoNickname(), + email: UserDefaults.standard.bool(forKey: DefaultKeys.isAppleLogin) ? KeychainUtil.getAppleEmail() : KeychainUtil.getKakaoEmail())] + + static let support: [MyPageRowData] = [MyPageRowData(image: .icGuide, title: I18N.guide), + MyPageRowData(image: .icQuestion1, title: I18N.oftenQuestion) + ] + + static let info: [MyPageRowData] = [MyPageRowData(title: I18N.notice), + MyPageRowData(title: I18N.sendFeedback), + MyPageRowData(title: I18N.inquiry), + MyPageRowData(title: I18N.policies) + ] + + static func version() -> [MyPageRowData] { return [MyPageRowData(title: I18N.version + " " + (Utils.version ?? "1.0.0"))] } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Viewcontrollers/MyInfoViewController.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewControllers/MyPageViewController.swift similarity index 67% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Viewcontrollers/MyInfoViewController.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewControllers/MyPageViewController.swift index a257c00a..9e6bb589 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Viewcontrollers/MyInfoViewController.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewControllers/MyPageViewController.swift @@ -1,38 +1,45 @@ // -// MyInfoViewController.swift +// MyPageViewController.swift // iOS-NOTTODO // -// Created by 강윤서 on 2023/02/15. +// Created by JEONGEUN KIM on 3/15/24. // import UIKit +import Combine import Then import SnapKit -final class MyInfoViewController: UIViewController { +final class MyPageViewController: UIViewController { // MARK: - Properties typealias CellRegistration = UICollectionView.CellRegistration typealias HeaderRegistration = UICollectionView.SupplementaryRegistration - typealias DataSource = UICollectionViewDiffableDataSource - typealias SnapShot = NSDiffableDataSourceSnapshot + typealias DataSource = UICollectionViewDiffableDataSource + typealias Snapshot = NSDiffableDataSourceSnapshot enum Sections: Int, CaseIterable { case profile, support, info, version } + private let viewWillAppearSubject = PassthroughSubject() + private let profilCellTapped = PassthroughSubject() + private var cancelBag = Set() + private var dataSource: DataSource? + private let viewModel: any MyPageViewModel - private lazy var safeArea = self.view.safeAreaLayoutGuide + // MARK: - UI Components - private weak var coordinator: MypageCoordinator? + private lazy var safeArea = self.view.safeAreaLayoutGuide + private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: .init()) // MARK: - init - init(coordinator: MypageCoordinator) { - self.coordinator = coordinator + init(viewModel: some MyPageViewModel) { + self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } @@ -40,12 +47,14 @@ final class MyInfoViewController: UIViewController { fatalError("init(coder:) has not been implemented") } - // MARK: - UI Components - - private let myInfoCollectionView = UICollectionView(frame: .zero, collectionViewLayout: .init()) - // MARK: - Life Cycle + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + viewWillAppearSubject.send(()) + } + override func viewDidLoad() { super.viewDidLoad() @@ -53,18 +62,18 @@ final class MyInfoViewController: UIViewController { setUI() setLayout() setupDataSource() - setSnapShot() + setBindings() } } // MARK: - Methods -extension MyInfoViewController { +extension MyPageViewController { private func setUI() { view.backgroundColor = .ntdBlack - myInfoCollectionView.do { + collectionView.do { $0.collectionViewLayout = layout() $0.backgroundColor = .clear $0.bounces = false @@ -75,9 +84,9 @@ extension MyInfoViewController { } private func setLayout() { - view.addSubview(myInfoCollectionView) + view.addSubview(collectionView) - myInfoCollectionView.snp.makeConstraints { + collectionView.snp.makeConstraints { $0.directionalHorizontalEdges.equalTo(safeArea).inset(22) $0.directionalVerticalEdges.equalTo(safeArea) } @@ -85,11 +94,11 @@ extension MyInfoViewController { private func setupDataSource() { - let profileCellRegistration = CellRegistration {cell, _, item in + let profileCellRegistration = CellRegistration {cell, _, item in cell.configure(model: item) } - let infoCellRegistration = CellRegistration {cell, indexPath, item in + let infoCellRegistration = CellRegistration {cell, indexPath, item in guard let section = Sections(rawValue: indexPath.section) else { return } @@ -103,9 +112,9 @@ extension MyInfoViewController { } } - let headerRegistration = HeaderRegistration(elementKind: UICollectionView.elementKindSectionHeader) { _, _, _ in } + let headerRegistration = HeaderRegistration(elementKind: UICollectionView.elementKindSectionHeader) { _, _, _ in } - dataSource = DataSource(collectionView: myInfoCollectionView, cellProvider: { collectionView, indexPath, item in + dataSource = DataSource(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in guard let section = Sections(rawValue: indexPath.section) else { return UICollectionViewCell() } @@ -126,24 +135,18 @@ extension MyInfoViewController { } } - private func setSnapShot() { - - var snapShot = SnapShot() + private func applySnapshot(data: MyPageModel) { + var snapshot = Snapshot() - defer { - dataSource?.apply(snapShot, animatingDifferences: false) + data.sections.forEach { section in + snapshot.appendSections([section]) + snapshot.appendItems(section.rows, toSection: section) } - snapShot.appendSections(Sections.allCases) - snapShot.appendItems(InfoModel.profile, toSection: .profile) - snapShot.appendItems(InfoModel.support, toSection: .support) - snapShot.appendItems(InfoModel.info, toSection: .info) - snapShot.appendItems(InfoModel.version(), toSection: .version) - + dataSource?.apply(snapshot, animatingDifferences: false) } private func layout() -> UICollectionViewLayout { - let layout = UICollectionViewCompositionalLayout { sectionIndex, env in guard let section = Sections(rawValue: sectionIndex) else { return nil } @@ -159,30 +162,40 @@ extension MyInfoViewController { return CompositionalLayout.setUpSection(layoutEnvironment: env, topContentInset: 18, bottomContentInset: 60) - } - } return layout } + + func setBindings() { + + let input = MyPageViewModelInput(viewWillAppearSubject: viewWillAppearSubject, profileCellTapped: profilCellTapped) + let output = viewModel.transform(input: input) + output.viewWillAppearSubject + .receive(on: RunLoop.main) + .sink { [weak self] in + self?.applySnapshot(data: $0) + } + .store(in: &cancelBag) + } } // MARK: - CollectionViewDelegate -extension MyInfoViewController: UICollectionViewDelegate { +extension MyPageViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { switch indexPath.section { case 0: - profileSectionSelection() + self.profileSectionSelection() case 1: - infoSectionSelection(for: indexPath, - events: [.clickGuide, .clickFaq], - urls: [.guid, .faq]) + self.infoSectionSelection(for: indexPath, + events: [.clickGuide, .clickFaq], + urls: [.guid, .faq]) case 2: - infoSectionSelection(for: indexPath, - events: [.clickNotice, .clickSuggestion, .clickQuestion, .clickTerms], - urls: [.notice, .suggestoin, .question, .service]) + self.infoSectionSelection(for: indexPath, + events: [.clickNotice, .clickSuggestion, .clickQuestion, .clickTerms], + urls: [.notice, .suggestoin, .question, .service]) default: return } @@ -190,7 +203,7 @@ extension MyInfoViewController: UICollectionViewDelegate { private func profileSectionSelection() { sendAnalyticsEvent(.clickMyInfo) { - coordinator?.showMyInfoAccountViewController() + profilCellTapped.send(()) } } diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModel.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModel.swift new file mode 100644 index 00000000..1ec3d6f9 --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModel.swift @@ -0,0 +1,22 @@ +// +// MyPageViewModel.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import Foundation +import Combine + +protocol MyPageViewModellPresentable {} + +protocol MyPageViewModel: ViewModel where Input == MyPageViewModelInput, Output == MyPageViewModelOutput {} + +struct MyPageViewModelInput { + let viewWillAppearSubject: PassthroughSubject + let profileCellTapped: PassthroughSubject +} + +struct MyPageViewModelOutput { + let viewWillAppearSubject: AnyPublisher +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModelImpl.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModelImpl.swift new file mode 100644 index 00000000..5835b96e --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModelImpl.swift @@ -0,0 +1,40 @@ +// +// MyPageViewModelImpl.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import Foundation +import Combine + +final class MyPageViewModelImpl: MyPageViewModel { + + private weak var coordinator: MypageCoordinator? + private var cancelBag = Set() + + init(coordinator: MypageCoordinator) { + self.coordinator = coordinator + } + + func transform(input: MyPageViewModelInput) -> MyPageViewModelOutput { + let viewWillAppearSubject = input.viewWillAppearSubject + .map { _ -> MyPageModel in + return MyPageModel(sections: [ + .profile, + .support, + .info, + .version + ]) + } + .eraseToAnyPublisher() + + input.profileCellTapped + .sink { [weak self] _ in + self?.coordinator?.showMyInfoAccountViewController() + } + .store(in: &cancelBag) + + return Output(viewWillAppearSubject: viewWillAppearSubject) + } +}