jsdw_ios/QuickLocation/Main/Tabbar/QuickLocationTabBar.swift

202 lines
6.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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?
/// false
var shouldSelectTab: ((Int) -> Bool)?
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 }
if shouldSelectTab?(index) == false { 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)
}
}