import UIKit // MARK: - MainTabBarController final class MainTabBarController: UITabBarController { private let customTabBar = CustomTabBar() override func viewDidLoad() { super.viewDidLoad() tabBar.isHidden = true view.backgroundColor = Theme.Color.background setupCustomTabBar() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() layoutCustomTabBar() } private func setupCustomTabBar() { customTabBar.translatesAutoresizingMaskIntoConstraints = false customTabBar.onTabSelected = { [weak self] index in self?.selectedIndex = index } view.addSubview(customTabBar) NSLayoutConstraint.activate([ customTabBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24), customTabBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24), customTabBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12), customTabBar.heightAnchor.constraint(equalToConstant: 64) ]) } private func layoutCustomTabBar() { view.bringSubviewToFront(customTabBar) } // Call this after setting viewControllers to sync selection override var selectedIndex: Int { didSet { customTabBar.setSelected(selectedIndex) } } override func setTabBarHidden(_ hidden: Bool, animated: Bool = true) { let duration = animated ? 0.25 : 0 UIView.animate(withDuration: duration) { self.customTabBar.alpha = hidden ? 0 : 1 } customTabBar.isUserInteractionEnabled = !hidden } } // MARK: - CustomTabBar private final class CustomTabBar: UIView { var onTabSelected: ((Int) -> Void)? private struct TabItem { let icon: String // SF Symbol name let label: String } private var items: [TabItem] {[ TabItem(icon: "wave.3.right.circle.fill", label: L10n.tabEmoney), TabItem(icon: "gearshape.fill", label: L10n.tabSettings) ]} private var itemViews: [TabItemView] = [] private var selectedIndex: Int = 0 override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } private func setup() { backgroundColor = .white layer.cornerRadius = 32 layer.shadowColor = UIColor.black.cgColor layer.shadowOpacity = 0.10 layer.shadowOffset = CGSize(width: 0, height: 4) layer.shadowRadius = 16 let stack = UIStackView() stack.axis = .horizontal stack.distribution = .fillEqually stack.translatesAutoresizingMaskIntoConstraints = false addSubview(stack) NSLayoutConstraint.activate([ stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8), stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8), stack.topAnchor.constraint(equalTo: topAnchor, constant: 8), stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8) ]) for (index, item) in items.enumerated() { let tabView = TabItemView(icon: item.icon, label: item.label) tabView.isActive = (index == selectedIndex) tabView.tag = index let tap = UITapGestureRecognizer(target: self, action: #selector(tabTapped(_:))) tabView.addGestureRecognizer(tap) stack.addArrangedSubview(tabView) itemViews.append(tabView) } } @objc private func tabTapped(_ gesture: UITapGestureRecognizer) { guard let index = gesture.view?.tag else { return } setSelected(index) onTabSelected?(index) } func setSelected(_ index: Int) { guard index != selectedIndex else { return } selectedIndex = index UIView.animate(withDuration: 0.25, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5) { self.itemViews.enumerated().forEach { i, view in view.isActive = (i == index) view.layoutIfNeeded() } } } } // MARK: - TabItemView private final class TabItemView: UIView { var isActive: Bool = false { didSet { applyState() } } private let iconView = UIImageView() private let labelView = UILabel() private let pill = UIView() init(icon: String, label: String) { super.init(frame: .zero) iconView.image = UIImage(systemName: icon) iconView.contentMode = .scaleAspectFit labelView.text = label labelView.font = Theme.Font.caption(weight: .semibold) labelView.textAlignment = .center setup() } required init?(coder: NSCoder) { fatalError() } private func setup() { pill.layer.cornerRadius = 20 pill.translatesAutoresizingMaskIntoConstraints = false addSubview(pill) let stack = UIStackView(arrangedSubviews: [iconView, labelView]) stack.axis = .vertical stack.spacing = 2 stack.alignment = .center stack.translatesAutoresizingMaskIntoConstraints = false addSubview(stack) NSLayoutConstraint.activate([ pill.centerXAnchor.constraint(equalTo: centerXAnchor), pill.centerYAnchor.constraint(equalTo: centerYAnchor), pill.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.85), pill.heightAnchor.constraint(equalToConstant: 48), stack.centerXAnchor.constraint(equalTo: centerXAnchor), stack.centerYAnchor.constraint(equalTo: centerYAnchor), iconView.widthAnchor.constraint(equalToConstant: 20), iconView.heightAnchor.constraint(equalToConstant: 20) ]) applyState() } private func applyState() { if isActive { pill.backgroundColor = Theme.Color.primary iconView.tintColor = .white labelView.textColor = .white pill.transform = .identity } else { pill.backgroundColor = .clear iconView.tintColor = Theme.Color.textSecondary labelView.textColor = Theme.Color.textSecondary pill.transform = .identity } } }