Open Bejil opened 3 years ago
After all, I realized that I had certain specific needs that would have required too much customization. I decided to make my own controller. It's an interpretation of SpecialTabBarController
. In mine you can add as many "normal" items you want because left stackView can scroll.
There are still a lot of cases to manage (hiding the tabBar for example) but it is already well advanced.
Thanks for sharing your code and let me imagine how to reach the tabBar I dreamt about 👍
import UIKit
public enum DC_TabBarItem_Type {
case Normal
case Special
}
public class DC_TabBarItem_Appearance {
public static var cornerRadius:CGFloat = 4.0
public static var backgroundColor:UIColor = .clear
public static var selectedBackgroundColor:UIColor = .clear
public static var textColor:UIColor = .black
public static var selectedTextColor:UIColor = .blue
public static var font:UIFont = .boldSystemFont(ofSize: 12)
public static var selectedFont:UIFont = .boldSystemFont(ofSize: 12)
public static var imageColor:UIColor = .black
public static var selectedImageColor:UIColor = .blue
}
public class DC_TabBarItem: UITabBarItem {
public var cornerRadius:CGFloat?
public var backgroundColor:UIColor?
public var selectedBackgroundColor:UIColor?
public var textColor:UIColor?
public var selectedTextColor:UIColor?
public var font:UIFont?
public var selectedFont:UIFont?
public var imageColor:UIColor?
public var selectedImageColor:UIColor?
public var type:DC_TabBarItem_Type = .Normal
}
public class DC_TabBarController: UITabBarController {
private var margins:CGFloat = 15.0
public var cornerRadius:CGFloat = 15.0 {
didSet {
backgroundView.layer.cornerRadius = cornerRadius
}
}
public var backgroundColor:UIColor = .white {
didSet {
backgroundView.backgroundColor = backgroundColor
}
}
public var shadowColor:UIColor = .lightGray {
didSet {
backgroundView.layer.shadowColor = shadowColor.cgColor
}
}
public var shadowOpacity:Float = 0.25 {
didSet {
backgroundView.layer.shadowOpacity = shadowOpacity
}
}
public var indicatorColor:UIColor = .blue {
didSet {
indicatorView.backgroundColor = indicatorColor
}
}
public override var viewControllers: [UIViewController]? {
didSet {
var items:[DC_TabBarItem] = .init()
viewControllers?.forEach({
if let navigationController = $0 as? UINavigationController, let tabBarItem = navigationController.viewControllers.first?.tabBarItem as? DC_TabBarItem {
items.append(tabBarItem)
}
else if let tabBarItem = $0.tabBarItem as? DC_TabBarItem {
items.append(tabBarItem)
}
})
items.forEach({
let button:UIButton = .init()
button.tag = items.firstIndex(of: $0) ?? 0
button.layer.cornerRadius = $0.cornerRadius ?? DC_TabBarItem_Appearance.cornerRadius
button.backgroundColor = $0.backgroundColor ?? DC_TabBarItem_Appearance.backgroundColor
button.setTitleColor($0.textColor ?? DC_TabBarItem_Appearance.textColor, for: .normal)
button.setTitleColor($0.selectedTextColor ?? DC_TabBarItem_Appearance.selectedTextColor, for: .selected)
button.contentEdgeInsets = .init(top: 0, left: margins/2, bottom: 0, right: margins/2)
button.titleLabel?.font = $0.font ?? DC_TabBarItem_Appearance.font
button.imageView?.tintColor = $0.imageColor ?? DC_TabBarItem_Appearance.imageColor
button.setImage($0.image, for: .normal)
button.setImage($0.selectedImage, for: .selected)
button.setTitle($0.title, for: .normal)
button.addAction(.init(handler: { [weak self] action in
var items:[DC_TabBarItem] = .init()
self?.viewControllers?.forEach({
if let navigationController = $0 as? UINavigationController, let tabBarItem = navigationController.viewControllers.first?.tabBarItem as? DC_TabBarItem {
items.append(tabBarItem)
}
else if let tabBarItem = $0.tabBarItem as? DC_TabBarItem {
items.append(tabBarItem)
}
})
((self?.normalButtonStackView.arrangedSubviews ?? .init()) + (self?.specialButtonStackView.arrangedSubviews ?? .init())).compactMap({ $0 as? UIButton }).filter({ $0 != button }).forEach({
$0.isSelected = false
if $0.tag < items.count {
let item = items[$0.tag]
$0.backgroundColor = item.backgroundColor ?? DC_TabBarItem_Appearance.backgroundColor
$0.titleLabel?.font = item.font ?? DC_TabBarItem_Appearance.font
$0.imageView?.tintColor = item.imageColor ?? DC_TabBarItem_Appearance.imageColor
}
})
if button.tag < items.count, !button.isSelected {
let item = items[button.tag]
button.isSelected = true
button.backgroundColor = !button.isSelected ? item.backgroundColor ?? DC_TabBarItem_Appearance.backgroundColor : item.selectedBackgroundColor ?? DC_TabBarItem_Appearance.selectedBackgroundColor
button.titleLabel?.font = !button.isSelected ? item.font ?? DC_TabBarItem_Appearance.font : item.selectedFont ?? DC_TabBarItem_Appearance.selectedFont
button.imageView?.tintColor = !button.isSelected ? item.imageColor ?? DC_TabBarItem_Appearance.imageColor : item.selectedImageColor ?? DC_TabBarItem_Appearance.selectedImageColor
self?.needSelection = false
self?.selectedIndex = button.tag
self?.needSelection = true
if let indicatorView = self?.indicatorView {
UIView.animate(withDuration: 0.3, delay: 0.0, options: [.allowUserInteraction,.curveEaseInOut]) {
indicatorView.snp.remakeConstraints { make in
make.size.equalTo(2*indicatorView.layer.cornerRadius)
make.bottom.equalToSuperview()
make.centerX.equalTo(button)
}
indicatorView.superview?.layoutIfNeeded()
}
}
}
}), for: .touchUpInside)
if $0.type == .Normal {
normalButtonStackView.addArrangedSubview(button)
button.snp.makeConstraints { make in
make.height.equalToSuperview()
}
}
else if $0.type == .Special {
specialButtonStackView.addArrangedSubview(button)
button.snp.makeConstraints { make in
make.height.equalToSuperview()
make.width.equalTo(specialButtonStackView.snp.height)
}
}
})
selectedIndex = 0
}
}
private var needSelection:Bool = true
public override var selectedIndex: Int {
didSet {
if needSelection, let button = (normalButtonStackView.arrangedSubviews + specialButtonStackView.arrangedSubviews).compactMap({ $0 as? UIButton }).first(where: { $0.tag == selectedIndex }) {
button.sendActions(for: .touchUpInside)
}
}
}
public override var selectedViewController: UIViewController? {
didSet {
if needSelection,
let selectedViewController = selectedViewController,
let index = viewControllers?.firstIndex(of: selectedViewController),
let button = (normalButtonStackView.arrangedSubviews + specialButtonStackView.arrangedSubviews).compactMap({ $0 as? UIButton }).first(where: { $0.tag == index }) {
button.sendActions(for: .touchUpInside)
}
}
}
public lazy var backgroundView:UIView = {
let view:UIView = .init()
view.layer.cornerRadius = cornerRadius
view.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
view.layer.shadowColor = shadowColor.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: -2)
view.layer.shadowRadius = view.layer.cornerRadius
view.layer.shadowOpacity = shadowOpacity
view.backgroundColor = backgroundColor
return view
}()
private lazy var normalButtonStackView:UIStackView = {
let stackView:UIStackView = .init()
stackView.axis = .horizontal
stackView.spacing = margins
return stackView
}()
private lazy var specialButtonStackView:UIStackView = {
let stackView:UIStackView = .init()
stackView.axis = .horizontal
stackView.spacing = margins
return stackView
}()
private lazy var indicatorView:UIView = {
let view:UIView = .init()
view.backgroundColor = indicatorColor
view.layer.cornerRadius = 2
return view
}()
public override func loadView() {
super.loadView()
tabBar.isHidden = true
view.addSubview(backgroundView)
backgroundView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(tabBar.snp.height)
}
let contentView:UIView = .init()
backgroundView.addSubview(contentView)
contentView.snp.makeConstraints { make in
make.top.equalToSuperview().inset(margins/2)
make.left.right.equalToSuperview().inset(margins)
make.bottom.equalTo(backgroundView.safeAreaLayoutGuide).inset(margins/2)
}
contentView.addSubview(indicatorView)
let normalButtonScrollView:UIScrollView = .init()
normalButtonScrollView.addSubview(normalButtonStackView)
normalButtonScrollView.showsHorizontalScrollIndicator = false
normalButtonStackView.snp.makeConstraints { (make) in
make.leading.trailing.top.bottom.height.equalToSuperview()
}
let contentStackView:UIStackView = .init(arrangedSubviews: [normalButtonScrollView,specialButtonStackView])
contentStackView.axis = .horizontal
contentView.addSubview(contentStackView)
contentStackView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
backgroundView.layer.shadowColor = shadowColor.cgColor
}
}
You can use it like this in an appDelegate for example:
DC_TabBarItem_Appearance.backgroundColor = .clear
DC_TabBarItem_Appearance.selectedBackgroundColor = Colors.Secondary
DC_TabBarItem_Appearance.textColor = UIColor.white.withAlphaComponent(0.5)
DC_TabBarItem_Appearance.selectedTextColor = .white
DC_TabBarItem_Appearance.font = Fonts.Content.Bold
DC_TabBarItem_Appearance.selectedFont = Fonts.Content.Bold
DC_TabBarItem_Appearance.imageColor = UIColor.white.withAlphaComponent(0.5)
DC_TabBarItem_Appearance.selectedImageColor = .white
let tabBarController:DC_TabBarController = .init()
tabBarController.cornerRadius = 0
tabBarController.backgroundColor = Colors.Primary
tabBarController.indicatorColor = .clear
let profileViewController:DC_Profile_ViewController = .init()
let profileNavigationController:DC_NavigationController = .init(rootViewController: profileViewController)
let jobsViewController:DC_Jobs_ViewController = .init()
let jobsNavigationController:DC_NavigationController = .init(rootViewController: jobsViewController)
let advicesViewController:DC_Advices_ViewController = .init()
let advicesNavigationController:DC_NavigationController = .init(rootViewController: advicesViewController)
let settingsViewController:DC_Settings_ViewController = .init()
let settingsNavigationController:DC_NavigationController = .init(rootViewController: settingsViewController)
let searchViewController:DC_Search_ViewController = .init()
let searchNavigationController:DC_NavigationController = .init(rootViewController: searchViewController)
tabBarController.viewControllers = [profileNavigationController,jobsNavigationController,advicesNavigationController,settingsNavigationController,searchNavigationController]
self?.window?.rootViewController = tabBarController
Hi, it would be great to have some appearance customization options. For now i do it this way in
SpecialTabBarController
:Then, call the properties in the right place in
SpecialCardTabBar
like this: