hussc / lightCardTabBar

Different Styles of Custom Tab Bar
36 stars 3 forks source link

Appearance Customization #2

Open Bejil opened 3 years ago

Bejil commented 3 years ago

Hi, it would be great to have some appearance customization options. For now i do it this way in SpecialTabBarController:

class SpecialTabBarControllerAppearance {

    public var backgroundColor:UIColor = .white
    public var indicatorColor:UIColor = .black
    public var itemsColor:UIColor = .black
    public var itemsFont:UIFont = .systemFont(ofSize: 14, weight: .bold)
    public var itemsSelectedColor:UIColor = .lightGray
    public var itemsSelectedFont:UIFont = .systemFont(ofSize: 14, weight: .bold)
}

class SpecialTabBarController: TabBarController {

    public static var appearance:SpecialTabBarControllerAppearance = .init()

    override func makeTabBar() -> BaseCardTabBar {
        SpecialCardTabBar()
    }
}

Then, call the properties in the right place in SpecialCardTabBar like this:

override var preferredBottomBackground: UIColor {
    SpecialTabBarController.appearance.backgroundColor
}
Bejil commented 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