import UIKitPlus class MainViewController: ViewController { override var statusBarStyle: StatusBarStyle { if let controller = controllers[current]?.protocolController { if let controller = controller as? ViewController { return controller.statusBarStyle } return .from(controller.preferredStatusBarStyle) } return .default } lazy var home = WrappedViewControllerView.home(self) lazy var statistics = WrappedViewControllerView.statistics(self) lazy var trophy = WrappedViewControllerView.trophy(self) lazy var marketplace = WrappedViewControllerView.marketplace(self) lazy var balance = WrappedViewControllerView.balance(self) typealias Controllers = [HomeTab: WrappedViewControllerable] lazy var controllers: Controllers = [.home: home, .statistics: statistics, .trophy: trophy, .marketplace: marketplace, .balance: balance] @UState var current: HomeTab = .home lazy var bottomBar = BottomTabBar($current) var presentingGoalEndedView = false override func buildUI() { super.buildUI() view.backgroundColor = 0xf5f6f8.color body { home statistics trophy marketplace balance bottomBar } switchToController(current) $current.listen { old, new in guard old != new else { return } self.switchToController(new) } } func switchToController(_ new: HomeTab) { controllers.forEach { $0.value.hidden($0.key != new) } navigationController?.setNeedsStatusBarAppearanceUpdate() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) (navigationController as? NavigationController)?.isSwipeBackEnabled = true } } #if canImport(SwiftUI) && DEBUG import SwiftUI @available(iOS 13.0, *) struct MainViewController_Preview: PreviewProvider, DeclarativePreview { static var preview: Preview { Preview { MainViewController() } .colorScheme(.dark) .device(.iPhoneX) .language(.en) } } #endif // MARK - AppDelegate extension AppDelegate { static var shared: AppDelegate { UIApplication.shared.delegate as! AppDelegate } } extension AppDelegate { var safeInsets: UIEdgeInsets { window?.safeInsets ?? .zero } } // MARK: - Tab images extension UIImage { static var homeTab: UIImage? { UIImage(named: "tabBarHome") } static var statisticsTab: UIImage? { UIImage(named: "tabBarStatistics") } static var trophyTab: UIImage? { UIImage(named: "tabBarTrophy") } static var marketplaceTab: UIImage? { UIImage(named: "tabBarMarketplace") } static var balanceTab: UIImage? { UIImage(named: "tabBarBalance") } } // MARK: - Tab titles extension String { static var homeTitle: String { String(.en("___"), .ru("___")) } static var statisticsTitle: String { String(.en("___"), .ru("___")) } static var trophyTitle: String { String(.en("___"), .ru("___")) } static var marketplaceTitle: String { String(.en("___"), .ru("___")) } static var balanceTitle: String { String(.en("___"), .ru("___")) } } // MARK: - Tab colors extension UIColor { static var tabBarActive = 0x3EC900.color static var tabBarInactive = UIColor.lightGray } // MARK: - Tab types enum HomeTab { case home, statistics, trophy, marketplace, balance var title: String { switch self { case .home: return .homeTitle case .statistics: return .statisticsTitle case .trophy: return .trophyTitle case .marketplace: return .marketplaceTitle case .balance: return .balanceTitle } } var icon: UIImage? { switch self { case .home: return .homeTab case .statistics: return .statisticsTab case .trophy: return .trophyTab case .marketplace: return .marketplaceTab case .balance: return .balanceTab } } } // MARK: TabBar view class BottomTabBar: UView { var state: UState init (_ state: UState) { self.state = state super.init(frame: .zero) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func buildView() { super.buildView() background(.white) border(.top, 1, 0xF3F3F3) height(84 + AppDelegate.shared.safeInsets.bottom) edgesToSuperview(leading: 0, trailing: 0, bottom: 0) body { UHStack { BottomTabBarButton(.home, state: state) BottomTabBarButton(.statistics, state: state) BottomTabBarButton(.trophy, state: state) BottomTabBarButton(.marketplace, state: state) BottomTabBarButton(.balance, state: state) } .centerXInSuperview() .topToSuperview(16) .height(50) .distribution(.equalSpacing) } } } // MARK: TabBar item view class BottomTabBarButton: UView { let image: UIImage? let title: String let type: HomeTab let state: UState lazy var imageView = Image(image) lazy var titleLabel = Text(title) init(_ type: HomeTab, state: UState) { self.title = type.title self.image = type.icon self.type = type self.state = state super.init(frame: .zero) updateSelection() state.listen(updateSelection) onTapGesture(tapAction) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func buildView() { super.buildView() body { UVStack { imageView .mode(.scaleAspectFit) .size(25) .topToSuperview() .centerXInSuperview() titleLabel .top(to: .bottom, of: imageView, 4) .edgesToSuperview(leading: 0, trailing: 0, bottom: 0) .alignment(.center) .font(.helveticaNeueMedium, 12) }.edgesToSuperview().width(75 !! .iPhone6(70) !! .iPhone5(70)) } } func updateSelection() { imageView.tint(state.wrappedValue == type ? .tabBarActive : .tabBarInactive) titleLabel.color(state.wrappedValue == type ? .tabBarActive : .tabBarInactive) } func tapAction() { guard state.wrappedValue != type else { return } ImpactFeedback.selected() state.wrappedValue = type } } // MARK: - Helper extensions extension WrappedViewControllerView { static func home(_ parent: MainViewController) -> WrappedViewControllerView> { let r = improve(_home(parent)).bottom(to: .top, of: parent.bottomBar, -20) r.inner.style(.transparent).hideNavigationBar() return r } static func statistics(_ parent: MainViewController) -> WrappedViewControllerView> { let r = improve(_statistics(parent)).bottom(to: .top, of: parent.bottomBar, -20) r.inner.style(.transparent).hideNavigationBar() return r } static func trophy(_ parent: MainViewController) -> WrappedViewControllerView> { let r = improve(_trophy(parent)).bottom(to: .top, of: parent.bottomBar, -20) r.inner.style(.transparent).hideNavigationBar() return r } static func marketplace(_ parent: MainViewController) -> WrappedViewControllerView> { let r = improve(_marketplace(parent)).bottom(to: .top, of: parent.bottomBar, -20) r.inner.style(.transparent).hideNavigationBar() return r } static func balance(_ parent: MainViewController) -> WrappedViewControllerView> { let r = improve(_balance(parent)).bottom(to: .top, of: parent.bottomBar, -20) r.inner.style(.transparent).hideNavigationBar() return r } static func improve(_ v: V) -> V { (v as? UView)?.edgesToSuperview(top: 0, leading: 0, trailing: 0) return v } } // MARK: Tricky implementation to make generics work extension WrappedViewControllerView { fileprivate static func _home(_ parent: MainViewController) -> WrappedViewControllerView> { .init(.init(.init(parent)), parent: parent) } fileprivate static func _statistics(_ parent: MainViewController) -> WrappedViewControllerView> { .init(.init(.init(parent)), parent: parent) } fileprivate static func _trophy(_ parent: MainViewController) -> WrappedViewControllerView> { .init(.init(.init(parent)), parent: parent) } fileprivate static func _marketplace(_ parent: MainViewController) -> WrappedViewControllerView> { .init(.init(.init(parent)), parent: parent) } fileprivate static func _balance(_ parent: MainViewController) -> WrappedViewControllerView> { .init(.init(.init(parent)), parent: parent) } } // MARK: - Tab Controllers class MainSubController: ViewController { let mainViewController: MainViewController init (_ mainViewController: MainViewController) { self.mainViewController = mainViewController super.init() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } class ViewController1: MainSubController {} class ViewController2: MainSubController {} class ViewController3: MainSubController {} class ViewController4: MainSubController {} class ViewController5: MainSubController {}