diff --git a/iBox/Sources/Model/MyPageItem.swift b/iBox/Sources/Model/MyPageItem.swift index 9c695c6..790c1c1 100644 --- a/iBox/Sources/Model/MyPageItem.swift +++ b/iBox/Sources/Model/MyPageItem.swift @@ -7,12 +7,29 @@ import Foundation -struct MyPageSection { - var title: String - var items: [MyPageItem] +enum MyPageType { + case theme + case homeTab + case preload + case guide + case feedback + case developer + + func toString() -> String { + switch self { + case .theme: "테마" + case .homeTab: "홈화면" + case .preload: "페이지 미리 로드" + case .guide: "이용 가이드" + case .feedback: "앱 피드백" + case .developer: "개발자 정보" + } + } + } struct MyPageItem { - var title: String + var type: MyPageType var description: String? + var flag: Bool? } diff --git a/iBox/Sources/Presenter/MyPage/MyPageItemCell.swift b/iBox/Sources/Presenter/MyPage/MyPageItemCell.swift index c3a3c72..a72402e 100644 --- a/iBox/Sources/Presenter/MyPage/MyPageItemCell.swift +++ b/iBox/Sources/Presenter/MyPage/MyPageItemCell.swift @@ -9,9 +9,12 @@ import UIKit import SnapKit -class MyPageItemCell: UITableViewCell, BaseViewProtocol { +class MyPageItemCell: UITableViewCell { - // MARK: - UI + static let reuseIdentifier = "MyPageItemCell" + private var viewModel: MyPageCellViewModel? + + // MARK: - UI Components let titleLabel = UILabel().then { $0.font = .systemFont(ofSize: 16) @@ -22,6 +25,10 @@ class MyPageItemCell: UITableViewCell, BaseViewProtocol { $0.textColor = .gray } + let switchControl = UISwitch().then { + $0.onTintColor = .box2 + } + let chevronButton = UIButton().then { $0.configuration = .plain() $0.configuration?.image = UIImage(systemName: "chevron.right") @@ -29,41 +36,74 @@ class MyPageItemCell: UITableViewCell, BaseViewProtocol { $0.tintColor = .systemGray3 } - // MARK: - initializer + // MARK: - Initializer override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - configureUI() - selectionStyle = .none // 셀 선택했을 때 회색으로 변하는 것 비활성화 + setupProperty() + setupHierarchy() + setupLayout() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - // MARK: - BaseViewProtocol + // MARK: - Setup Methods - func configureUI() { + private func setupProperty() { backgroundColor = .clear - addSubview(titleLabel) - addSubview(descriptionLabel) - addSubview(chevronButton) - + selectionStyle = .none + } + + private func setupHierarchy() { + contentView.addSubview(titleLabel) + contentView.addSubview(switchControl) + contentView.addSubview(descriptionLabel) + contentView.addSubview(chevronButton) + } + + private func setupLayout() { titleLabel.snp.makeConstraints { $0.left.equalToSuperview().inset(20) $0.centerY.equalToSuperview() } - chevronButton.snp.makeConstraints { - $0.right.equalToSuperview().inset(20) + switchControl.snp.makeConstraints { + $0.right.equalToSuperview().inset(30) $0.centerY.equalToSuperview() - $0.width.height.equalTo(20) } descriptionLabel.snp.makeConstraints { - $0.right.equalTo(chevronButton.snp.left).offset(-10) + $0.right.equalToSuperview().inset(30) $0.centerY.equalToSuperview() } + + chevronButton.snp.makeConstraints { + $0.right.equalToSuperview().inset(20) + $0.centerY.equalToSuperview() + } + } + + // MARK: - Bind ViewModel + + func bindViewModel(_ viewModel: MyPageCellViewModel) { + self.viewModel = viewModel + titleLabel.text = viewModel.title + + descriptionLabel.isHidden = true + switchControl.isHidden = true + chevronButton.isHidden = true + + if let description = viewModel.description { + descriptionLabel.text = description + descriptionLabel.isHidden = false + } else if let flag = viewModel.flag { + switchControl.isOn = flag + switchControl.isHidden = false + } else { + chevronButton.isHidden = false + } } } diff --git a/iBox/Sources/Presenter/MyPage/MyPageView.swift b/iBox/Sources/Presenter/MyPage/MyPageView.swift index c0f57a7..00ae02b 100644 --- a/iBox/Sources/Presenter/MyPage/MyPageView.swift +++ b/iBox/Sources/Presenter/MyPage/MyPageView.swift @@ -8,15 +8,13 @@ import Combine import UIKit -class MyPageView: BaseView { - - // MARK: - Properties +final class MyPageView: BaseView { var delegate: MyPageViewDelegate? private var viewModel: MyPageViewModel? private var cancellables = Set() - // MARK: - UI + // MARK: - UI Components let profileView = UIView().then { $0.isUserInteractionEnabled = true @@ -40,7 +38,7 @@ class MyPageView: BaseView { } let tableView = UITableView().then { - $0.register(MyPageItemCell.self, forCellReuseIdentifier: "MyPageItemCell") + $0.register(MyPageItemCell.self, forCellReuseIdentifier: MyPageItemCell.reuseIdentifier) $0.separatorStyle = .none $0.sectionHeaderTopPadding = 0 $0.backgroundColor = .clear @@ -50,30 +48,38 @@ class MyPageView: BaseView { override init(frame: CGRect) { super.init(frame: frame) - setupProperties() + setupProperty() + setupHierarchy() + setupLayout() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - // MARK: - BaseViewProtocol + // MARK: - Setup Methods - private func setupProperties() { + private func setupProperty() { tableView.delegate = self tableView.dataSource = self - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(profileViewTapped)) - profileView.addGestureRecognizer(tapGesture) + profileView.addGestureRecognizer( + UITapGestureRecognizer( + target: self, + action: #selector(profileViewTapped) + ) + ) } - override func configureUI() { + private func setupHierarchy() { addSubview(profileView) profileView.addSubview(profileImageView) profileView.addSubview(profileLabel) profileView.addSubview(chevronButton) addSubview(tableView) - + } + + private func setupLayout() { profileView.snp.makeConstraints { $0.left.top.right.equalToSuperview() $0.height.equalTo(90) @@ -91,7 +97,7 @@ class MyPageView: BaseView { } chevronButton.snp.makeConstraints { - $0.right.equalToSuperview().inset(20) + $0.right.equalToSuperview().inset(30) $0.centerY.equalToSuperview() $0.width.height.equalTo(20) } @@ -110,67 +116,74 @@ class MyPageView: BaseView { .receive(on: RunLoop.main) .sink { [weak self] event in switch event { - case .updateMyPageSectionViewModels: + case .updateSectionViewModels: self?.tableView.reloadData() } }.store(in: &cancellables) } - // MARK: - functions + // MARK: - Action Functions - @objc func profileViewTapped() { + @objc private func profileViewTapped() { delegate?.pushViewController(ProfileViewController()) } + @objc private func handleSwitchControlTap(_ controlSwitch: UISwitch) { + guard let viewModel = viewModel else { return } + viewModel.input.send(.setPreload(controlSwitch.isOn)) + } + } -extension MyPageView: UITableViewDelegate, UITableViewDataSource { +extension MyPageView: UITableViewDelegate { - // 테이블 뷰의 섹션 개수 설정 func numberOfSections(in tableView: UITableView) -> Int { guard let viewModel = viewModel else { return 0 } - return viewModel.myPageSectionViewModels.count - } - - // 테이블 뷰의 행 개수 설정 - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard let viewModel = viewModel else { return 0 } - return viewModel.myPageSectionViewModels[section].model.items.count - } - - // 테이블 뷰 셀 구성 - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let viewModel = viewModel, - let cell = tableView.dequeueReusableCell(withIdentifier: "MyPageItemCell") - as? MyPageItemCell else { return UITableViewCell() } - let item = viewModel.myPageSectionViewModels[indexPath.section].model.items[indexPath.row] - cell.titleLabel.text = item.title - cell.descriptionLabel.text = item.description - return cell - } - - // 셀의 높이 설정 - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 65 + return viewModel.sectionViewModels.count } - // 섹션 헤더의 View 설정 func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = UIView() headerView.backgroundColor = .backgroundColor return headerView } - // 섹션 헤더의 높이 설정 func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return 10 + return 20 + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 55 } - // 테이블 뷰 셀이 선택되었을 때 실행되는 메서드 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let viewModel = viewModel else { return } - let item = viewModel.myPageSectionViewModels[indexPath.section].model.items[indexPath.row] - delegate?.pushViewController(indexPath) + let myPageItem = viewModel.sectionViewModels[indexPath.section].cellViewModels[indexPath.row].myPageItem + if (myPageItem.type != MyPageType.preload) { + delegate?.pushViewController(myPageItem.type) + } + } + +} + +extension MyPageView: UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + guard let viewModel = viewModel else { return 0 } + return viewModel.sectionViewModels[section].cellViewModels.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let viewModel = viewModel, + let cell = tableView.dequeueReusableCell(withIdentifier: MyPageItemCell.reuseIdentifier) + as? MyPageItemCell else { return UITableViewCell() } + let cellViewModel = viewModel.sectionViewModels[indexPath.section].cellViewModels[indexPath.row] + cell.bindViewModel(cellViewModel) + if cellViewModel.flag != nil { + cell.switchControl.removeTarget(nil, action: nil, for: .valueChanged) + cell.switchControl.addTarget(self, action: #selector(handleSwitchControlTap), for: .valueChanged) + } + return cell } } diff --git a/iBox/Sources/Presenter/MyPage/MyPageViewController.swift b/iBox/Sources/Presenter/MyPage/MyPageViewController.swift index 69349b1..1c6a0f1 100644 --- a/iBox/Sources/Presenter/MyPage/MyPageViewController.swift +++ b/iBox/Sources/Presenter/MyPage/MyPageViewController.swift @@ -8,24 +8,21 @@ import UIKit protocol MyPageViewDelegate { - func pushViewController(_ indexPath: IndexPath) + func pushViewController(_ type: MyPageType) func pushViewController(_ viewController: UIViewController) } -class MyPageViewController: BaseNavigationBarViewController { - - // MARK: - Properties +final class MyPageViewController: BaseNavigationBarViewController { private let viewModel = MyPageViewModel() - // MARK: - life cycle + // MARK: - Life Cycle override func viewDidLoad() { super.viewDidLoad() guard let contentView = contentView as? MyPageView else { return } contentView.delegate = self contentView.bindViewModel(viewModel) - viewModel.input.send(.viewWillAppear) } override func viewWillAppear(_ animated: Bool) { @@ -36,27 +33,26 @@ class MyPageViewController: BaseNavigationBarViewController { // MARK: - BaseNavigationBarViewControllerProtocol override func setupNavigationBar() { - setNavigationBarTitleLabelText("My Page") + setNavigationBarTitleLabelText("마이 페이지") } } extension MyPageViewController: MyPageViewDelegate { - func pushViewController(_ indexPath: IndexPath) { - if indexPath.section == 0 { - switch indexPath.row { - case 0: navigationController?.pushViewController(ThemeViewController(), animated: true) - case 1: navigationController?.pushViewController(HomeTabSelectorViewController(), animated: true) - default: break - } - } else { - switch indexPath.row { - case 0: print("이용 가이드 탭 !") - case 1: print("앱 피드백 탭 !") - case 2: print("개발자 정보 탭 !") - default: break - } + func pushViewController(_ type: MyPageType) { + switch type { + case .theme: + navigationController?.pushViewController(ThemeViewController(), animated: true) + case .homeTab: + navigationController?.pushViewController(HomeTabSelectorViewController(), animated: true) + case .guide: + print("이용 가이드 탭 !") + case .feedback: + print("앱 피드백 탭 !") + case .developer: + print("개발자 정보 탭 !") + default: break } } diff --git a/iBox/Sources/Utils/UserDefaultsManager.swift b/iBox/Sources/Utils/UserDefaultsManager.swift index 5d3e226..48e785d 100644 --- a/iBox/Sources/Utils/UserDefaultsManager.swift +++ b/iBox/Sources/Utils/UserDefaultsManager.swift @@ -20,6 +20,9 @@ final class UserDefaultsManager { @UserDefaultsData(key: "isDefaultDataInserted", defaultValue: false) static var isDefaultDataInserted: Bool + + @UserDefaultsData(key: "isPreload", defaultValue: false) + static var isPreload: Bool } diff --git a/iBox/Sources/ViewModel/MyPageCellViewModel.swift b/iBox/Sources/ViewModel/MyPageCellViewModel.swift index f5c4c81..77a77c4 100644 --- a/iBox/Sources/ViewModel/MyPageCellViewModel.swift +++ b/iBox/Sources/ViewModel/MyPageCellViewModel.swift @@ -9,18 +9,22 @@ import Foundation class MyPageCellViewModel { - let model: MyPageItem + let myPageItem: MyPageItem - init(_ model: MyPageItem) { - self.model = model + init(_ myPageItem: MyPageItem) { + self.myPageItem = myPageItem } var title: String { - model.title + myPageItem.type.toString() + } + + var flag: Bool? { + myPageItem.flag } var description: String? { - model.description + myPageItem.description } } diff --git a/iBox/Sources/ViewModel/MyPageSectionViewModel.swift b/iBox/Sources/ViewModel/MyPageSectionViewModel.swift index 550d976..53b2a81 100644 --- a/iBox/Sources/ViewModel/MyPageSectionViewModel.swift +++ b/iBox/Sources/ViewModel/MyPageSectionViewModel.swift @@ -9,11 +9,10 @@ import Foundation class MyPageSectionViewModel { + let cellViewModels: [MyPageCellViewModel] - let model: MyPageSection - - init(_ model: MyPageSection) { - self.model = model + init(cellViewModels: [MyPageCellViewModel]) { + self.cellViewModels = cellViewModels } } diff --git a/iBox/Sources/ViewModel/MyPageViewModel.swift b/iBox/Sources/ViewModel/MyPageViewModel.swift index 6a414a0..f388ac2 100644 --- a/iBox/Sources/ViewModel/MyPageViewModel.swift +++ b/iBox/Sources/ViewModel/MyPageViewModel.swift @@ -12,10 +12,11 @@ class MyPageViewModel { enum Input { case viewWillAppear + case setPreload(_ isOn: Bool) } enum Output { - case updateMyPageSectionViewModels + case updateSectionViewModels } // MARK: - Properties @@ -23,40 +24,33 @@ class MyPageViewModel { let input = PassthroughSubject() private let output = PassthroughSubject() private var cancellables = Set() - var myPageSectionViewModels = [MyPageSectionViewModel]() + var sectionViewModels = [MyPageSectionViewModel]() func transform(input: AnyPublisher) -> AnyPublisher { input.sink { [weak self] event in switch event { case .viewWillAppear: - self?.myPageSectionViewModels.removeAll() - self?.updateMyPageSectionViewModels() - self?.output.send(.updateMyPageSectionViewModels) + self?.sectionViewModels.removeAll() + self?.updateSectionViewModels() + self?.output.send(.updateSectionViewModels) + case let .setPreload(isOn): + UserDefaultsManager.isPreload = isOn } }.store(in: &cancellables) return output.eraseToAnyPublisher() } - private func updateMyPageSectionViewModels() { - myPageSectionViewModels.append(MyPageSectionViewModel( - MyPageSection( - title: "settings", - items: [ - MyPageItem(title: "테마", description: UserDefaultsManager.theme.toString()), - MyPageItem(title: "홈화면", description: HomeTabType.allCases[UserDefaultsManager.homeTabIndex].toString()) - ] - ) - )) - myPageSectionViewModels.append(MyPageSectionViewModel( - MyPageSection( - title: "help", - items: [ - MyPageItem(title: "이용 가이드"), - MyPageItem(title: "앱 피드백"), - MyPageItem(title: "개발자 정보") - ] - )) - ) + private func updateSectionViewModels() { + sectionViewModels.append(MyPageSectionViewModel(cellViewModels: [ + MyPageCellViewModel(MyPageItem(type: .theme, description: UserDefaultsManager.theme.toString())), + MyPageCellViewModel(MyPageItem(type: .homeTab, description: HomeTabType.allCases[UserDefaultsManager.homeTabIndex].toString())), + MyPageCellViewModel(MyPageItem(type: .preload, flag: UserDefaultsManager.isPreload)) + ])) + sectionViewModels.append(MyPageSectionViewModel(cellViewModels: [ + MyPageCellViewModel(MyPageItem(type: .guide)), + MyPageCellViewModel(MyPageItem(type: .feedback)), + MyPageCellViewModel(MyPageItem(type: .developer)) + ])) } }