不是这个意思,肯定是后面写的什么代码影响了原本的onlinedevicellist VC,我要求这个是不动的啊,只是新建了一套devicelist代码,现在这原本的出现了问题肯定不行。重新帮我检查所有的地方://
// DeviceListView.swift
// SurveillanceHome
//
// Created by MaCong on 2025/12/3.
// Copyright © 2025 tplink. All rights reserved.
//
// DeviceListView.swift
class DeviceListView: UIView {
// ✅ 使用 lazy 初始化,解决 Self 问题
private lazy var collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: Self.createLayout()
)
var devices: [TPSSDeviceForDeviceList] = [] {
didSet {
collectionView.reloadData()
invalidateIntrinsicContentSize()
}
}
var onMoreButtonTap: ((TPSSDeviceForDeviceList, TPSSChannelInfo?) -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private static func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
layout.minimumLineSpacing = 10
return layout
}
private func setup() {
collectionView.backgroundColor = .clear
collectionView.showsVerticalScrollIndicator = false
collectionView.register(DeviceListCell.self, forCellWithReuseIdentifier: "DeviceListCell")
collectionView.delegate = self
collectionView.dataSource = self
addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: topAnchor),
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
}
// MARK: - UICollectionView DataSource & Delegate
extension DeviceListView: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return devices.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DeviceListCell", for: indexPath) as! DeviceListCell
let device = devices[indexPath.item]
// ✅ 安全处理:不再使用 device.channels
cell.configure(with: device, channel: nil)
cell.onMoreTap = { [weak self] channel in
self?.onMoreButtonTap?(device, channel)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.bounds.width > 0 ? collectionView.bounds.width : UIScreen.main.bounds.width
let itemWidth = width - 32
let itemHeight = itemWidth * 9 / 16 + 60
return CGSize(width: itemWidth, height: itemHeight)
}
}
// MARK: - intrinsicContentSize 自动高度
extension DeviceListView {
override var intrinsicContentSize: CGSize {
CGSize(width: UIView.noIntrinsicMetric, height: calculateRequiredHeight())
}
override func invalidateIntrinsicContentSize() {
super.invalidateIntrinsicContentSize()
if let tableView = findParentTableView() {
DispatchQueue.main.async {
tableView.beginUpdates()
tableView.endUpdates()
}
}
}
private func calculateRequiredHeight() -> CGFloat {
guard !devices.isEmpty else { return 150 }
let width = collectionView.frame.width > 0 ? collectionView.frame.width : UIScreen.main.bounds.width
let itemWidth = width - 32
let itemHeight = itemWidth * 9 / 16 + 60
let lineSpacing: CGFloat = 10
let totalHeight = CGFloat(devices.count) * itemHeight + lineSpacing * CGFloat(devices.count - 1)
return max(totalHeight, 150)
}
private func findParentTableView() -> UITableView? {
var parent = superview
while parent != nil {
if let tableView = parent as? UITableView {
return tableView
}
parent = parent?.superview
}
return nil
}
}
//
// DeviceListCell.swift
// SurveillanceHome
//
// Created by MaCong on 2025/12/3.
// Copyright © 2025 tplink. All rights reserved.
//
import UIKit
// 如果用了 Kingfisher 才需要下一行
// import Kingfisher
// ⚠️ 临时定义,防止找不到类型
class TPSSChannelInfo {}
// 模拟设备模型(等真实 SDK 导入后删除)
extension TPSSDeviceForDeviceList {
// 模拟计算属性(仅用于调试)
var newdisplayName: String {
return self.value(forKey: "deviceName") as? String ?? self.value(forKey: "name") as? String ?? "Unknown Device"
}
var isOnline: Bool {
return (self.value(forKey: "onlineStatus") as? Int) == 1 ||
(self.value(forKey: "isOnline") as? Bool) == true
}
var thumbnailURL: URL? {
return self.value(forKey: "snapshotUrl") as? URL ??
self.value(forKey: "thumbnailUrl") as? URL
}
}
class DeviceListCell: UICollectionViewCell {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var thumbnailImageView: UIImageView!
@IBOutlet weak var statusLabel: UILabel!
@IBOutlet weak var moreButton: UIButton!
var onMoreTap: ((TPSSChannelInfo?) -> Void)?
private var currentDevice: TPSSDeviceForDeviceList!
private var selectedChannel: TPSSChannelInfo?
override func awakeFromNib() {
super.awakeFromNib()
setupUI()
}
private func setupUI() {
layer.cornerRadius = 8
clipsToBounds = true
backgroundColor = .tpbBackground
}
func configure(with device: TPSSDeviceForDeviceList, channel: TPSSChannelInfo? = nil) {
self.currentDevice = device
self.selectedChannel = channel
// ✅ 安全取名
nameLabel.text = device.newdisplayName
// ✅ 在线状态判断(多种可能)
if device.isOnline {
statusLabel.text = "在线"
statusLabel.textColor = .systemGreen
} else {
statusLabel.text = "离线"
statusLabel.textColor = .systemRed
}
// ✅ 加载缩略图
if let url = device.thumbnailURL {
// 如果集成了 Kingfisher
#if canImport(Kingfisher)
thumbnailImageView.kf.setImage(with: url)
#else
// 否则只打印日志
print(" loadImage: $url)")
#endif
} else {
// ✅ 兼容低版本 iOS 的 system image
let cameraImage: UIImage?
if #available(iOS 13.0, *) {
cameraImage = UIImage(systemName: "camera")
} else {
cameraImage = UIImage(named: "camera_placeholder") // 提供一张资源图
}
thumbnailImageView.image = cameraImage ?? UIImage()
thumbnailImageView.backgroundColor = UIColor.gray
}
// ✅ 清理旧事件绑定
moreButton.removeTarget(nil, action: nil, for: .allEvents)
moreButton.addTarget(self, action: #selector(moreButtonTapped), for: .touchUpInside)
}
@objc private func moreButtonTapped() {
onMoreTap?(selectedChannel)
}
}
//
// DeviceListNewViewController.swift
// SurveillanceHome
//
// Created by MaCong on 2025/12/3.
// Copyright © 2025 tplink. All rights reserved.
//
import UIKit
import SnapKit
// MARK: - DeviceListNewViewController
class DeviceListNewViewController: SurveillanceCommonTableController, SelectOrganizationViewDelegate {
// MARK: - 属性
private lazy var titleView: NewTitleView = {
let view = NewTitleView()
view.titleText = "设备列表"
view.didClickCallback = { [weak self] _ in
guard let self = self else { return }
let selectOrgView = self.selectOrganizationView
let titleView = self.titleView
if titleView.isAnimating { return }
if !titleView.isInExpandStatus {
titleView.isAnimating = true
self.view.addSubview(selectOrgView)
selectOrgView.show(animated: true) {
titleView.isAnimating = false
}
} else {
titleView.isAnimating = true
self.hideLoadingView()
selectOrgView.dismiss(animated: true) {
titleView.isAnimating = false
}
}
// 更新箭头状态
titleView.changeToStatus(expand: !titleView.isInExpandStatus)
}
return view
}()
private lazy var multiScreenButton: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(TPImageLiteral("media_player_switch_multi_live"), for: .normal)
btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
btn.addTarget(self, action: #selector(clickMultileLive), for: .touchUpInside)
return btn
}()
var selectedTabType: DeviceListMasterSelectedType = .all {
didSet {
UserDefaults.standard.deviceListSelectedType = selectedTabType
forceRefreshDeviceList()
}
}
var selectedSiteInfo: TPSSVMSSubsiteInfo? {
didSet {
saveSelectedSiteInfo(selectedSiteInfo)
forceRefreshDeviceList()
}
}
private func saveSelectedSiteInfo(_ info: TPSSVMSSubsiteInfo?) {
guard let siteInfo = info else {
try? FileManager.default.removeItem(at: URL(fileURLWithPath: DeviceListMasterViewController.getDocumentsPath(path: DeviceListMasterViewController.kSelectedSiteFileName) ?? ""))
return
}
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: siteInfo, requiringSecureCoding: true)
try data.write(to: URL(fileURLWithPath: DeviceListMasterViewController.getDocumentsPath(path: DeviceListMasterViewController.kSelectedSiteFileName) ?? ""))
} catch {
print(error)
}
}
private lazy var selectOrganizationView: SelectOrganizationView = {
let view = SelectOrganizationView()
view.rootViewController = self
view.delegate = self
view.frame = CGRect(
x: 0,
y: SelectOrganizationView.statusBarHeight + SelectOrganizationView.navigationBarHeight,
width: screenWidth,
height: screenHeight - SelectOrganizationView.statusBarHeight - SelectOrganizationView.navigationBarHeight
)
return view
}()
// MARK: - 设备数据与视图
private var devices: [TPSSDeviceForDeviceList] = [] {
didSet {
deviceListView.devices = devices
}
}
private lazy var deviceListView: DeviceListView = {
let view = DeviceListView()
view.onMoreButtonTap = { [weak self] device, channel in
self?.showDeviceMenu(for: device, channel: channel)
}
return view
}()
// MARK: - 生命周期
override func viewDidLoad() {
super.viewDidLoad()
setNavigation()
tableView.contentInsetAdjustmentBehavior = .automatic
reloadData()
// 初始加载设备
loadDevicesFromContext()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(false, animated: false)
}
// MARK: - 子视图与约束
override func tpbSetupSubviews() {
super.tpbSetupSubviews()
}
override func tpbMakeConstraint() {
tableView.snp.remakeConstraints { make in
make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top)
make.leading.trailing.equalToSuperview()
make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom)
}
}
// MARK: - 创建 UI 组件
private func setNavigation(){
navigationItem.titleView = titleView
titleView.snp.makeConstraints { make in
make.width.lessThanOrEqualTo(200)
make.height.equalTo(44)
}
let backButtonItem = self.tpbCreateLeftBarButtonItem(with: TPImageLiteral("common_light_back_nor"), andTarget: self, andAction: #selector(jumpToXXX))
let centralButtonItem = self.tpbCreateLeftBarButtonItem(with: TPImageLiteral("central_surveillance_button"), andTarget: self, andAction: #selector(centralButtonClicked))
let messageButtonItem = self.tpbCreateLeftBarButtonItem(with: TPImageLiteral("tabbar_message_nor"), andTarget: self, andAction: #selector(onMessageButtonTapped))
navigationItem.leftBarButtonItems = [backButtonItem, centralButtonItem]
navigationItem.rightBarButtonItem = messageButtonItem
}
// MARK: - 创建组件:设备数量视图
private func createDeviceCountView() -> UIView {
let containerView = UIView()
containerView.backgroundColor = .tpbCard
containerView.layer.cornerRadius = 4
containerView.clipsToBounds = true
let labels = ["NVR", "4K", "2K", "HD"]
var categoryViews: [UIView] = []
for text in labels {
let categoryView = UIView()
categoryView.backgroundColor = UIColor(white: 0.93, alpha: 1.0)
categoryView.layer.cornerRadius = 8
categoryView.clipsToBounds = true
let label = UILabel()
label.text = text
label.textColor = UIColor.tpbTextPrimary
label.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
label.textAlignment = .center
categoryView.addSubview(label)
label.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(10)
}
containerView.addSubview(categoryView)
categoryViews.append(categoryView)
}
for (index, view) in categoryViews.enumerated() {
view.snp.makeConstraints { make in
make.height.equalTo(60)
make.centerY.equalTo(containerView)
if index == 0 {
make.leading.equalTo(containerView).offset(20)
} else {
make.leading.equalTo(categoryViews[index - 1].snp.trailing).offset(16)
make.width.equalTo(categoryViews[0])
}
if index == labels.count - 1 {
make.trailing.equalTo(containerView).offset(-20)
}
}
}
return containerView
}
// MARK: - 创建组件:存储空间视图
private func createStorageUsageView() -> UIView {
let containerView = UIView()
containerView.backgroundColor = .tpbCard
containerView.layer.cornerRadius = 8
containerView.clipsToBounds = true
let titleLabel = UILabel()
titleLabel.text = "存储空间"
titleLabel.font = UIFont.systemFont(ofSize: 14, weight: .medium)
titleLabel.textColor = .tpbTextPrimary
containerView.addSubview(titleLabel)
let progressView = UIProgressView(progressViewStyle: .default)
progressView.progressTintColor = UIColor.systemBlue
progressView.trackTintColor = UIColor(white: 0.9, alpha: 1.0)
containerView.addSubview(progressView)
let detailLabel = UILabel()
detailLabel.font = UIFont.systemFont(ofSize: 13, weight: .regular)
detailLabel.textColor = .tpbTextPrimary
detailLabel.textAlignment = .center
containerView.addSubview(detailLabel)
let used = 1.2
let total = 5.0
let percent = Float(used / total)
progressView.setProgress(percent, animated: false)
let percentage = Int(percent * 100)
detailLabel.text = String(format: "%.1f TB / %.1f TB (%d%%)", used, total, percentage)
titleLabel.snp.makeConstraints { make in
make.top.leading.equalTo(containerView).offset(16)
}
progressView.snp.makeConstraints { make in
make.centerX.centerY.equalTo(containerView)
make.width.equalTo(200)
make.height.equalTo(6)
}
detailLabel.snp.makeConstraints { make in
make.top.equalTo(progressView.snp.bottom).offset(8)
make.centerX.equalTo(progressView)
}
return containerView
}
// MARK: - 创建组件:设备列表容器 Cell
private func createDeviceListCell() -> TPBBaseTableCellModel {
let containerView = UIView()
containerView.backgroundColor = .tpbBackground
let paddedView = UIView()
paddedView.backgroundColor = .clear
paddedView.layer.cornerRadius = 3
paddedView.clipsToBounds = true
containerView.addSubview(paddedView)
paddedView.addSubview(deviceListView)
// 布局约束
paddedView.snp.makeConstraints { make in
make.edges.equalTo(containerView).inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16))
}
deviceListView.snp.makeConstraints { make in
make.edges.equalTo(paddedView)
make.height.greaterThanOrEqualTo(1)
}
// 使用 deviceListView 作为内容视图
let cellModel = TPBBaseTableCellModel.customContent(with: deviceListView)
// ✅ 正确设置高度:使用 OC 工厂方法
cellModel.height = TPBTableElementHeight.automatic()
return cellModel
}
// MARK: - 创建组件:多屏按钮作为 Header View
private func createMultiScreenHeader() -> UIView {
let headerView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 44))
headerView.backgroundColor = .clear
let container = TPBBaseView()
container.backgroundColor = .clear
container.layer.cornerRadius = 3
container.clipsToBounds = true
headerView.addSubview(container)
container.snp.makeConstraints { make in
make.trailing.equalTo(headerView).offset(-16)
make.centerY.equalTo(headerView)
make.size.equalTo(CGSize(width: 44, height: 44))
}
multiScreenButton.removeFromSuperview()
container.addSubview(multiScreenButton)
multiScreenButton.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
return headerView
}
// MARK: - 创建合并后的 Section(Header + Cell)
private func createMergedDeviceSection() -> TPBTableSectionModel {
let section = TPBTableSectionModel()
section.customHeaderView = createMultiScreenHeader()
section.sectionHeaderHeight = TPBTableElementHeight.customHeight(44)
section.cellModelArray = [createDeviceListCell()]
return section
}
// MARK: - 刷新数据
private func reloadData() {
var tempSectionArray = [TPBTableSectionModel]()
// Section 0: 设备数量
let section0 = TPBTableSectionModel()
let deviceCountView = createDeviceCountView()
let deviceCountCellModel = TPBBaseTableCellModel.customContent(with: deviceCountView)
deviceCountCellModel.height = TPBTableElementHeight.customHeight(100)
section0.cellModelArray = [deviceCountCellModel]
tempSectionArray.append(section0)
// Section 1: 存储空间
let section1 = TPBTableSectionModel()
let storageView = createStorageUsageView()
let storageCellModel = TPBBaseTableCellModel.customContent(with: storageView)
storageCellModel.height = TPBTableElementHeight.customHeight(100)
section1.cellModelArray = [storageCellModel]
tempSectionArray.append(section1)
// Section 2: 合并后的设备列表
let section2 = createMergedDeviceSection()
tempSectionArray.append(section2)
sectionArray = tempSectionArray
tableView.reloadData()
}
// MARK: - Actions
@objc private func clickMultileLive() {
print("Multi-Screen 按钮被点击")
}
@objc private func jumpToXXX() {
print("跳转到【待定】页面")
}
@objc private func centralButtonClicked() {
print("跳转到【中心监控】页面")
let centralJumpVC = CentralJumpViewController()
let centralJumpNaviVC = BaseNavigationController(rootViewController: centralJumpVC)
centralJumpNaviVC.view.frame = self.view.frame
centralJumpNaviVC.view.backgroundColor = .clear
centralJumpVC.currentSiteId = selectedTabType == .all ? nil : selectedSiteInfo?.siteId
centralJumpVC.maskDidClickBlock = {
centralJumpNaviVC.view.removeFromSuperview()
}
UIApplication.shared.keyWindow?.addSubview(centralJumpNaviVC.view)
}
@objc private func onMessageButtonTapped() {
print("消息按钮被点击")
}
// MARK: - 加载视图控制
func hideLoadingView() {
titleView.hideLoadingAnimation()
}
// MARK: - SelectOrganizationViewDelegate
func didSelectOrganization(_ organization: OrganizationModel) {
titleView.titleText = organization.name
hideLoadingView()
reloadData()
}
func didCancelSelectOrganization() {}
// MARK: - 设备数据管理
private func loadDevicesFromContext() {
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
// 获取所有设备
let devices = TPAppContextFactory.shared().devices(inDeviceGroup: "default", includingHiddenChannels: false)
let filteredDevices = devices.filter { $0.listType == .remote && $0.displayOnline }
DispatchQueue.main.async {
self.devices = filteredDevices
}
}
}
private func forceRefreshDeviceList() {
loadDevicesFromContext()
}
// MARK: - 弹出菜单
private func showDeviceMenu(for device: TPSSDeviceForDeviceList, channel: TPSSChannelInfo? = nil) {
let menu = DeviceListMenuView(frame: .zero)
// 配置菜单项显示逻辑
DeviceListMenuView.configure(device: device, channel: channel)
// // 设置点击回调
// menu.action = { [weak self] (item, device, channel) in
// switch item {
// case .setting:
// self?.jumpToDeviceSetting(for: device, channel: channel)
//
// case .alarmMode:
// self?.toggleNotification(for: device, channel: channel)
//
// case .upgrade:
// self?.startFirmwareUpgrade(for: device, channel: channel)
//
// case .unbind:
// self?.confirmUnbindDevice(for: device, channel: channel)
//
// case .collect:
// self?.toggleFavoriteStatus(for: device, channel: channel)
//
// @unknown default:
// assertionFailure("Unhandled menu item: \(item)")
// }
// }
// 弹出菜单,忽略返回值(或保存)
_ = presentGuideWith(
viewToPresent: menu,
size: CGSize(width: 200, height: menu.items.preferredheight),
source: self.multiScreenButton
)
}
// private func jumpToDeviceSetting(for device: TPSSDeviceForDeviceList, channel: TPSSChannelInfo?) {
// print("跳转到设备设置: \(device.deviceName ?? device.deviceId)")
// // 实际跳转逻辑
// let vc = DeviceSettingViewController(device: device, channel: channel)
// navigationController?.pushViewController(vc, animated: true)
// }
// private func toggleNotification(for device: TPSSDeviceForDeviceList, channel: TPSSChannelInfo?) {
// print("切换通知: \(device.deviceName ?? device.deviceId)")
//
// let enable = !(channel?.messageEnable ?? device.messageEnable)
// DeviceService.shared.setNotificationEnabled(enable, for: device, channel: channel) { result in
// switch result {
// case .success:
// DispatchQueue.main.async {
// // 可选:刷新菜单状态
// }
// case .failure(let error):
// print("设置通知失败: $error)")
// }
// }
// }
//
// private func startFirmwareUpgrade(for device: TPSSDeviceForDeviceList, channel: TPSSChannelInfo?) {
// print("开始固件升级: \(device.deviceName ?? device.deviceId)")
// let vc = FirmwareUpgradeViewController(device: device, channel: channel)
// navigationController?.pushViewController(vc, animated: true)
// }
// private func confirmUnbindDevice(for device: TPSSDeviceForDeviceList, channel: TPSSChannelInfo?) {
// let alert = UIAlertController(title: "解绑设备", message: "确定要解绑此设备吗?", preferredStyle: .alert)
// alert.addAction(.init(title: "取消", style: .cancel))
// alert.addAction(.init(title: "确定", style: .destructive) { _ in
// UnbindService.unbind(device: device, channel: channel)
// })
// present(alert, animated: true)
// }
//
// private func toggleFavoriteStatus(for device: TPSSDeviceForDeviceList, channel: TPSSChannelInfo?) {
// let isVms = TPAppContextFactory.shared().isVmsLogin
// let isCollected = isVms ? device.isVMSFavorited : device.isCollected
//
// FavoriteService.updateFavoriteStatus(of: device, channel: channel, isFavorite: !isCollected) { [weak self] success in
// if success {
// DispatchQueue.main.async {
// // 成功后可刷新 UI
// }
// }
// }
// }
}
最新发布