diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift index 08b34bbc..6afb2cd9 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift @@ -42,7 +42,7 @@ class CenterMainCoordinator: CenterMainCoordinatable { ) } - let tabController = IdleTabBar() + let tabController = IdleTabBarProto() tabController.setViewControllers(info: tabInfo) tabController.selectedIndex = 0 diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift index 1bdea884..8f1578fb 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift @@ -31,76 +31,61 @@ class WorkerMainCoordinator: ParentCoordinator { func start() { - let tabInfo = WorkerMainScreen.allCases.map { tab in + let pageInfo = IdleWorkerMainPage.allCases.map { page in - TabBarInfo( - viewController: createNavForTab(tab: tab), - tabBarItem: .init( - name: tab.name - ) + let navigationController = createNavForTab(page: page) + + return IdleTabBar.PageTabItemInfo( + page: page, + navigationController: navigationController ) } - let tabController = IdleTabBar() - tabController.setViewControllers(info: tabInfo) - tabController.selectedIndex = 0 + let tabController = IdleTabBar( + initialPage: IdleWorkerMainPage.home, + info: pageInfo + ) + navigationController.pushViewController(tabController, animated: false) } // #1. Tab별 네비게이션 컨트롤러 생성 - func createNavForTab(tab: WorkerMainScreen) -> UINavigationController { + func createNavForTab(page: IdleWorkerMainPage) -> UINavigationController { let tabNavController = UINavigationController() tabNavController.setNavigationBarHidden(false, animated: false) startTabCoordinator( - tab: tab, + page: page, navigationController: tabNavController ) return tabNavController } // #2. 생성한 컨트롤러를 각 탭별 Coordinator에 전달 - func startTabCoordinator(tab: WorkerMainScreen, navigationController: UINavigationController) { + func startTabCoordinator(page: IdleWorkerMainPage, navigationController: UINavigationController) { var coordinator: ChildCoordinator! - switch tab { - case .recruitmentBoard: + switch page { + case .home: coordinator = RecruitmentBoardCoordinator( navigationController: navigationController ) - case .applyManagement: + case .preferredPost: coordinator = ApplyManagementCoordinator( navigationController: navigationController ) case .setting: - coordinator = SettingCoordinator( + coordinator = RecruitmentBoardCoordinator( navigationController: navigationController ) } + addChildCoordinator(coordinator) // 코디네이터들을 실행 coordinator.start() } } - -// MARK: Worker 탭의 구성요소들 -enum WorkerMainScreen: Int, CaseIterable { - case recruitmentBoard = 0 - case applyManagement = 1 - case setting = 2 - - var name: String { - switch self { - case .recruitmentBoard: - "채용" - case .applyManagement: - "공고관리" - case .setting: - "설정" - } - } -} diff --git a/project/Projects/App/Sources/RootCoordinator/RootCoordinator+Extension.swift b/project/Projects/App/Sources/RootCoordinator/RootCoordinator+Extension.swift index a5b5dcc7..3e032225 100644 --- a/project/Projects/App/Sources/RootCoordinator/RootCoordinator+Extension.swift +++ b/project/Projects/App/Sources/RootCoordinator/RootCoordinator+Extension.swift @@ -13,14 +13,18 @@ extension RootCoordinator { /// 로그인및 회원가입을 실행합니다. func auth() { - let authCoordinator = AuthCoordinator( + let coordinator = AuthCoordinator( dependency: .init( navigationController: navigationController, injector: injector ) ) - authCoordinator.start() + coordinator.parent = self + + addChildCoordinator(coordinator) + + coordinator.start() } /// 요양보호사 메인화면을 실행합니다. diff --git a/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift index e0e9da8e..b9fbe6b2 100644 --- a/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift @@ -28,7 +28,7 @@ class RootCoordinator: ParentCoordinator { func start() { navigationController.setNavigationBarHidden(true, animated: false) - centerMain() + workerMain() } func popViewController() { diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_home.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_home.imageset/Contents.json new file mode 100644 index 00000000..4b897f29 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_home.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "worker_home.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_home.imageset/worker_home.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_home.imageset/worker_home.svg new file mode 100644 index 00000000..63da851f --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_home.imageset/worker_home.svg @@ -0,0 +1,3 @@ + + + diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_post.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_post.imageset/Contents.json new file mode 100644 index 00000000..ff1988bd --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_post.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "worker_post.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_post.imageset/worker_post.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_post.imageset/worker_post.svg new file mode 100644 index 00000000..074fa3ef --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_post.imageset/worker_post.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_setting.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_setting.imageset/Contents.json new file mode 100644 index 00000000..f2dbb21c --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_setting.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "worker_setting.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_setting.imageset/worker_setting.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_setting.imageset/worker_setting.svg new file mode 100644 index 00000000..51efca5e --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_setting.imageset/worker_setting.svg @@ -0,0 +1,3 @@ + + + diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBarProto.swift similarity index 95% rename from project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift rename to project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBarProto.swift index 33bcba96..99c407c2 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBarProto.swift @@ -1,5 +1,5 @@ // -// IdleTabBar.swift +// IdleTabBarProto.swift // PresentationCore // // Created by choijunios on 7/25/24. @@ -10,14 +10,14 @@ import RxSwift import RxCocoa import PresentationCore -public class IdleTabBar: UIViewController { +public class IdleTabBarProto: UIViewController { // Coordinator public weak var coordinator: ParentCoordinator? // 탭바구성 public private(set) var viewControllers: [UIViewController] = [] - private var tabBarItems: [IdleTabBarItem] = [] + private var tabBarItems: [IdleTabBarItemProto] = [] // View var tabBarItemStack: UIView! @@ -151,7 +151,7 @@ public protocol IdleTabBarItemViewable: UIView { } -public struct IdleTabBarItem { +public struct IdleTabBarItemProto { let name: String public init(name: String) { @@ -161,9 +161,9 @@ public struct IdleTabBarItem { public struct TabBarInfo { let viewController: UIViewController - let tabBarItem: IdleTabBarItem + let tabBarItem: IdleTabBarItemProto - public init(viewController: UIViewController, tabBarItem: IdleTabBarItem) { + public init(viewController: UIViewController, tabBarItem: IdleTabBarItemProto) { self.viewController = viewController self.tabBarItem = tabBarItem } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/TabBarContainer/IdleTabBarItem.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/TabBarContainer/IdleTabBarItem.swift new file mode 100644 index 00000000..8ebb488a --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/TabBarContainer/IdleTabBarItem.swift @@ -0,0 +1,95 @@ +// +// itemView.swift +// DSKit +// +// Created by choijunios on 8/15/24. +// + +import UIKit +import RxSwift +import RxCocoa + +/// 메인 화면의 탭바 아이템으로 사용됩니다. +public class IdleTabBarItem: TappableUIView { + + // Init + public let index: Int + + // State components + public enum State { + case idle + case accent + } + + // idle + let idleIconColor: UIColor = DSColor.gray300.color + + + // accent + let accentIconColor: UIColor = DSColor.gray700.color + + // View + let label: IdleLabel = { + let label = IdleLabel(typography: .caption) + label.attrTextColor = DSColor.gray700.color + return label + }() + let imageView: UIImageView = { + let view = UIImageView() + view.contentMode = .scaleAspectFit + return view + }() + + public init(index: Int, labelText: String, image: UIImage) { + self.index = index + self.label.textString = labelText + self.imageView.image = image + + super.init() + + setAppearance() + setLayout() + } + public required init?(coder: NSCoder) { nil } + + private func setAppearance() { + imageView.tintColor = idleIconColor + } + + private func setLayout() { + let mainStack = VStack([imageView, label], spacing: 4, alignment: .center) + + self.addSubview(mainStack) + mainStack.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint(equalToConstant: 32), + imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor), + + mainStack.topAnchor.constraint(equalTo: self.topAnchor), + mainStack.leftAnchor.constraint(equalTo: self.leftAnchor), + mainStack.rightAnchor.constraint(equalTo: self.rightAnchor), + mainStack.bottomAnchor.constraint(equalTo: self.bottomAnchor), + ]) + } + + /// 탭바 아이템의 상태를 변경합니다. + public func setState(_ state: State, duration: CGFloat = 0.2) { + UIView.animate(withDuration: duration) { [weak self] in + if state == .accent { + self?.setToAccent() + } else { + self?.setToIdle() + } + } + } + + private func setToIdle() { + imageView.tintColor = idleIconColor + } + + private func setToAccent() { + imageView.tintColor = accentIconColor + } +} + diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/TabBarContainer/TabBarContainer.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/TabBarContainer/TabBarContainer.swift new file mode 100644 index 00000000..ed550753 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/TabBarContainer/TabBarContainer.swift @@ -0,0 +1,65 @@ +// +// TabBarContainer.swift +// DSKit +// +// Created by choijunios on 8/15/24. +// + +import UIKit +import RxSwift +import RxCocoa + +public class IdleTabBarContainer: UIView { + + let items: [UIView] + + public init(items: [UIView]) { + self.items = items + super.init(frame: .zero) + + setAppearance() + setLayout() + } + required init?(coder: NSCoder) { nil } + + func setAppearance() { + self.backgroundColor = DSColor.gray0.color + } + + func setLayout() { + self.layoutMargins = .init( + top: 8, + left: 71.5, + bottom: 8, + right: 71.5 + ) + + let mainStack = HStack( + items, + alignment: .fill, + distribution: .equalCentering + ) + + let border = Spacer(height: 1) + border.backgroundColor = DSColor.gray100.color + + [ + border, + mainStack + ].forEach { + self.addSubview($0) + $0.translatesAutoresizingMaskIntoConstraints = false + } + + NSLayoutConstraint.activate([ + mainStack.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor), + mainStack.leftAnchor.constraint(equalTo: self.layoutMarginsGuide.leftAnchor), + mainStack.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor), + mainStack.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor), + + border.topAnchor.constraint(equalTo: self.topAnchor), + border.leftAnchor.constraint(equalTo: self.leftAnchor), + border.rightAnchor.constraint(equalTo: self.rightAnchor), + ]) + } +} diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Base/DSKitShortcuts.swift b/project/Projects/Presentation/DSKit/Sources/Component/Base/DSKitShortcuts.swift new file mode 100644 index 00000000..e2696ddc --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/Component/Base/DSKitShortcuts.swift @@ -0,0 +1,11 @@ +// +// DSKitShortcuts.swift +// DSKit +// +// Created by choijunios on 8/15/24. +// + +import Foundation + +public typealias DSColor = DSKitAsset.Colors +public typealias DSIcon = DSKitAsset.Icons diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/TabBar/IdleTabBar.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/TabBar/IdleTabBar.swift new file mode 100644 index 00000000..c5b07afe --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/TabBar/IdleTabBar.swift @@ -0,0 +1,160 @@ +// +// IdleTabBar.swift +// DSKit +// +// Created by choijunios on 8/15/24. +// + +import UIKit +import RxSwift +import RxCocoa +import DSKit + +public protocol IdleMainPageItemable: Hashable & CaseIterable { + var tabItemIcon: UIImage { get } + var tabItemText: String { get } + var pageOrderNumber: Int { get } +} + +public class IdleTabBar: UIViewController { + + // 탭바구성 + public private(set) var controllers: [Item: UIViewController] = [:] + public private(set) var items: [Item: IdleTabBarItem] = [:] + + // View + private(set) var displayingVC: UIViewController? + + // Observable + public private(set) var displayingPage: Item? + + // 탭바 컨테이너 + private var tabBarItemContainer: IdleTabBarContainer! + + // Observable + private let disposeBag = DisposeBag() + + public init( + initialPage: Item, + info: [PageTabItemInfo] + ) { + + super.init(nibName: nil, bundle: nil) + + setPageControllers(info) + setPageTabItem() + + setAppearance() + setLayout() + setObservable() + + // 초기세팅 + items[initialPage]?.setState(.accent) + setPage(page: initialPage) + } + + public required init?(coder: NSCoder) { fatalError() } + + private func setAppearance() { + view.backgroundColor = DSColor.gray0.color + } + + private func setLayout() { + view.addSubview(tabBarItemContainer) + tabBarItemContainer.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + tabBarItemContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + tabBarItemContainer.leftAnchor.constraint(equalTo: view.leftAnchor), + tabBarItemContainer.rightAnchor.constraint(equalTo: view.rightAnchor), + ]) + } + + private func setObservable() { + + let selectionPublishers = items.map { (page: Item, itemView: IdleTabBarItem) in + itemView.rx.tap.map { _ in page } + } + + Observable + .merge(selectionPublishers) + .subscribe(onNext: { [weak self] selectedPage in + guard let self else { return } + + // 선택된 화면 표출 + setPage(page: selectedPage) + + // Item들 외향 변경 + items.forEach { (page: Item, itemView: IdleTabBarItem) in + + itemView.setState(selectedPage == page ? .accent : .idle) + } + }) + .disposed(by: disposeBag) + + } +} + +public extension IdleTabBar { + + struct PageTabItemInfo { + let page: Item + let navigationController: UINavigationController + + public init(page: Item, navigationController: UINavigationController) { + self.page = page + self.navigationController = navigationController + } + } + + /// #1. 현재 컨트롤러에 페이지 컨트롤러들을 세팅합니다. + private func setPageControllers(_ using: [PageTabItemInfo]) { + using.forEach { info in + let controller = info.navigationController + addChild(controller) + controller.didMove(toParent: self) + controllers[info.page] = controller + } + } + + /// #2. 페이지별 탭바 아이템뷰를 설정합니다. + private func setPageTabItem() { + + let pages = controllers.keys.sorted { $0.pageOrderNumber < $1.pageOrderNumber } + + let itemViews = pages.map { page in + let item = IdleTabBarItem( + index: page.pageOrderNumber, + labelText: page.tabItemText, + image: page.tabItemIcon + ) + items[page] = item + return item + } + + tabBarItemContainer = IdleTabBarContainer( + items: itemViews + ) + } + + /// 해당 함수는 뷰모델에 의해서만 호출됩니다. 특정 페이지를 display합니다. + private func setPage(page: Item) { + + displayingVC?.view.removeFromSuperview() + + let willDisplayVC = controllers[page]! + let willDisplayView = willDisplayVC.view! + + view.addSubview(willDisplayView) + willDisplayView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + willDisplayView.topAnchor.constraint(equalTo: view.topAnchor), + willDisplayView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + willDisplayView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + willDisplayView.bottomAnchor.constraint(equalTo: tabBarItemContainer.topAnchor) + ]) + + displayingVC = willDisplayVC + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/TabBar/IdleWorkerMainPage.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/TabBar/IdleWorkerMainPage.swift new file mode 100644 index 00000000..b8d64506 --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/TabBar/IdleWorkerMainPage.swift @@ -0,0 +1,63 @@ +// +// IdleWorkerMainPage.swift +// DSKit +// +// Created by choijunios on 8/15/24. +// + +import UIKit +import DSKit + +public enum IdleWorkerMainPage: IdleMainPageItemable { + + public enum State { + case idle + case accent + } + + case home + case preferredPost + case setting + + public init?(index: Int) { + switch index { + case 0: self = .home + case 1: self = .preferredPost + case 2: self = .setting + default: return nil + } + } + + public var pageOrderNumber: Int { + switch self { + case .home: + return 0 + case .preferredPost: + return 1 + case .setting: + return 2 + } + } + + public var tabItemIcon: UIImage { + switch self { + case .home: + return DSIcon.workerHome.image + case .preferredPost: + return DSIcon.workerPost.image + case .setting: + return DSIcon.workerSetting.image + } + } + + public var tabItemText: String { + switch self { + case .home: + "홈" + case .preferredPost: + "공고" + case .setting: + "설정" + } + } +}