Skip to content

Commit

Permalink
[Feat] #244 - Mypage MVVM 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
jeongdung-eo committed Mar 15, 2024
1 parent b81b1be commit f56cb7a
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// MyPageViewController.swift
// iOS-NOTTODO
//
// Created by JEONGEUN KIM on 3/15/24.
//

import Foundation
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -81,7 +81,7 @@ extension InfoCollectionViewCell {
}
}

func configureWithIcon(with model: InfoModel) {
func configureWithIcon(with model: MyPageRowData) {

iconImage.image = model.image
titleLabel.text = model.title
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
//
// MyInfoHeaderCollectionReusableView.swift
// MyPageHeaderView.swift
// iOS-NOTTODO
//
// Created by JEONGEUN KIM on 2023/03/08.
// Created by JEONGEUN KIM on 3/15/24.
//

import UIKit

import Then
import SnapKit

final class MyInfoHeaderView: UICollectionReusableView {
final class MyPageHeaderView: UICollectionReusableView {

// MARK: - Properties

Expand All @@ -36,7 +36,7 @@ final class MyInfoHeaderView: UICollectionReusableView {

// MARK: - Methods

extension MyInfoHeaderView {
extension MyPageHeaderView {

private func setUI() {
myInfoLabel.do {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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"))] }
}
Original file line number Diff line number Diff line change
@@ -1,70 +1,79 @@
//
// 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<Sections, InfoModel>
typealias SnapShot = NSDiffableDataSourceSnapshot<Sections, InfoModel>
typealias DataSource = UICollectionViewDiffableDataSource<MyPageModel.Section, MyPageRowData>
typealias Snapshot = NSDiffableDataSourceSnapshot<MyPageModel.Section, MyPageRowData>

enum Sections: Int, CaseIterable {
case profile, support, info, version
}

private let viewWillAppearSubject = PassthroughSubject<Void, Never>()
private let profilCellTapped = PassthroughSubject<Void, Never>()
private var cancelBag = Set<AnyCancellable>()

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

required init?(coder: NSCoder) {
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()

AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.MyInfo.viewMyInfo)
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
Expand All @@ -75,21 +84,21 @@ 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)
}
}

private func setupDataSource() {

let profileCellRegistration = CellRegistration<MyProfileCollectionViewCell, InfoModel> {cell, _, item in
let profileCellRegistration = CellRegistration<MyProfileCollectionViewCell, MyPageRowData> {cell, _, item in
cell.configure(model: item)
}

let infoCellRegistration = CellRegistration<InfoCollectionViewCell, InfoModel> {cell, indexPath, item in
let infoCellRegistration = CellRegistration<InfoCollectionViewCell, MyPageRowData> {cell, indexPath, item in

guard let section = Sections(rawValue: indexPath.section) else { return }

Expand All @@ -103,9 +112,9 @@ extension MyInfoViewController {
}
}

let headerRegistration = HeaderRegistration<MyInfoHeaderView>(elementKind: UICollectionView.elementKindSectionHeader) { _, _, _ in }
let headerRegistration = HeaderRegistration<MyPageHeaderView>(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() }

Expand All @@ -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 }
Expand All @@ -159,38 +162,48 @@ 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
}
}

private func profileSectionSelection() {
sendAnalyticsEvent(.clickMyInfo) {
coordinator?.showMyInfoAccountViewController()
profilCellTapped.send(())
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<Void, Never>
let profileCellTapped: PassthroughSubject<Void, Never>
}

struct MyPageViewModelOutput {
let viewWillAppearSubject: AnyPublisher<MyPageModel, Never>
}
Loading

0 comments on commit f56cb7a

Please sign in to comment.