// // QuickLocationTabBar.swift // QuickLocation // import UIKit protocol QuickLocationTabBarDelegate: AnyObject { func tabBar(_ tabBar: QuickLocationTabBar, didSelectTabAt index: Int) } final class QuickLocationTabBar: UIView { struct TabItem { let title: String let image: UIImage? let selectedImage: UIImage? init(title: String, image: UIImage?, selectedImage: UIImage? = nil) { self.title = title self.image = image self.selectedImage = selectedImage ?? image } } // MARK: - Design Constants (in iOS points, from Lanhu @2x 容器 2) private let barContentHeight: CGFloat = 56 private let containerCornerRadius: CGFloat = 28 private let horizontalInset: CGFloat = 18 private let iconSize: CGFloat = 32 private let iconLabelSpacing: CGFloat = 4 private let labelHeight: CGFloat = 11 private let selectedTextColor = UIColor(hexStr: "#1A1A1A") private let unselectedTextColor = UIColor(hexStr: "#AAAAAA") private let selectedFont = UIFont(name: "PingFangSC-Semibold", size: 11) ?? UIFont.systemFont(ofSize: 11, weight: .semibold) private let unselectedFont = UIFont.systemFont(ofSize: 11, weight: .regular) // MARK: - Properties weak var delegate: QuickLocationTabBarDelegate? private let items: [TabItem] private var tabViews: [UIView] = [] private let containerView = UIView() private(set) var selectedIndex: Int = 0 // MARK: - Init init(items: [TabItem]) { self.items = items super.init(frame: .zero) setupUI() updateSelection(animated: false) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Layout override var intrinsicContentSize: CGSize { CGSize(width: UIView.noIntrinsicMetric, height: barContentHeight + kSafeBottomMargin) } // MARK: - Setup private func setupUI() { backgroundColor = .clear containerView.backgroundColor = .white containerView.layer.cornerRadius = containerCornerRadius containerView.layer.shadowColor = UIColor(red: 0.059, green: 0.157, blue: 0.275, alpha: 0.1).cgColor containerView.layer.shadowOffset = .zero containerView.layer.shadowRadius = 8 containerView.layer.shadowOpacity = 1 addSubview(containerView) for (index, item) in items.enumerated() { let tabView = makeTabView(item: item, index: index) containerView.addSubview(tabView) tabViews.append(tabView) } containerView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset), containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset), containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -kSafeBottomMargin), containerView.heightAnchor.constraint(equalToConstant: barContentHeight) ]) } private func makeTabView(item: TabItem, index: Int) -> UIView { let view = UIView() view.tag = index view.backgroundColor = .clear let imageView = UIImageView(image: item.image?.withRenderingMode(.alwaysOriginal)) imageView.contentMode = .scaleAspectFit imageView.tag = 100 view.addSubview(imageView) let label = UILabel() label.text = item.title label.font = unselectedFont label.textColor = unselectedTextColor label.textAlignment = .center label.tag = 200 view.addSubview(label) let tap = UITapGestureRecognizer(target: self, action: #selector(tabTapped(_:))) view.addGestureRecognizer(tap) return view } // MARK: - Actions @objc private func tabTapped(_ gesture: UITapGestureRecognizer) { guard let view = gesture.view else { return } let index = view.tag guard index != selectedIndex else { return } selectedIndex = index updateSelection(animated: true) delegate?.tabBar(self, didSelectTabAt: index) } // MARK: - Selection func setSelectedIndex(_ index: Int, animated: Bool = true) { guard index != selectedIndex, index >= 0, index < items.count else { return } selectedIndex = index updateSelection(animated: animated) } private func updateSelection(animated: Bool) { for (i, view) in tabViews.enumerated() { let isSelected = i == selectedIndex if let imageView = view.viewWithTag(100) as? UIImageView { let img = isSelected ? items[i].selectedImage : items[i].image imageView.image = img?.withRenderingMode(.alwaysOriginal) } if let label = view.viewWithTag(200) as? UILabel { label.font = isSelected ? selectedFont : unselectedFont label.textColor = isSelected ? selectedTextColor : unselectedTextColor } } } override func layoutSubviews() { super.layoutSubviews() let containerWidth = containerView.bounds.width guard containerWidth > 0, !tabViews.isEmpty else { return } let tabWidth = containerWidth / CGFloat(tabViews.count) let tabHeight = containerView.bounds.height let totalHeight = iconSize + iconLabelSpacing + labelHeight let startY = (tabHeight - totalHeight) / 2 for (i, view) in tabViews.enumerated() { view.frame = CGRect( x: CGFloat(i) * tabWidth, y: 0, width: tabWidth, height: tabHeight ) if let imageView = view.viewWithTag(100) as? UIImageView { imageView.frame = CGRect( x: (tabWidth - iconSize) / 2, y: startY, width: iconSize, height: iconSize ) } if let label = view.viewWithTag(200) as? UILabel { label.frame = CGRect( x: 0, y: startY + iconSize + iconLabelSpacing, width: tabWidth, height: labelHeight ) } } } // MARK: - Hit Testing override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { if containerView.frame.contains(point) { return true } let extendedBounds = bounds.insetBy(dx: 0, dy: -kSafeBottomMargin) return extendedBounds.contains(point) } }