UITableView中奇怪的问题真不少!

本文探讨了UITableView中设置tableFooterView时出现的额外灰色分割线问题,以及如何设置cell的布局边界与颜色,特别指出对于UITableViewCellStyleValue1风格的cell某些设置不起作用的现象。

1.以前说到,可以设置tableview空白的部分不再显示分割线,self.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; 或者 self.tableFooterView = [UIView new]; 

但最近发现一个问题,如果我们分别设置tableview的头和尾,self.tableHeaderView = self.headView; self.tableFooterView = self.footView; 这样设置没有问题,可是顺序倒过

来,self.tableFooterView = self.footView; self.tableHeaderView = self.headView; 你会发现莫名其妙多了一条顶格的小灰线停留在底部,不同于默认的分割线,怎么都消不去。。。。

2.还有设置cell分割线顶格,

self.layoutMargins = UIEdgeInsetsZero;
self.preservesSuperviewLayoutMargins = NO;

//self在这里是一个tableview的子类对象

以及设置cell.tintColor = [UIColor RedColor];//这个可以用来设置cell右侧的accessoryView的颜色

上面的设置方式,当我们对于这样创建出来的cell,并没有任何作用

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cell"];
}
对于UITableViewCellStyleValue1风格的cell不好使,其他的还没去一一尝试,自己可以去试一下,感觉挺奇怪的 。。。。WTF

 

 

UITableViewCellAccessoryType

 

 

不是这个意思,肯定是后面写的什么代码影响了原本的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 // } // } // } // } }
最新发布
12-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值