import UIKit
import SnapKit
// MARK: - DeviceListNewViewController
class DeviceListNewViewController: SurveillanceCommonTableController {
// MARK: - 子视图声明
private lazy var tabScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
scrollView.layer.cornerRadius = 8
scrollView.clipsToBounds = true
return scrollView
}()
private let tabButtonTitles = ["所有设备", "收藏页面", "站点选择"]
private var selectedTabIndex = 0
private var tabButtons: [UIButton] = []
// 吸顶头部
private lazy var stickyHeader: UIView = {
let header = UIView()
header.backgroundColor = tpbColorBackground()
return header
}()
private lazy var backButton: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("←", for: .normal)
btn.setTitleColor(UIColor.blue, for: .normal)
btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
btn.addTarget(self, action: #selector(onBackTapped), for: .touchUpInside)
return btn
}()
private lazy var locationButton: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("📍", for: .normal)
btn.setTitleColor(UIColor.blue, for: .normal)
btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
btn.addTarget(self, action: #selector(onLocationTapped), for: .touchUpInside)
return btn
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "设备列表"
label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
label.textColor = tpbColorTextPrimary()
label.textAlignment = .center
return label
}()
private lazy var searchButton: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("🔍", for: .normal)
btn.setTitleColor(UIColor.blue, for: .normal)
btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
btn.addTarget(self, action: #selector(onSearchTapped), for: .touchUpInside)
return btn
}()
private lazy var separatorLine: UIView = {
let line = UIView()
line.backgroundColor = UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 0.3)
return line
}()
// 横向分页容器
private lazy var deviceCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: UIScreen.main.bounds.width - 32, height: 400)
layout.minimumLineSpacing = 16
layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .clear
cv.isPagingEnabled = true
cv.showsHorizontalScrollIndicator = false
cv.bounces = true
cv.delegate = self
cv.dataSource = self
cv.register(DevicePageCollectionViewCell.self, forCellWithReuseIdentifier: "DevicePageCell")
return cv
}()
// 页面控制器指示器
private lazy var pageControl: UIPageControl = {
let pc = UIPageControl()
pc.numberOfPages = 3
pc.currentPage = 0
pc.pageIndicatorTintColor = UIColor(white: 0.7, alpha: 1.0)
pc.currentPageIndicatorTintColor = UIColor.blue
pc.hidesForSinglePage = true
return pc
}()
// MARK: - 生命周期
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.contentInsetAdjustmentBehavior = .never
reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: false)
}
override func tpbSetupSubviews() {
super.tpbSetupSubviews()
// 添加吸顶栏
view.addSubview(stickyHeader)
stickyHeader.addSubview(backButton)
stickyHeader.addSubview(locationButton)
stickyHeader.addSubview(titleLabel)
stickyHeader.addSubview(searchButton)
stickyHeader.addSubview(separatorLine)
createTabButtons()
layoutTabButtonsInScrollView()
}
override func tpbMakeConstraint() {
stickyHeader.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
make.leading.trailing.equalToSuperview()
make.height.equalTo(50)
}
backButton.snp.remakeConstraints { make in
make.leading.equalTo(stickyHeader).offset(16)
make.centerY.equalTo(stickyHeader)
make.width.greaterThanOrEqualTo(30)
}
locationButton.snp.remakeConstraints { make in
make.leading.equalTo(backButton.snp.trailing).offset(8)
make.centerY.equalTo(backButton)
make.size.equalTo(30)
}
titleLabel.snp.remakeConstraints { make in
make.centerX.equalToSuperview()
make.centerY.equalTo(backButton)
}
searchButton.snp.remakeConstraints { make in
make.trailing.equalTo(stickyHeader).offset(-16)
make.centerY.equalTo(backButton)
make.width.greaterThanOrEqualTo(30)
}
separatorLine.snp.makeConstraints { make in
make.height.equalTo(1 / UIScreen.main.scale)
make.leading.trailing.bottom.equalToSuperview()
}
tableView.snp.remakeConstraints { make in
make.top.equalTo(stickyHeader.snp.bottom).offset(10)
make.leading.trailing.bottom.equalToSuperview()
}
}
// MARK: - Tab 操作
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(UIColor(white: 0.4, alpha: 1.0), for: .normal)
btn.setTitleColor(UIColor.blue, 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
}
updateTabSelection(index: selectedTabIndex)
}
@objc private func onTabTapped(_ sender: UIButton) {
guard sender.tag != selectedTabIndex else { return }
updateTabSelection(index: sender.tag)
scrollToPage(index: sender.tag)
}
private func updateTabSelection(index: Int) {
self.selectedTabIndex = index
for (i, btn) in tabButtons.enumerated() {
btn.isSelected = (i == index)
}
pageControl.currentPage = index
DispatchQueue.main.async {
self.deviceCollectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
}
}
private func layoutTabButtonsInScrollView() {
tabScrollView.subviews.forEach { $0.removeFromSuperview() }
var previousButton: UIButton?
for button in tabButtons {
tabScrollView.addSubview(button)
button.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.height.equalTo(36)
if let prev = previousButton {
make.leading.equalTo(prev.snp.trailing).offset(8)
} else {
make.leading.equalTo(tabScrollView).offset(16)
}
make.width.greaterThanOrEqualTo(90)
make.width.equalTo(button.titleLabel!.snp.width).offset(32).priority(.low)
}
previousButton = button
}
if let lastBtn = previousButton {
lastBtn.snp.makeConstraints { make in
make.trailing.equalTo(tabScrollView).offset(-16)
}
}
tabScrollView.layoutIfNeeded()
}
// MARK: - 创建横向滑动设备区域(单个 cell 内容)
private func createHorizontalScrollableDeviceView() -> UIView {
let containerView = UIView()
containerView.backgroundColor = .clear
// Collection View
deviceCollectionView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(deviceCollectionView)
// Page Control
pageControl.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(pageControl)
NSLayoutConstraint.activate([
deviceCollectionView.topAnchor.constraint(equalTo: containerView.topAnchor),
deviceCollectionView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
deviceCollectionView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
deviceCollectionView.height.constraint(equalToConstant: 400),
pageControl.topAnchor.constraint(equalTo: deviceCollectionView.bottomAnchor, constant: 8),
pageControl.centerX.constraint(equalTo: containerView.centerXAnchor),
pageControl.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
return containerView
}
// 主动滚动到某一页
private func scrollToPage(index: Int) {
let indexPath = IndexPath(item: index, section: 0)
deviceCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
// MARK: - 刷新设备列表(现在只刷新一次,作为单个 cell 插入)
private func refreshDeviceListSection() {
let cellModel = TPBBaseTableCellModel.customContent(with: createHorizontalScrollableDeviceView())
cellModel.height = TPBTableElementHeight.customHeight(440) // 400 + 8 + 32
let section = TPBTableSectionModel()
section.cellModelArray = [cellModel]
if sectionArray.count > 1 {
sectionArray[1] = section
} else {
sectionArray.append(section)
}
tableView.reloadData()
}
// MARK: - 加载初始数据
private func reloadData() {
var tempSectionArray = [TPBTableSectionModel]()
// Section 0: 卡片信息
let section0 = TPBTableSectionModel()
var cellModels = [TPBBaseTableCellModel]()
// 设备总数视图
let deviceCountView = createDeviceCountView()
let deviceCountCellModel = TPBBaseTableCellModel.customContent(with: deviceCountView)
deviceCountCellModel.height = TPBTableElementHeight.customHeight(60)
cellModels.append(deviceCountCellModel)
// 存储空间视图
let storageView = createStorageUsageView()
let storageCellModel = TPBBaseTableCellModel.customContent(with: storageView)
storageCellModel.height = TPBTableElementHeight.customHeight(60)
cellModels.append(storageCellModel)
// Tab 区域
let tabView = createTabContainerView()
let tabCellModel = TPBBaseTableCellModel.customContent(with: tabView)
tabCellModel.height = TPBTableElementHeight.customHeight(72)
cellModels.append(tabCellModel)
section0.cellModelArray = cellModels
tempSectionArray.append(section0)
// 初始化设备列表区域(单 cell 多页)
refreshDeviceListSection()
// 合并 sections
sectionArray = tempSectionArray + Array(sectionArray.dropFirst(max(0, sectionArray.count - 1)))
tableView.reloadData()
}
// MARK: - 自定义 View 构建函数
/// 获取背景色(兼容 iOS 12)
private func tpbColorBackground() -> UIColor {
if #available(iOS 13, *) {
return UIColor.systemBackground
} else {
return UIColor(white: 1.0, alpha: 1.0)
}
}
/// 获取主文本色(兼容 iOS 12)
private func tpbColorTextPrimary() -> UIColor {
if #available(iOS 13, *) {
return UIColor.label
} else {
return UIColor.black
}
}
private func createDeviceCountView() -> UIView {
let view = UIView()
view.backgroundColor = .clear
let label = UILabel()
label.text = "设备总数:32 台"
label.font = UIFont.systemFont(ofSize: 16, weight: .regular)
label.textColor = tpbColorTextPrimary()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
label.snp.makeConstraints { make in
make.centerY.equalTo(view)
make.leading.equalTo(view).offset(16)
}
return view
}
private func createStorageUsageView() -> UIView {
let view = UIView()
view.backgroundColor = .clear
let label = UILabel()
label.text = "已用存储:1.2 TB / 5 TB"
label.font = UIFont.systemFont(ofSize: 16, weight: .regular)
label.textColor = tpbColorTextPrimary()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
label.snp.makeConstraints { make in
make.centerY.equalTo(view)
make.leading.equalTo(view).offset(16)
}
return view
}
private func createTabContainerView() -> UIView {
let container = UIView()
container.backgroundColor = .clear
tabScrollView.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(tabScrollView)
tabScrollView.snp.makeConstraints { make in
make.leading.equalTo(container).offset(16)
make.trailing.equalTo(container).offset(-16)
make.centerY.equalTo(container)
make.height.equalTo(44)
}
return container
}
// MARK: - Action 回调
@objc private func onBackTapped() {
navigationController?.popViewController(animated: true)
}
@objc private func onLocationTapped() {
print("定位按钮点击")
}
@objc private func onSearchTapped() {
print("搜索按钮点击")
}
}
// MARK: - UICollectionViewDataSource & Delegate
extension DeviceListNewViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DevicePageCell", for: indexPath) as! DevicePageCollectionViewCell
switch indexPath.item {
case 0:
let devices = (0..<30).map { "所有设备 \($0 + 1)" }
cell.configure(with: "所有设备", deviceNames: devices)
case 1:
let favorites = (0..<10).map { "收藏设备 \($0 + 1)" }
cell.configure(with: "收藏页面", deviceNames: favorites)
case 2:
let sites = ["站点 A", "站点 B", "站点 C", "站点 D"]
cell.configure(with: "站点选择", deviceNames: sites)
default:
break
}
return cell
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView === deviceCollectionView {
let page = Int(scrollView.contentOffset.x / scrollView.bounds.width)
updateTabSelection(index: page)
}
}
}
// MARK: - DevicePageCollectionViewCell(每一页的内容)
class DevicePageCollectionViewCell: UICollectionViewCell {
private let titleLabel = UILabel()
private let innerStackView = UIStackView()
private var deviceViews: [UIView] = []
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
backgroundColor = UIColor(white: 0.95, alpha: 1.0)
layer.cornerRadius = 12
clipsToBounds = true
// 标题
titleLabel.font = UIFont.systemFont(ofSize: 18, weight: .bold)
titleLabel.textAlignment = .center
titleLabel.textColor = UIColor(red: 0.0, green: 0.4, blue: 1.0, alpha: 1.0)
// 内部堆叠视图
innerStackView.axis = .vertical
innerStackView.spacing = 10
innerStackView.distribution = .fillProportionally
// 布局
addSubview(titleLabel)
addSubview(innerStackView)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
innerStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 20),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
innerStackView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),
innerStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
innerStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
innerStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16)
])
}
func configure(with title: String, deviceNames: [String]) {
titleLabel.text = title
// 清除旧视图
for view in deviceViews {
innerStackView.removeArrangedSubview(view)
view.removeFromSuperview()
}
deviceViews.removeAll()
// 添加新设备项
for name in deviceNames {
let deviceView = createDeviceItemView(name: name)
innerStackView.addArrangedSubview(deviceView)
deviceViews.append(deviceView)
}
}
private func createDeviceItemView(name: String) -> UIView {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 8
view.layer.borderWidth = 1
view.layer.borderColor = UIColor(red: 0.0, green: 0.4, blue: 1.0, alpha: 0.3).cgColor
let label = UILabel()
label.text = name
label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
label.textColor = UIColor(red: 0.0, green: 0.4, blue: 1.0, alpha: 1.0)
label.textAlignment = .center
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerX.constraint(equalTo: view.centerXAnchor),
label.centerY.constraint(equalTo: view.centerYAnchor)
])
return view
}
}
// MARK: - DeviceListCell(保留原类用于其他场景或过渡)
class DeviceListCell: UICollectionViewCell {
private let titleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupUI()
}
private func setupUI() {
backgroundColor = UIColor(red: 0.0, green: 0.4, blue: 1.0, alpha: 0.1)
layer.borderColor = UIColor(red: 0.0, green: 0.4, blue: 1.0, alpha: 1.0).cgColor
layer.borderWidth = 1
layer.cornerRadius = 8
clipsToBounds = true
titleLabel.font = UIFont.systemFont(ofSize: 16, weight: .medium)
titleLabel.textColor = UIColor(red: 0.0, green: 0.4, blue: 1.0, alpha: 1.0)
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 1
titleLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.center.equalTo(contentView)
make.leading.greaterThanOrEqualTo(contentView).offset(8)
make.trailing.lessThanOrEqualTo(contentView).offset(-8)
}
}
func configure(with title: String) {
titleLabel.text = title
}
override func prepareForReuse() {
super.prepareForReuse()
titleLabel.text = nil
}
}这里面都使用snapkit布局,而且出现了错误:Value of type 'UICollectionView' has no member 'height'。func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView === deviceCollectionView {
let page = Int(scrollView.contentOffset.x / scrollView.bounds.width)
updateTabSelection(index: page)
}
} Overriding declaration requires an 'override' keyword