import UIKit
import SnapKit
class DeviceListNewViewController: SurveillanceCommonTableController {
// MARK: - UI Components
private lazy var fixedTitleView: UIView = {
let view = UIView()
view.backgroundColor = .systemRed.withAlphaComponent(0.8) // 🔴
return view
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "设备列表"
label.font = UIFont.systemFont(ofSize: 20, weight: .medium)
label.textColor = .white
label.textAlignment = .center
return label
}()
private lazy var tabScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = true
scrollView.bounces = true
scrollView.alwaysBounceHorizontal = true
scrollView.backgroundColor = .systemGreen.withAlphaComponent(0.5) // 🟢
return scrollView
}()
private let tabButtonTitles = ["所有设备", "收藏页面", "站点选择"]
private var selectedTabIndex = 0
private var tabButtons: [UIButton] = []
private var collectionView: UICollectionView!
private lazy var stickyContainer: UIView = {
let view = UIView()
view.backgroundColor = .clear
return view
}()
// MARK: - Constraint
private var stickyTopConstraint: Constraint?
private var hasSetupHeader = false
private let tabScrollViewHeight: CGFloat = 60
// MARK: - 👇 把 addSubViews 放进 setup 阶段,确保约束前已有父视图
override func tpbSetupSubviews() {
super.tpbSetupSubviews()
// ✅ 第一步:构建所有 view 层级关系(全部提前 addSubview)
view.addSubview(fixedTitleView)
view.addSubview(stickyContainer)
view.addSubview(tableView) // 确保 tableView 是最后加的?根据你的基类逻辑调整
stickyContainer.addSubview(tabScrollView)
setupTitleView()
createTabButtons()
setupCollectionView()
// 添加按钮到 tabScrollView
for button in tabButtons {
tabScrollView.addSubview(button)
}
updateTabSelection(index: selectedTabIndex)
}
private func setupTitleView() {
fixedTitleView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
make.leading.greaterThanOrEqualToSuperview().offset(20)
make.trailing.lessThanOrEqualToSuperview().offset(-20)
}
}
private func createTabButtons() {
tabButtons = tabButtonTitles.enumerated().map { index, title in
let btn = UIButton(type: .custom)
btn.tag = index
btn.setTitle(title, for: .normal)
btn.titleLabel?.font = UIFont.systemFont(ofSize: 14)
btn.setTitleColor(.black, for: .normal)
btn.setTitleColor(.systemBlue, for: .selected)
btn.layer.borderWidth = 1
btn.layer.borderColor = UIColor(white: 0.85, alpha: 1.0).cgColor
btn.layer.cornerRadius = 20
btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)
btn.addTarget(self, action: #selector(onTabTapped(_:)), for: .touchUpInside)
return btn
}
}
private func setupCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
layout.sectionInset = UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = .gray
collectionView.alwaysBounceVertical = true
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "DeviceCell")
}
// MARK: - ✅ 所有约束在此设置,此时所有 view 都已 addSubview
override func tpbMakeConstraint() {
// 固定标题栏
fixedTitleView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide)
make.leading.trailing.equalToSuperview()
make.height.equalTo(40)
}
// stickyContainer
stickyContainer.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview()
make.height.equalTo(tabScrollViewHeight)
self.stickyTopConstraint = make.top.equalTo(fixedTitleView.snp.bottom).constraint
}
// ✅ 此时 tabScrollView 已经有父视图 stickyContainer → superview 不为 nil
tabScrollView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16))
make.height.equalTo(tabScrollViewHeight - 8)
}
// tableView 占据剩余空间
tableView.snp.remakeConstraints { make in
make.top.equalTo(stickyContainer.snp.bottom)
make.leading.trailing.bottom.equalToSuperview()
}
}
// MARK: - Layout Buttons
private func layoutTabButtonsInScrollView() {
for button in tabButtons {
button.snp.removeConstraints()
}
let buttonHeight: CGFloat = 36
let padding: CGFloat = 16
let spacing: CGFloat = 8
var lastButton: UIButton?
for button in tabButtons {
button.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.height.equalTo(buttonHeight)
if let prev = lastButton {
make.leading.equalTo(prev.snp.trailing).offset(spacing)
} else {
make.leading.equalTo(tabScrollView).offset(padding)
}
make.width.greaterThanOrEqualTo(80)
make.width.lessThanOrEqualTo(120)
}
lastButton = button
}
if let last = lastButton {
last.snp.makeConstraints { make in
make.trailing.equalTo(tabScrollView).offset(-padding)
}
}
tabScrollView.layoutIfNeeded()
print("📊 tabScrollView contentSize: \(tabScrollView.contentSize)")
}
// MARK: - Data
private func reloadData() {
var tempSectionArray = [TPBTableSectionModel]()
let section = TPBTableSectionModel()
var cellModels = [TPBBaseTableCellModel]()
let numItems = 20
let itemsPerRow: CGFloat = 2
let rowHeight: CGFloat = 80
let lineSpacing: CGFloat = 10
let numRows = ceil(CGFloat(numItems) / itemsPerRow)
let collectionHeight = numRows * rowHeight + (numRows - 1) * lineSpacing
let collectionCellModel = TPBBaseTableCellModel.customContent(with: collectionView)
collectionCellModel.height = TPBTableElementHeight.customHeight(collectionHeight)
cellModels.append(collectionCellModel)
section.cellModelArray = cellModels
tempSectionArray.append(section)
sectionArray = tempSectionArray
tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
collectionView.dataSource = self
collectionView.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard view.bounds.width > 0 else { return }
if !hasSetupHeader {
hasSetupHeader = true
layoutTabButtonsInScrollView()
reloadData()
}
}
// MARK: - ScrollView Delegate
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
super.scrollViewDidScroll(scrollView)
guard scrollView === tableView else { return }
let offsetY = scrollView.contentOffset.y
let fixedTitleBottom = fixedTitleView.frame.maxY
if offsetY <= 0 {
stickyTopConstraint?.update(offset: fixedTitleBottom)
} else {
let targetY = max(fixedTitleBottom, fixedTitleBottom + offsetY)
stickyTopConstraint?.update(offset: targetY - offsetY)
}
view.layoutIfNeeded()
}
// MARK: - Actions
@objc private func onBack() {
navigationController?.popViewController(animated: true)
}
@objc private func onSettings() {
print("Settings tapped")
}
@objc private func onTabTapped(_ sender: UIButton) {
guard sender.tag != selectedTabIndex else { return }
tabButtons[selectedTabIndex].isSelected = false
selectedTabIndex = sender.tag
sender.isSelected = true
}
private func updateTabSelection(index: Int) {
for (i, btn) in tabButtons.enumerated() {
btn.isSelected = (i == index)
}
}
}
// MARK: - UICollectionViewDataSource
extension DeviceListNewViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DeviceCell", for: indexPath)
cell.backgroundColor = .systemBlue
return cell
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension DeviceListNewViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 32 + 10
let width = (collectionView.bounds.width > 0 ? collectionView.bounds.width : UIScreen.main.bounds.width) - padding
return CGSize(width: width / 2, height: 80)
}
}