import UIKit
import SnapKit
class DeviceListNewViewController: SurveillanceCommonTableController {
// MARK: - 属性
private lazy var titleView: UILabel = {
let label = UILabel()
label.text = "设备列表"
label.font = .tpm20Medium()
label.textColor = .tpbTextPrimary
label.textAlignment = .center
return label
}()
private lazy var searchBar: TPBSearchBar = {
let bar = TPBSearchBar()
bar.placeholder = "搜索"
bar.backgroundColor = .clear
bar.delegate = self
return bar
}()
private lazy var searchContainerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.96, alpha: 1.0)
view.layer.cornerRadius = 22
view.clipsToBounds = true
view.addSubview(searchBar)
return view
}()
private lazy var tabScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.backgroundColor = .clear
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
private lazy var tabButtons: [UIButton] = {
return tabButtonTitles.enumerated().map { index, title in
let btn = UIButton(type: .custom)
btn.tag = index
btn.setTitle(title, for: .normal)
btn.titleLabel?.font = UIFont.tpr14Regular()
btn.setTitleColor(.black, for: .normal)
btn.setTitleColor(.tpbPrimary, 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 let tabButtonTitles = ["所有设备", "收藏页面", "站点选择"]
private var selectedTabIndex = 0
// MARK: - View Setup
override func tpbSetupSubviews() {
super.tpbSetupSubviews()
setupNav()
view.addSubview(titleView)
view.addSubview(searchContainerView)
view.addSubview(tabScrollView)
// 初始化按钮选中状态
updateTabSelection(index: selectedTabIndex)
// 👇 直接在这里布局 tab 按钮(内联实现,避免找不到方法)
layoutTabButtonsInScrollView()
}
private func setupNav() {
navigationItem.leftBarButtonItem = tpbCreateLeftBarButtonItem(
with: TPImageLiteral("icon_back"),
andTarget: self,
andAction: #selector(onBack)
)
navigationItem.rightBarButtonItem = tpbCreateRightBarButtonItem(
with: TPImageLiteral("icon_settings"),
andTarget: self,
andAction: #selector(onSettings)
)
navigationItem.title = ""
}
override func tpbMakeConstraint() {
super.tpbMakeConstraint()
titleView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(8)
make.centerX.equalToSuperview()
}
searchContainerView.snp.makeConstraints { make in
make.top.equalTo(titleView.snp.bottom).offset(16)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(44) // 固定高度适配 TPBSearchBar 常见大小
}
searchBar.snp.makeConstraints { make in
make.edges.equalTo(searchContainerView).inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16))
}
tabScrollView.snp.makeConstraints { make in
make.top.equalTo(searchContainerView.snp.bottom).offset(16)
make.leading.trailing.equalToSuperview().inset(0)
make.height.equalTo(40)
}
}
// MARK: - 内联布局 Tab 按钮(防止 “Cannot find” 错误)
private func layoutTabButtonsInScrollView() {
// 清空之前的内容
tabScrollView.subviews.forEach { $0.removeFromSuperview() }
var previousButton: UIButton?
var currentX: CGFloat = 16
for button in tabButtons {
tabScrollView.addSubview(button)
// 使用固定 frame 或 SnapKit 设置位置
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.contentLayoutGuide).offset(16)
}
make.width.greaterThanOrEqualTo(80)
}
previousButton = button
currentX += button.intrinsicContentSize.width + 8
}
// 手动设置 contentSize(更稳定)
DispatchQueue.main.async {
var totalWidth: CGFloat = 16
for (i, btn) in self.tabButtons.enumerated() {
totalWidth += btn.intrinsicContentSize.width + (i > 0 ? 8 : 0)
}
totalWidth += 16
self.tabScrollView.contentSize = CGSize(width: totalWidth, height: 40)
}
}
// MARK: - 数据模型构建(仅搭建结构,不填充真实数据)
private func reloadData() {
var tempSectionArray = [TPBTableSectionModel]()
let section0 = TPBTableSectionModel()
var cellModels = [TPBBaseTableCellModel]()
// Cell 1: Tab ScrollView
let tabCellModel = TPBBaseTableCellModel.customContent(with: tabScrollView)
cellModels.append(tabCellModel)
// Cell 2: 占位用的 CollectionView 区域(仅视觉框架)
let collectionViewPlaceholder = UIView()
collectionViewPlaceholder.backgroundColor = .tpbBackground
collectionViewPlaceholder.layer.cornerRadius = 12
collectionViewPlaceholder.clipsToBounds = true
// 添加一个简单的提示标签表示“这里是列表”
let placeholderLabel = UILabel()
placeholderLabel.text = "设备内容区域"
placeholderLabel.textColor = .tpbTextSecondaryContent
placeholderLabel.font = .tpr14Regular()
placeholderLabel.textAlignment = .center
placeholderLabel.sizeToFit()
collectionViewPlaceholder.addSubview(placeholderLabel)
placeholderLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
}
let collectionCellModel = TPBBaseTableCellModel.customContent(with: collectionViewPlaceholder)
collectionCellModel.height = TPBTableElementHeight.customHeight(240)
cellModels.append(collectionCellModel)
section0.cellModelArray = cellModels
tempSectionArray.append(section0)
sectionArray = tempSectionArray
tableView.reloadData()
}
// MARK: - 生命周期
override func viewDidLoad() {
super.viewDidLoad()
reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 后续绑定 collectionView delegate
}
// MARK: - 点击事件
@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
// 后续刷新数据
print("Switched to tab: \(selectedTabIndex)")
}
private func updateTabSelection(index: Int) {
for (i, btn) in tabButtons.enumerated() {
btn.isSelected = (i == index)
}
}
}
// MARK: - TPBSearchBarDelegate(空实现,仅满足协议要求)
extension DeviceListNewViewController: TPBSearchBarDelegate {
func searchBar(_ searchBar: TPBSearchBar, textDidChange searchText: String) {
// TODO: 搜索关键词变化时处理(后续接入 ViewModel)
}
func searchBarTextDidBeginEditing(_ searchBar: TPBSearchBar) {
addTapToDismissKeyboard()
}
func searchBarTextDidEndEditing(_ searchBar: TPBSearchBar) {
removeTapToDismissKeyboard()
}
func searchBarSearchButtonClicked(_ searchBar: TPBSearchBar) {
searchBar.resignFirstResponder()
}
}
// MARK: - Keyboard Dismiss Gesture
private extension DeviceListNewViewController {
func addTapToDismissKeyboard() {
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
objc_setAssociatedObject(
self,
&AssociatedKeys.searchBarTapGestureKey,
tap,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
func removeTapToDismissKeyboard() {
guard let tap = objc_getAssociatedObject(self, &AssociatedKeys.searchBarTapGestureKey) as? UITapGestureRecognizer
else { return }
view.removeGestureRecognizer(tap)
objc_setAssociatedObject(self, &AssociatedKeys.searchBarTapGestureKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
@objc func dismissKeyboard() {
searchBar.resignFirstResponder()
}
}
// MARK: - Associated Object Keys
private struct AssociatedKeys {
static var searchBarTapGestureKey: UInt8 = 0
}
现在这个,给我实现成这样的效果:
屏幕靠上部分显示titleview,然后下面显示searchbar,然后下面显示收藏按钮、所有设备那个scrollview,然后在下面显示一个UIcollectionView,滚动范围就在这个UICollectionView区域,明白我想要的结构了吗?修改代码
import UIKit
import SnapKit
class DeviceListNewViewController: SurveillanceCommonTableController {
// MARK: - 属性
private lazy var titleView: UILabel = {
let label = UILabel()
label.text = "设备列表"
label.font = .tpm20Medium()
label.textColor = .tpbTextPrimary
label.textAlignment = .center
return label
}()
private lazy var searchBar: TPBSearchBar = {
let bar = TPBSearchBar()
bar.placeholder = "搜索"
bar.backgroundColor = .clear
bar.delegate = self
return bar
}()
private lazy var searchContainerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.96, alpha: 1.0)
view.layer.cornerRadius = 22
view.clipsToBounds = true
view.addSubview(searchBar)
return view
}()
private lazy var tabScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.backgroundColor = .clear
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
private lazy var tabButtons: [UIButton] = {
return tabButtonTitles.enumerated().map { index, title in
let btn = UIButton(type: .custom)
btn.tag = index
btn.setTitle(title, for: .normal)
btn.titleLabel?.font = UIFont.tpr14Regular()
btn.setTitleColor(.black, for: .normal)
btn.setTitleColor(.tpbPrimary, 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 let tabButtonTitles = ["所有设备", "收藏页面", "站点选择"]
private var selectedTabIndex = 0
// MARK: - View Setup
override func tpbSetupSubviews() {
super.tpbSetupSubviews()
setupNav()
view.addSubview(titleView)
view.addSubview(searchContainerView)
view.addSubview(tabScrollView)
// 初始化按钮选中状态
updateTabSelection(index: selectedTabIndex)
// 👇 直接在这里布局 tab 按钮(内联实现,避免找不到方法)
layoutTabButtonsInScrollView()
}
private func setupNav() {
navigationItem.leftBarButtonItem = tpbCreateLeftBarButtonItem(
with: TPImageLiteral("icon_back"),
andTarget: self,
andAction: #selector(onBack)
)
navigationItem.rightBarButtonItem = tpbCreateRightBarButtonItem(
with: TPImageLiteral("icon_settings"),
andTarget: self,
andAction: #selector(onSettings)
)
navigationItem.title = ""
}
override func tpbMakeConstraint() {
super.tpbMakeConstraint()
titleView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(8)
make.centerX.equalToSuperview()
}
searchContainerView.snp.makeConstraints { make in
make.top.equalTo(titleView.snp.bottom).offset(16)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(44) // 固定高度适配 TPBSearchBar 常见大小
}
searchBar.snp.makeConstraints { make in
make.edges.equalTo(searchContainerView).inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16))
}
tabScrollView.snp.makeConstraints { make in
make.top.equalTo(searchContainerView.snp.bottom).offset(16)
make.leading.trailing.equalToSuperview().inset(0)
make.height.equalTo(40)
}
}
// MARK: - 内联布局 Tab 按钮(防止 “Cannot find” 错误)
private func layoutTabButtonsInScrollView() {
// 清空之前的内容
tabScrollView.subviews.forEach { $0.removeFromSuperview() }
var previousButton: UIButton?
var currentX: CGFloat = 16
for button in tabButtons {
tabScrollView.addSubview(button)
// 使用固定 frame 或 SnapKit 设置位置
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.contentLayoutGuide).offset(16)
}
make.width.greaterThanOrEqualTo(80)
}
previousButton = button
currentX += button.intrinsicContentSize.width + 8
}
// 手动设置 contentSize(更稳定)
DispatchQueue.main.async {
var totalWidth: CGFloat = 16
for (i, btn) in self.tabButtons.enumerated() {
totalWidth += btn.intrinsicContentSize.width + (i > 0 ? 8 : 0)
}
totalWidth += 16
self.tabScrollView.contentSize = CGSize(width: totalWidth, height: 40)
}
}
// MARK: - 数据模型构建(仅搭建结构,不填充真实数据)
private func reloadData() {
var tempSectionArray = [TPBTableSectionModel]()
let section0 = TPBTableSectionModel()
var cellModels = [TPBBaseTableCellModel]()
// Cell 1: Tab ScrollView
let tabCellModel = TPBBaseTableCellModel.customContent(with: tabScrollView)
cellModels.append(tabCellModel)
// Cell 2: 占位用的 CollectionView 区域(仅视觉框架)
let collectionViewPlaceholder = UIView()
collectionViewPlaceholder.backgroundColor = .tpbBackground
collectionViewPlaceholder.layer.cornerRadius = 12
collectionViewPlaceholder.clipsToBounds = true
// 添加一个简单的提示标签表示“这里是列表”
let placeholderLabel = UILabel()
placeholderLabel.text = "设备内容区域"
placeholderLabel.textColor = .tpbTextSecondaryContent
placeholderLabel.font = .tpr14Regular()
placeholderLabel.textAlignment = .center
placeholderLabel.sizeToFit()
collectionViewPlaceholder.addSubview(placeholderLabel)
placeholderLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
}
let collectionCellModel = TPBBaseTableCellModel.customContent(with: collectionViewPlaceholder)
collectionCellModel.height = TPBTableElementHeight.customHeight(240)
cellModels.append(collectionCellModel)
section0.cellModelArray = cellModels
tempSectionArray.append(section0)
sectionArray = tempSectionArray
tableView.reloadData()
}
// MARK: - 生命周期
override func viewDidLoad() {
super.viewDidLoad()
reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 后续绑定 collectionView delegate
}
// MARK: - 点击事件
@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
// 后续刷新数据
print("Switched to tab: \(selectedTabIndex)")
}
private func updateTabSelection(index: Int) {
for (i, btn) in tabButtons.enumerated() {
btn.isSelected = (i == index)
}
}
}
// MARK: - TPBSearchBarDelegate(空实现,仅满足协议要求)
extension DeviceListNewViewController: TPBSearchBarDelegate {
func searchBar(_ searchBar: TPBSearchBar, textDidChange searchText: String) {
// TODO: 搜索关键词变化时处理(后续接入 ViewModel)
}
func searchBarTextDidBeginEditing(_ searchBar: TPBSearchBar) {
addTapToDismissKeyboard()
}
func searchBarTextDidEndEditing(_ searchBar: TPBSearchBar) {
removeTapToDismissKeyboard()
}
func searchBarSearchButtonClicked(_ searchBar: TPBSearchBar) {
searchBar.resignFirstResponder()
}
}
// MARK: - Keyboard Dismiss Gesture
private extension DeviceListNewViewController {
func addTapToDismissKeyboard() {
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
objc_setAssociatedObject(
self,
&AssociatedKeys.searchBarTapGestureKey,
tap,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
func removeTapToDismissKeyboard() {
guard let tap = objc_getAssociatedObject(self, &AssociatedKeys.searchBarTapGestureKey) as? UITapGestureRecognizer
else { return }
view.removeGestureRecognizer(tap)
objc_setAssociatedObject(self, &AssociatedKeys.searchBarTapGestureKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
@objc func dismissKeyboard() {
searchBar.resignFirstResponder()
}
}
// MARK: - Associated Object Keys
private struct AssociatedKeys {
static var searchBarTapGestureKey: UInt8 = 0
}