jsdw_ios/QuickLocation/Main/Tabbar/QuickLocationTabBar.swift

199 lines
6.6 KiB
Swift

//
// 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)
}
}