Created
June 26, 2020 11:13
-
-
Save MihaelIsaev/0c0950ccec4a74fee67ba2b052d3d6eb to your computer and use it in GitHub Desktop.
Custom TabBar implementation for UIKitPlus. Not beautiful cause not wrapped, just sources. But it works well.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<MainViewController>)?.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<HomeTab> | |
init (_ state: UState<HomeTab>) { | |
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<HomeTab> | |
lazy var imageView = Image(image) | |
lazy var titleLabel = Text(title) | |
init(_ type: HomeTab, state: UState<HomeTab>) { | |
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<NavigationController<ViewController1>> { | |
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<NavigationController<ViewController2>> { | |
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<NavigationController<ViewController3>> { | |
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<NavigationController<ViewController4>> { | |
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<NavigationController<ViewController5>> { | |
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 { | |
(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<NavigationController<ViewController1>> { | |
.init(.init(.init(parent)), parent: parent) | |
} | |
fileprivate static func _statistics(_ parent: MainViewController) -> WrappedViewControllerView<NavigationController<ViewController2>> { | |
.init(.init(.init(parent)), parent: parent) | |
} | |
fileprivate static func _trophy(_ parent: MainViewController) -> WrappedViewControllerView<NavigationController<ViewController3>> { | |
.init(.init(.init(parent)), parent: parent) | |
} | |
fileprivate static func _marketplace(_ parent: MainViewController) -> WrappedViewControllerView<NavigationController<ViewController4>> { | |
.init(.init(.init(parent)), parent: parent) | |
} | |
fileprivate static func _balance(_ parent: MainViewController) -> WrappedViewControllerView<NavigationController<ViewController5>> { | |
.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 {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment