diff --git a/iBox/Sources/Extension/UIView+Extension.swift b/iBox/Sources/Extension/UIView+Extension.swift index 5841271..77e401b 100644 --- a/iBox/Sources/Extension/UIView+Extension.swift +++ b/iBox/Sources/Extension/UIView+Extension.swift @@ -19,3 +19,15 @@ extension Then where Self: AnyObject { } extension UIView: Then {} + +extension UIView { + + func toUserInterfaceStyle(_ theme: Theme) -> UIUserInterfaceStyle { + switch theme { + case .light: return UIUserInterfaceStyle.light + case .dark: return UIUserInterfaceStyle.dark + case .system: return UIUserInterfaceStyle.unspecified + } + } + +} diff --git a/iBox/Sources/Model/MyPageItem.swift b/iBox/Sources/Model/MyPageItem.swift index 881cc26..9c695c6 100644 --- a/iBox/Sources/Model/MyPageItem.swift +++ b/iBox/Sources/Model/MyPageItem.swift @@ -5,7 +5,7 @@ // Created by jiyeon on 1/3/24. // -import UIKit +import Foundation struct MyPageSection { var title: String @@ -15,5 +15,4 @@ struct MyPageSection { struct MyPageItem { var title: String var description: String? - var viewController: UIViewController? } diff --git a/iBox/Sources/Model/Theme.swift b/iBox/Sources/Model/Theme.swift index 413e934..aceb433 100644 --- a/iBox/Sources/Model/Theme.swift +++ b/iBox/Sources/Model/Theme.swift @@ -20,19 +20,11 @@ enum Theme: Codable, CaseIterable { } } - func toImage() -> UIImage? { + func toImageString() -> String { switch self { - case .light: UIImage(systemName: "circle") - case .dark: UIImage(systemName: "circle.fill") - case .system: UIImage(systemName: "circle.righthalf.filled") - } - } - - func toUserInterfaceStyle() -> UIUserInterfaceStyle { - switch self { - case .light: UIUserInterfaceStyle.light - case .dark: UIUserInterfaceStyle.dark - case .system: UIUserInterfaceStyle.unspecified + case .light: "circle" + case .dark: "circle.fill" + case .system: "circle.righthalf.filled" } } } diff --git a/iBox/Sources/Presenter/MyPage/MyPageView.swift b/iBox/Sources/Presenter/MyPage/MyPageView.swift index 976188a..ee07b29 100644 --- a/iBox/Sources/Presenter/MyPage/MyPageView.swift +++ b/iBox/Sources/Presenter/MyPage/MyPageView.swift @@ -9,6 +9,11 @@ import UIKit class MyPageView: BaseView { + // MARK: - Properties + + var delegate: MyPageViewDelegate? + private var viewModel: MyPageViewModel? + // MARK: - UI let profileView = UIView().then { @@ -38,8 +43,27 @@ class MyPageView: BaseView { $0.sectionHeaderTopPadding = 0 } + // MARK: - Initializer + + override init(frame: CGRect) { + super.init(frame: frame) + setupProperties() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: - BaseViewProtocol + private func setupProperties() { + tableView.delegate = self + tableView.dataSource = self + + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(profileViewTapped)) + profileView.addGestureRecognizer(tapGesture) + } + override func configureUI() { addSubview(profileView) profileView.addSubview(profileImageView) @@ -75,4 +99,67 @@ class MyPageView: BaseView { } } + // MARK: - Bind ViewModel + + func bindViewModel(_ viewModel: MyPageViewModel) { + self.viewModel = viewModel + } + + // MARK: - functions + + @objc func profileViewTapped() { + delegate?.pushViewController(ProfileViewController()) + } + +} + +extension MyPageView: UITableViewDelegate, UITableViewDataSource { + + // 테이블 뷰의 섹션 개수 설정 + 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 + } + + // 섹션 헤더의 View 설정 + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let headerView = UIView() + headerView.backgroundColor = .systemGroupedBackground + return headerView + } + + // 섹션 헤더의 높이 설정 + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 10 + } + + // 테이블 뷰 셀이 선택되었을 때 실행되는 메서드 + 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) + } + } diff --git a/iBox/Sources/Presenter/MyPage/MyPageViewController.swift b/iBox/Sources/Presenter/MyPage/MyPageViewController.swift index 0858dc7..886a2f6 100644 --- a/iBox/Sources/Presenter/MyPage/MyPageViewController.swift +++ b/iBox/Sources/Presenter/MyPage/MyPageViewController.swift @@ -7,32 +7,24 @@ import UIKit +protocol MyPageViewDelegate { + func pushViewController(_ indexPath: IndexPath) + func pushViewController(_ viewController: UIViewController) +} + class MyPageViewController: BaseNavigationBarViewController { - // MARK: - properties + // MARK: - Properties - var myPageSections: [MyPageSection] = [ - .init(title: "settings", items: [ - MyPageItem(title: "테마", viewController: ThemeViewController()) - ]), - .init(title: "help", items: [ - MyPageItem(title: "이용 가이드"), - MyPageItem(title: "앱 피드백"), - MyPageItem(title: "개발자 정보", description: "지쿠 😆✌🏻") - ]) - ] + private let viewModel = MyPageViewModel() // MARK: - life cycle override func viewDidLoad() { super.viewDidLoad() guard let contentView = contentView as? MyPageView else { return } - - contentView.tableView.delegate = self - contentView.tableView.dataSource = self - - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(profileViewTapped)) - contentView.profileView.addGestureRecognizer(tapGesture) + contentView.delegate = self + contentView.bindViewModel(viewModel) } // MARK: - BaseNavigationBarViewControllerProtocol @@ -41,58 +33,24 @@ class MyPageViewController: BaseNavigationBarViewController { setNavigationBarTitleLabelText("My Page") } - // MARK: - functions - - @objc func profileViewTapped(_ gesture: UITapGestureRecognizer) { - let viewController = ProfileViewController() - navigationController?.pushViewController(viewController, animated: true) - } - } -extension MyPageViewController: UITableViewDelegate, UITableViewDataSource { - - // 테이블 뷰의 섹션 개수 설정 - func numberOfSections(in tableView: UITableView) -> Int { - return myPageSections.count - } - - // 테이블 뷰의 행 개수 설정 - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return myPageSections[section].items.count - } - - // 테이블 뷰 셀 구성 - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "MyPageItemCell") - as? MyPageItemCell else { return UITableViewCell() } - let item = myPageSections[indexPath.section].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 - } - - // 섹션 헤더의 View 설정 - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let headerView = UIView() - headerView.backgroundColor = .systemGroupedBackground - return headerView - } - - // 섹션 헤더의 높이 설정 - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return 10 - } - - // 테이블 뷰 셀이 선택되었을 때 실행되는 메서드 - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let item = myPageSections[indexPath.section].items[indexPath.row] - guard let viewController = item.viewController else { return } +extension MyPageViewController: MyPageViewDelegate { + + func pushViewController(_ indexPath: IndexPath) { + if indexPath.section == 0 { + navigationController?.pushViewController(ThemeViewController(), animated: true) + } else { + switch indexPath.row { + case 0: print("이용 가이드 탭 !") + case 1: print("앱 피드백 탭 !") + case 2: print("개발자 정보 탭 !") + default: break; + } + } + } + + func pushViewController(_ viewController: UIViewController) { navigationController?.pushViewController(viewController, animated: true) } diff --git a/iBox/Sources/Presenter/MyPage/Theme/ThemeCell.swift b/iBox/Sources/Presenter/MyPage/Theme/ThemeCell.swift index 8a2cbdb..8d1997a 100644 --- a/iBox/Sources/Presenter/MyPage/Theme/ThemeCell.swift +++ b/iBox/Sources/Presenter/MyPage/Theme/ThemeCell.swift @@ -64,7 +64,7 @@ class ThemeCell: UITableViewCell, BaseViewProtocol { func bind(_ theme: Theme) { titleLabel.text = theme.toString() - themeImageView.image = theme.toImage() + themeImageView.image = UIImage(systemName: theme.toImageString()) } func setupSelectButton(_ selected: Bool) { diff --git a/iBox/Sources/Presenter/MyPage/Theme/ThemeView.swift b/iBox/Sources/Presenter/MyPage/Theme/ThemeView.swift index 9d005e0..0d42e03 100644 --- a/iBox/Sources/Presenter/MyPage/Theme/ThemeView.swift +++ b/iBox/Sources/Presenter/MyPage/Theme/ThemeView.swift @@ -5,12 +5,18 @@ // Created by jiyeon on 1/3/24. // +import Combine import UIKit import SnapKit class ThemeView: BaseView { + // MARK: - Properties + + private var viewModel: ThemeViewModel? + private var cancellables = Set() + // MARK: - UI let tableView = UITableView().then { @@ -19,8 +25,24 @@ class ThemeView: BaseView { $0.sectionHeaderTopPadding = 0 } + // MARK: - Initializer + + override init(frame: CGRect) { + super.init(frame: frame) + setupProperties() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: - BaseViewProtocol + private func setupProperties() { + tableView.delegate = self + tableView.dataSource = self + } + override func configureUI() { addSubview(tableView) @@ -29,4 +51,49 @@ class ThemeView: BaseView { } } + // MARK: - Bind ViewModel + + func bineViewModel(_ viewModel: ThemeViewModel) { + self.viewModel = viewModel + viewModel.$selectedIndex + .receive(on: RunLoop.main) + .sink { [weak self] selectedIndex in + guard let window = self?.window else { return } + UserDefaultsManager.theme.value = Theme.allCases[selectedIndex] + window.overrideUserInterfaceStyle = self?.toUserInterfaceStyle(UserDefaultsManager.theme.value) ?? .unspecified + self?.tableView.reloadData() + }.store(in: &cancellables) + } + +} + +extension ThemeView: UITableViewDelegate, UITableViewDataSource { + + // 테이블 뷰의 행 개수 설정 + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return Theme.allCases.count + } + + // 테이블 뷰 셀 구성 + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let viewModel = viewModel, + let cell = tableView.dequeueReusableCell(withIdentifier: "ThemeCell") + as? ThemeCell else { return UITableViewCell() } + let theme = Theme.allCases[indexPath.row] + cell.bind(theme) + cell.setupSelectButton(viewModel.selectedIndex == indexPath.row) + return cell + } + + // 셀의 높이 설정 + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 55 + } + + // 테이블 뷰 셀이 선택되었을 때 실행되는 메서드 + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let viewModel = viewModel else { return } + viewModel.selectedIndex = indexPath.row + } + } diff --git a/iBox/Sources/Presenter/MyPage/Theme/ThemeViewController.swift b/iBox/Sources/Presenter/MyPage/Theme/ThemeViewController.swift index 5bacd40..ca443fc 100644 --- a/iBox/Sources/Presenter/MyPage/Theme/ThemeViewController.swift +++ b/iBox/Sources/Presenter/MyPage/Theme/ThemeViewController.swift @@ -11,7 +11,7 @@ class ThemeViewController: BaseNavigationBarViewController { // MARK: - properties - var selected = UserDefaultsManager.theme.value + private let viewModel = ThemeViewModel() // MARK: - life cycle @@ -20,8 +20,7 @@ class ThemeViewController: BaseNavigationBarViewController { setupNavigationBar() guard let contentView = contentView as? ThemeView else { return } - contentView.tableView.delegate = self - contentView.tableView.dataSource = self + contentView.bineViewModel(viewModel) } // MARK: - BaseNavigationBarViewControllerProtocol @@ -33,36 +32,3 @@ class ThemeViewController: BaseNavigationBarViewController { } } - -extension ThemeViewController: UITableViewDelegate, UITableViewDataSource { - - // 테이블 뷰의 행 개수 설정 - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return Theme.allCases.count - } - - // 테이블 뷰 셀 구성 - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "ThemeCell") - as? ThemeCell else { return UITableViewCell() } - let theme = Theme.allCases[indexPath.row] - cell.bind(theme) - cell.setupSelectButton(theme == selected) - return cell - } - - // 셀의 높이 설정 - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 55 - } - - // 테이블 뷰 셀이 선택되었을 때 실행되는 메서드 - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - selected = Theme.allCases[indexPath.row] - guard let window = self.view.window else { return } - window.overrideUserInterfaceStyle = selected.toUserInterfaceStyle() - tableView.reloadData() // 다시 그리기 - UserDefaultsManager.theme.value = selected - } - -} diff --git a/iBox/Sources/SceneDelegate.swift b/iBox/Sources/SceneDelegate.swift index 1b368ff..94e635e 100644 --- a/iBox/Sources/SceneDelegate.swift +++ b/iBox/Sources/SceneDelegate.swift @@ -18,7 +18,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.windowScene = windowScene // 앱 테마 정보 - window?.overrideUserInterfaceStyle = UserDefaultsManager.theme.value.toUserInterfaceStyle() + window?.overrideUserInterfaceStyle = window?.toUserInterfaceStyle(UserDefaultsManager.theme.value) ?? .unspecified // 나중에 userDefaults에 저장해두고 꺼내와서 preload하기 let urlsToPreload = [ diff --git a/iBox/Sources/ViewModel/MyPageCellViewModel.swift b/iBox/Sources/ViewModel/MyPageCellViewModel.swift new file mode 100644 index 0000000..f5c4c81 --- /dev/null +++ b/iBox/Sources/ViewModel/MyPageCellViewModel.swift @@ -0,0 +1,26 @@ +// +// MyPageCellViewModel.swift +// iBox +// +// Created by jiyeon on 2/22/24. +// + +import Foundation + +class MyPageCellViewModel { + + let model: MyPageItem + + init(_ model: MyPageItem) { + self.model = model + } + + var title: String { + model.title + } + + var description: String? { + model.description + } + +} diff --git a/iBox/Sources/ViewModel/MyPageSectionViewModel.swift b/iBox/Sources/ViewModel/MyPageSectionViewModel.swift new file mode 100644 index 0000000..550d976 --- /dev/null +++ b/iBox/Sources/ViewModel/MyPageSectionViewModel.swift @@ -0,0 +1,19 @@ +// +// MyPageSectionViewModel.swift +// iBox +// +// Created by jiyeon on 2/22/24. +// + +import Foundation + +class MyPageSectionViewModel { + + + let model: MyPageSection + + init(_ model: MyPageSection) { + self.model = model + } + +} diff --git a/iBox/Sources/ViewModel/MyPageViewModel.swift b/iBox/Sources/ViewModel/MyPageViewModel.swift new file mode 100644 index 0000000..75ce095 --- /dev/null +++ b/iBox/Sources/ViewModel/MyPageViewModel.swift @@ -0,0 +1,27 @@ +// +// MyPageViewModel.swift +// iBox +// +// Created by jiyeon on 2/22/24. +// + +import Foundation + +class MyPageViewModel { + + let myPageSectionViewModels: [MyPageSectionViewModel] = [ + MyPageSectionViewModel(MyPageSection( + title: "settings", + items: [MyPageItem(title: "테마")] + )), + MyPageSectionViewModel(MyPageSection( + title: "help", + items: [ + MyPageItem(title: "이용 가이드"), + MyPageItem(title: "앱 피드백"), + MyPageItem(title: "개발자 정보") + ] + )) + ] + +} diff --git a/iBox/Sources/ViewModel/ThemeViewModel.swift b/iBox/Sources/ViewModel/ThemeViewModel.swift new file mode 100644 index 0000000..a89e6ea --- /dev/null +++ b/iBox/Sources/ViewModel/ThemeViewModel.swift @@ -0,0 +1,23 @@ +// +// ThemeViewModel.swift +// iBox +// +// Created by jiyeon on 2/22/24. +// + +import Combine +import Foundation + +class ThemeViewModel { + + @Published var selectedIndex: Int + + init() { + switch UserDefaultsManager.theme.value { + case .light: selectedIndex = 0 + case .dark: selectedIndex = 1 + case .system: selectedIndex = 2 + } + } + +}