Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save MihaelIsaev/0c0950ccec4a74fee67ba2b052d3d6eb to your computer and use it in GitHub Desktop.
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.
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