UICollectionView一个section只有一个item的时候它会出现在屏幕中间的bug

本文介绍如何使用UICollectionViewFlowLayout的自定义方法解决特定布局需求,即首行单元素显示,后续行双元素左对齐的问题。

需求:为实现第一行显示一个,第二行以后显示两个

方案1:用系统自带的流布局,实现的效果是,若第二行只有一个,则系统默认会居中显示,不是左对齐(如下图),不符合项目要求。

方案2:自定义系统的UICollectionViewFLowLayout,主要代码如下, 只要继承super的layoutAttributes,修改section=0,row=1的Item的X 为0即可

(之前走了很多弯路,

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.translatesAutoresizingMaskIntoConstraints = false return scrollView }() private let tabButtonTitles = ["所有设备", "收藏页面", "站点选择"] private var selectedTabIndex = 0 private var tabButtons: [UIButton] = [] private var collectionView: UICollectionView! // 新增:固定头部视图(始终在最上方) private lazy var stickyHeader: UIView = { let header = UIView() header.backgroundColor = .tpbBackground header.layer.cornerRadius = 0 header.clipsToBounds = false return header }() private lazy var backButton: UIButton = { let btn = UIButton(type: .custom) btn.setTitle("1", for: .normal) btn.setTitleColor(.systemBlue, 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("2", for: .normal) btn.setTitleColor(.systemBlue, 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 = .tpbTextPrimary label.textAlignment = .center return label }() private lazy var searchButton: UIButton = { let btn = UIButton(type: .custom) btn.setTitle("3", for: .normal) btn.setTitleColor(.systemBlue, for: .normal) btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) btn.addTarget(self, action: #selector(onSearchTapped), for: .touchUpInside) return btn }() // MARK: - 生命周期 override func viewDidLoad() { super.viewDidLoad() self.tableView.contentInsetAdjustmentBehavior = .never } 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) setupCollectionView() createTabButtons() reloadData() } override func tpbMakeConstraint() { // super.tpbMakeConstraint() // 布局 stickyHeader 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) } // tableView 紧贴 stickyHeader 下方 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(.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) _ = btn.intrinsicContentSize return btn } updateTabSelection(index: selectedTabIndex) } @objc private func onTabTapped(_ sender: UIButton) { guard sender.tag != selectedTabIndex else { return } tabButtons[selectedTabIndex].isSelected = false selectedTabIndex = sender.tag sender.isSelected = true collectionView.reloadData() } private func updateTabSelection(index: Int) { for (i, btn) in tabButtons.enumerated() { btn.isSelected = (i == index) } } // MARK: - 构建独立的 View 组件 /// 构建“设备数量”View private func createDeviceCountView() -> UIView { let view = UIView() view.backgroundColor = UIColor.yellow.withAlphaComponent(0.2) view.layer.cornerRadius = 8 view.clipsToBounds = true let label = UILabel() label.text = "设备总数:32 台" label.font = UIFont.systemFont(ofSize: 16, weight: .medium) label.textColor = .black label.textAlignment = .center view.addSubview(label) label.snp.makeConstraints { make in make.edges.equalTo(view).inset(12) } return view } /// 构建“存储空间”View private func createStorageUsageView() -> UIView { let view = UIView() view.backgroundColor = UIColor.orange.withAlphaComponent(0.2) view.layer.cornerRadius = 8 view.clipsToBounds = true let label = UILabel() label.text = "已用存储:1.2 TB / 5 TB" label.font = UIFont.systemFont(ofSize: 16, weight: .medium) label.textColor = .blue label.textAlignment = .center view.addSubview(label) label.snp.makeConstraints { make in make.edges.equalTo(view).inset(12) } return view } /// 构建 tabScrollView 的容器 View(替代原来的 tableHeaderView) private func createTabScrollContainerView() -> UIView { let container = UIView() container.backgroundColor = .clear container.addSubview(tabScrollView) tabScrollView.snp.makeConstraints { make in make.top.equalTo(container).offset(16) make.leading.trailing.equalToSuperview().inset(8) make.height.equalTo(40) make.bottom.equalTo(container).offset(-16) } layoutTabButtonsInScrollView() // 背景装饰 tabScrollView.backgroundColor = UIColor.yellow.withAlphaComponent(0.2) tabScrollView.layer.cornerRadius = 8 tabScrollView.clipsToBounds = true return container } /// 初始化 CollectionView 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 = .white collectionView.alwaysBounceVertical = false collectionView.showsVerticalScrollIndicator = false collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.dataSource = self collectionView.delegate = self // 用于 layout 回调 collectionView.register(DeviceListCell.self, forCellWithReuseIdentifier: "DeviceListCell") } // MARK: - 加载数据(核心:全部改为 Cell) private func reloadData() { var sectionArray = [TPBTableSectionModel]() let section = TPBTableSectionModel() var cellModels = [TPBBaseTableCellModel]() // 设备数量 Cell let deviceCountCellModel = TPBBaseTableCellModel.customContent(with: createDeviceCountView()) deviceCountCellModel.height = .customHeight(60) cellModels.append(deviceCountCellModel) // 存储空间 Cell let storageCellModel = TPBBaseTableCellModel.customContent(with: createStorageUsageView()) storageCellModel.height = .customHeight(60) cellModels.append(storageCellModel) // tabScrollView 区域 let tabScrollCellModel = TPBBaseTableCellModel.customContent(with: createTabScrollContainerView()) tabScrollCellModel.height = .customHeight(72) cellModels.append(tabScrollCellModel) // collectionView Cell let collectionCellModel = TPBBaseTableCellModel.customContent(with: collectionView) collectionCellModel.height = .customHeight(800) cellModels.append(collectionCellModel) section.cellModelArray = cellModels sectionArray.append(section) self.sectionArray = sectionArray tableView.reloadData() } // MARK: - Action 回调 @objc private func onBackTapped() { print("返回") navigationController?.popViewController(animated: true) } @objc private func onLocationTapped() { print("定位") } @objc private func onSearchTapped() { print("搜索") } // MARK: - 布局 Tab 按钮到 ScrollView private func layoutTabButtonsInScrollView() { // 先清空 scrollView 内容 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 } // 最后一个按钮约束到底部右边 previousButton?.snp.makeConstraints { make in make.trailing.equalTo(tabScrollView).offset(-16) } // 强制刷新布局 tabScrollView.layoutIfNeeded() } } // MARK: - UICollectionViewDataSource extension DeviceListNewViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 30 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DeviceListCell", for: indexPath) as! DeviceListCell cell.configure(with: "设备 \(indexPath.item + 1)") return cell } } // MARK: - UICollectionViewDelegateFlowLayout extension DeviceListNewViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let inset: CGFloat = 16 * 2 // 左右 sectionInset let spacing: CGFloat = 10 // item 间距 let width = (collectionView.bounds.width - inset - spacing) / 2 return CGSize(width: width, height: 80) // 固定高度 80,整齐划一 } } // 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.systemBlue.withAlphaComponent(0.1) layer.borderColor = UIColor.systemBlue.cgColor layer.borderWidth = 1 layer.cornerRadius = 8 clipsToBounds = true // 标题居中显示 titleLabel.font = UIFont.systemFont(ofSize: 16, weight: .medium) titleLabel.textColor = .systemBlue titleLabel.textAlignment = .center titleLabel.numberOfLines = 1 contentView.addSubview(titleLabel) titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.snp.makeConstraints { make in make.center.equalToSuperview() make.leading.trailing.greaterThanOrEqualToSuperview().inset(8) } } func configure(with title: String) { titleLabel.text = title } override func prepareForReuse() { super.prepareForReuse() titleLabel.text = nil } }我这个里面的滚动区域相关的是怎么个情况
最新发布
12-03
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值