viewDidLayoutSubviews

本文探讨了在iOS开发中使用Autolayout时遇到的问题,特别是关于视图在不同尺寸屏幕上的布局表现不一致的情况。作者分享了一个经验教训,即如何正确地利用ViewDidLayoutSubviews来替代ViewDidLoad,确保视图能够根据约束正确调整大小。

由于种种原因,最近才开始真正在新项目中使用autolayout,使用过程中虽说是比较顺畅,但是也遇到了一些麻烦,比如:


我使用的XIB默认是4寸屏幕,我再XIB中增加一个viewA,宽度为320,约束为SuperView等宽。

然后我用4寸模拟器,代码跑起来,在ViewDidload中取出viewA的宽度,发现宽度为320,SuperView(self.view)的宽度也为320.

这个结果没有错误。

但是我用5.5寸的模拟器,代码跑起来,在ViewDidLoad取出viewA的宽度发现还是320,SuperView(self.view)的宽度也为320.

此时就不对了

这会导致在ViewDidLoad中初始化一些控件时,导致这些控件的frame的大小是在XIB默认的大小,而不是我们增加约束后希望的大小。


笔者这个问题纠结了很久也百度Google了很久,也不知道是关键字没有正确还是什么原因,寻求无果,最后还是请教一个大神才豁然开朗。

原来在iOS5.0开始就有另外一个生命周期函数ViewDidLayoutSubViews这个方法基本可以代替ViewDidload使用,只不过差别在于前者是约束后,后者是约束前。


当让聪明的读者肯定会想到有Did肯定也会有Will,即ViewWillLayoutSubViews.


希望本片文章可以帮到大家!

我原本的代码里没有override func viewDidLayoutSubviews() {这个方法 我加在了scrollView的后面 这样可以吗?private lazy var scrollView: DeviceListMasterMultiResponseScrollView = { guard !showLocalDeviceOnly else { return DeviceListMasterMultiResponseScrollView() } let scrollView = DeviceListMasterMultiResponseScrollView() scrollView.bounces = false scrollView.isScrollEnabled = true scrollView.allowPanDownToShowSearchBar = allowPanDownToShowSearchBar scrollView.showsVerticalScrollIndicator = false scrollView.delegate = self scrollView.backgroundColor = .clear scrollView.contentInset = UIEdgeInsets.zero scrollView.addSubview(scrollViewContainerView) return scrollView }() private lazy var scrollViewContainerView: UIView = { guard !showLocalDeviceOnly else { return UIView() } let scrollViewContainerView = UIView() scrollViewContainerView.backgroundColor = .clear scrollViewContainerView.addSubview(headerSearchView) scrollViewContainerView.addSubviews([tabBarContainer]) // 注意:SnapKit 约束不需要改,它们是基于 Auto Layout 的 headerSearchView.snp.makeConstraints { make in make.top.equalToSuperview().offset(10) make.height.equalTo(62) make.leading.trailing.equalToSuperview() } tabBarContainer.snp.makeConstraints { make in make.top.equalTo(headerSearchView.snp.bottom) make.height.equalTo(tabCollectionHeightConstant.constant) make.leading.equalToSuperview() make.trailing.equalToSuperview() } return scrollViewContainerView }() override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() // 如果只显示本地设备,则不进行任何布局 guard !showLocalDeviceOnly else { return } // 获取最新的布局参数 let top: CGFloat = navigationController?.navigationBar.frame.maxY ?? 0 let bottom: CGFloat = tabBarController?.tabBar.frame.height ?? 0 let width = view.bounds.width let height = view.bounds.height - top - bottom // 更新 scrollView 的 frame 和 contentSize scrollView.frame = CGRect(x: 0, y: top, width: width, height: height) // 内容高度 = 可见区域 + 额外头部空间 let contentHeight = height + nameLabelViewHeight + 62 scrollView.contentSize = CGSize(width: width, height: contentHeight) // 更新容器视图的 frame(注意 y=0,它是 scrollView 内部的内容起点) scrollViewContainerView.frame = CGRect(x: 0, y: 0, width: width, height: contentHeight) // 可选:如果你有其他子视图需要调整,也可以在这里补充 }
10-29
// // OnlineDeviceListViewController2.swift // SurveillanceHome // // Created by MaCong on 2025/10/10. // Copyright © 2025 tplink. All rights reserved. // import UIKit class OnlineDeviceListViewController2: OnlineDeviceListViewController { private var customCollectionView: UICollectionView? private let image: UIImage = { if #available(iOS 13.0, *) { return UIImage(named: "device_evaluation_fiveStars") ?? UIImage(systemName: "photo")! } else { return UIImage(named: "device_evaluation_fiveStars") ?? UIImage(named: "defaultImage") ?? UIImage() } }() override func viewDidLoad() { super.viewDidLoad() setupCustomCollectionView() } override func viewDidLoad() { super.viewDidLoad() // 确保父类没有干扰布局的代码(如添加了其他子视图) setupCustomCollectionView() } // 在 viewDidLayoutSubviews 中动态更新布局(关键!) override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() setupCustomCollectionViewIfNeeded() // 重新初始化布局(确保尺寸正确) customCollectionView?.collectionViewLayout.invalidateLayout() // 强制刷新 } private func setupCustomCollectionView() { // 如果已存在则移除,避免重复添加 customCollectionView?.removeFromSuperview() let layout = UICollectionViewFlowLayout() let itemSpacing: CGFloat = 10 let numberOfColumns: CGFloat = 2 let inset: CGFloat = 10 // 动态计算单元格宽度(基于当前视图尺寸) let availableWidth = view.bounds.width - inset * 2 - (numberOfColumns - 1) * itemSpacing let cellWidth = availableWidth / numberOfColumns layout.itemSize = CGSize(width: cellWidth, height: cellWidth) layout.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: inset, right: inset) layout.minimumInteritemSpacing = itemSpacing layout.minimumLineSpacing = itemSpacing customCollectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) // 直接使用 view.bounds guard let collectionView = customCollectionView else { return } collectionView.backgroundColor = .white collectionView.register(ImageCell.self, forCellWithReuseIdentifier: "ImageCell") collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.contentInset = .zero // 关键:确保 collectionView 是唯一子视图,或位于最上层 view.subviews.forEach { $0.removeFromSuperview() } // 清除其他子视图(调试用,慎用) view.addSubview(collectionView) // 约束改为直接绑定到 view 的边缘(忽略 safeArea) NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: view.topAnchor), collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) collectionView.contentInsetAdjustmentBehavior = .never collectionView.dataSource = self collectionView.delegate = self } } // MARK: - 数据源协议 extension OnlineDeviceListViewController2: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 2 // 返回2个单元格 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell cell.configure(with: image) return cell } } // MARK: - 代理协议 extension OnlineDeviceListViewController2 { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { print("图片 \(indexPath.row + 1) 被点击") } } // ImageCell 实现保持不变... class ImageCell: UICollectionViewCell { private let imageView = UIImageView() override init(frame: CGRect) { super.init(frame: frame) setupUI() } required init?(coder: NSCoder) { fatalError("init(coder:)未实现") } private func setupUI() { backgroundColor = .lightGray layer.cornerRadius = 8 clipsToBounds = true imageView.contentMode = .scaleAspectFill // 必须填满单元格 imageView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(imageView) // 绝对紧贴边缘(无任何间距) NSLayoutConstraint.activate([ imageView.topAnchor.constraint(equalTo: contentView.topAnchor), imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) ]) } func configure(with image: UIImage) { imageView.image = image } } 完整修改
10-13
帮我修改这些地方: private lazy var scrollView: DeviceListMasterMultiResponseScrollView = { guard !showLocalDeviceOnly else { return DeviceListMasterMultiResponseScrollView() } let scrollView = DeviceListMasterMultiResponseScrollView() scrollView.bounces = false scrollView.isScrollEnabled = true scrollView.allowPanDownToShowSearchBar = allowPanDownToShowSearchBar scrollView.showsVerticalScrollIndicator = false scrollView.delegate = self scrollView.backgroundColor = .clear scrollView.contentInset = UIEdgeInsets.zero scrollView.addSubview(scrollViewContainerView) return scrollView }() private lazy var scrollViewContainerView: UIView = { guard !showLocalDeviceOnly else { return UIView() } let scrollViewContainerView = UIView() scrollViewContainerView.backgroundColor = .clear scrollViewContainerView.addSubview(headerSearchView) scrollViewContainerView.addSubviews([tabBarContainer]) // 注意:SnapKit 约束不需要改,它们是基于 Auto Layout 的 headerSearchView.snp.makeConstraints { make in make.top.equalToSuperview().offset(10) make.height.equalTo(62) make.leading.trailing.equalToSuperview() } tabBarContainer.snp.makeConstraints { make in make.top.equalTo(headerSearchView.snp.bottom) make.height.equalTo(tabCollectionHeightConstant.constant) make.leading.equalToSuperview() make.trailing.equalToSuperview() } return scrollViewContainerView }() override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() guard !showLocalDeviceOnly else { return } // 安全获取导航栏底部 let top: CGFloat = navigationController?.navigationBar.frame.maxY ?? 0 // ✅ 使用 tabBar.isHidden 而不是 isTabBarHidden(兼容 iOS < 18) let tabBar = tabBarController?.tabBar let isTabBarHidden = tabBar?.isHidden ?? true let tabBarHeight = tabBar?.frame.height ?? 0 let safeBottom = view.safeAreaInsets.bottom // 只有当 TabBar 存在且未被隐藏时才加上它的高度 let bottom = !isTabBarHidden ? (tabBarHeight + safeBottom) : safeBottom let width = view.bounds.width let visibleHeight = view.bounds.height - top - bottom let contentHeight = visibleHeight + nameLabelViewHeight + 62 // 避免重复设置相同的 frame if scrollView.frame != CGRect(x: 0, y: top, width: width, height: visibleHeight) { scrollView.frame = CGRect(x: 0, y: top, width: width, height: visibleHeight) scrollView.contentSize = CGSize(width: width, height: contentHeight) scrollViewContainerView.frame = CGRect(x: 0, y: 0, width: width, height: contentHeight) // 可选:强制刷新显示 scrollView.setNeedsDisplay() } } 以及viewdidload里的 @objc private func orientationDidChange() { DispatchQueue.main.async { // 强制重新计算所有布局 self.view.setNeedsLayout() self.view.layoutIfNeeded() // 立即执行 layout } } override func viewDidLoad() { super.viewDidLoad() // 开启设备方向通知 UIDevice.current.beginGeneratingDeviceOrientationNotifications() // 监听方向改变 NotificationCenter.default.addObserver( self, selector: #selector(orientationDidChange), name: UIDevice.orientationDidChangeNotification, object: nil )
10-29
import UIKit import SnapKit class DeviceListNewViewController: SurveillanceCommonTableController { // MARK: - UI Components private lazy var fixedTitleView: UIView = { let view = UIView() view.backgroundColor = .systemRed.withAlphaComponent(0.8) // 🔴 return view }() private lazy var titleLabel: UILabel = { let label = UILabel() label.text = "设备列表" label.font = UIFont.systemFont(ofSize: 20, weight: .medium) label.textColor = .white label.textAlignment = .center return label }() private lazy var tabScrollView: UIScrollView = { let scrollView = UIScrollView() scrollView.showsHorizontalScrollIndicator = true scrollView.bounces = true scrollView.alwaysBounceHorizontal = true scrollView.backgroundColor = .systemGreen.withAlphaComponent(0.5) // 🟢 return scrollView }() private let tabButtonTitles = ["所有设备", "收藏页面", "站点选择"] private var selectedTabIndex = 0 private var tabButtons: [UIButton] = [] private var collectionView: UICollectionView! private lazy var stickyContainer: UIView = { let view = UIView() view.backgroundColor = .clear return view }() // MARK: - Constraint private var stickyTopConstraint: Constraint? private var hasSetupHeader = false private let tabScrollViewHeight: CGFloat = 60 // MARK: - 👇 把 addSubViews 放进 setup 阶段,确保约束前已有父视图 override func tpbSetupSubviews() { super.tpbSetupSubviews() // ✅ 第一步:构建所有 view 层级关系(全部提前 addSubview) view.addSubview(fixedTitleView) view.addSubview(stickyContainer) view.addSubview(tableView) // 确保 tableView 是最后加的?根据你的基类逻辑调整 stickyContainer.addSubview(tabScrollView) setupTitleView() createTabButtons() setupCollectionView() // 添加按钮到 tabScrollView for button in tabButtons { tabScrollView.addSubview(button) } updateTabSelection(index: selectedTabIndex) } private func setupTitleView() { fixedTitleView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.center.equalToSuperview() make.leading.greaterThanOrEqualToSuperview().offset(20) make.trailing.lessThanOrEqualToSuperview().offset(-20) } } 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(.black, 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) return btn } } 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 = .gray collectionView.alwaysBounceVertical = true collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "DeviceCell") } // MARK: - ✅ 所有约束在此设置,此时所有 view 都已 addSubview override func tpbMakeConstraint() { // 固定标题栏 fixedTitleView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide) make.leading.trailing.equalToSuperview() make.height.equalTo(40) } // stickyContainer stickyContainer.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.height.equalTo(tabScrollViewHeight) self.stickyTopConstraint = make.top.equalTo(fixedTitleView.snp.bottom).constraint } // ✅ 此时 tabScrollView 已经有父视图 stickyContainer → superview 不为 nil tabScrollView.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) make.height.equalTo(tabScrollViewHeight - 8) } // tableView 占据剩余空间 tableView.snp.remakeConstraints { make in make.top.equalTo(stickyContainer.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } } // MARK: - Layout Buttons private func layoutTabButtonsInScrollView() { for button in tabButtons { button.snp.removeConstraints() } let buttonHeight: CGFloat = 36 let padding: CGFloat = 16 let spacing: CGFloat = 8 var lastButton: UIButton? for button in tabButtons { button.snp.makeConstraints { make in make.centerY.equalToSuperview() make.height.equalTo(buttonHeight) if let prev = lastButton { make.leading.equalTo(prev.snp.trailing).offset(spacing) } else { make.leading.equalTo(tabScrollView).offset(padding) } make.width.greaterThanOrEqualTo(80) make.width.lessThanOrEqualTo(120) } lastButton = button } if let last = lastButton { last.snp.makeConstraints { make in make.trailing.equalTo(tabScrollView).offset(-padding) } } tabScrollView.layoutIfNeeded() print("📊 tabScrollView contentSize: \(tabScrollView.contentSize)") } // MARK: - Data private func reloadData() { var tempSectionArray = [TPBTableSectionModel]() let section = TPBTableSectionModel() var cellModels = [TPBBaseTableCellModel]() let numItems = 20 let itemsPerRow: CGFloat = 2 let rowHeight: CGFloat = 80 let lineSpacing: CGFloat = 10 let numRows = ceil(CGFloat(numItems) / itemsPerRow) let collectionHeight = numRows * rowHeight + (numRows - 1) * lineSpacing let collectionCellModel = TPBBaseTableCellModel.customContent(with: collectionView) collectionCellModel.height = TPBTableElementHeight.customHeight(collectionHeight) cellModels.append(collectionCellModel) section.cellModelArray = cellModels tempSectionArray.append(section) sectionArray = tempSectionArray tableView.reloadData() } override func viewDidLoad() { super.viewDidLoad() tableView.delegate = self collectionView.dataSource = self collectionView.delegate = self } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() guard view.bounds.width > 0 else { return } if !hasSetupHeader { hasSetupHeader = true layoutTabButtonsInScrollView() reloadData() } } // MARK: - ScrollView Delegate override func scrollViewDidScroll(_ scrollView: UIScrollView) { super.scrollViewDidScroll(scrollView) guard scrollView === tableView else { return } let offsetY = scrollView.contentOffset.y let fixedTitleBottom = fixedTitleView.frame.maxY if offsetY <= 0 { stickyTopConstraint?.update(offset: fixedTitleBottom) } else { let targetY = max(fixedTitleBottom, fixedTitleBottom + offsetY) stickyTopConstraint?.update(offset: targetY - offsetY) } view.layoutIfNeeded() } // MARK: - Actions @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 } private func updateTabSelection(index: Int) { for (i, btn) in tabButtons.enumerated() { btn.isSelected = (i == index) } } } // MARK: - UICollectionViewDataSource extension DeviceListNewViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 20 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DeviceCell", for: indexPath) cell.backgroundColor = .systemBlue return cell } } // MARK: - UICollectionViewDelegateFlowLayout extension DeviceListNewViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let padding: CGFloat = 32 + 10 let width = (collectionView.bounds.width > 0 ? collectionView.bounds.width : UIScreen.main.bounds.width) - padding return CGSize(width: width / 2, height: 80) } }
12-03
import UIKit import SnapKit class DeviceListNewViewController: SurveillanceCommonTableController { // MARK: - UI Components private lazy var fixedTitleView: UIView = { let view = UIView() view.backgroundColor = .systemRed.withAlphaComponent(0.8) // 🔴 return view }() private lazy var titleLabel: UILabel = { let label = UILabel() label.text = "设备列表" label.font = UIFont.systemFont(ofSize: 20, weight: .medium) label.textColor = .white label.textAlignment = .center return label }() private lazy var tabScrollView: UIScrollView = { let scrollView = UIScrollView() scrollView.showsHorizontalScrollIndicator = true scrollView.bounces = true scrollView.alwaysBounceHorizontal = true scrollView.backgroundColor = .systemGreen.withAlphaComponent(0.5) // 🟢 return scrollView }() private let tabButtonTitles = ["所有设备", "收藏页面", "站点选择"] private var selectedTabIndex = 0 private var tabButtons: [UIButton] = [] private var collectionView: UICollectionView! private lazy var stickyContainer: UIView = { let view = UIView() view.backgroundColor = .clear return view }() // MARK: - Constraint private var stickyTopConstraint: Constraint? private var hasSetupHeader = false private let tabScrollViewHeight: CGFloat = 60 // MARK: - 👇 把 addSubViews 放进 setup 阶段,确保约束前已有父视图 override func tpbSetupSubviews() { super.tpbSetupSubviews() // ✅ 第一步:构建所有 view 层级关系(全部提前 addSubview) view.addSubview(fixedTitleView) view.addSubview(stickyContainer) view.addSubview(tableView) // 确保 tableView 是最后加的?根据你的基类逻辑调整 stickyContainer.addSubview(tabScrollView) setupTitleView() createTabButtons() setupCollectionView() // 添加按钮到 tabScrollView for button in tabButtons { tabScrollView.addSubview(button) } updateTabSelection(index: selectedTabIndex) } private func setupTitleView() { fixedTitleView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.center.equalToSuperview() make.leading.greaterThanOrEqualToSuperview().offset(20) make.trailing.lessThanOrEqualToSuperview().offset(-20) } } 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(.black, 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) return btn } } 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 = .gray collectionView.alwaysBounceVertical = true collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "DeviceCell") } // MARK: - ✅ 所有约束在此设置,此时所有 view 都已 addSubview override func tpbMakeConstraint() { // 固定标题栏 fixedTitleView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide) make.leading.trailing.equalToSuperview() make.height.equalTo(40) } // stickyContainer stickyContainer.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.height.equalTo(tabScrollViewHeight) self.stickyTopConstraint = make.top.equalTo(fixedTitleView.snp.bottom).constraint } // ✅ 此时 tabScrollView 已经有父视图 stickyContainer → superview 不为 nil tabScrollView.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) make.height.equalTo(tabScrollViewHeight - 8) } // tableView 占据剩余空间 tableView.snp.remakeConstraints { make in make.top.equalTo(stickyContainer.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } } // MARK: - Layout Buttons private func layoutTabButtonsInScrollView() { for button in tabButtons { button.snp.removeConstraints() } let buttonHeight: CGFloat = 36 let padding: CGFloat = 16 let spacing: CGFloat = 8 var lastButton: UIButton? for button in tabButtons { button.snp.makeConstraints { make in make.centerY.equalToSuperview() make.height.equalTo(buttonHeight) if let prev = lastButton { make.leading.equalTo(prev.snp.trailing).offset(spacing) } else { make.leading.equalTo(tabScrollView).offset(padding) } make.width.greaterThanOrEqualTo(80) make.width.lessThanOrEqualTo(120) } lastButton = button } if let last = lastButton { last.snp.makeConstraints { make in make.trailing.equalTo(tabScrollView).offset(-padding) } } tabScrollView.layoutIfNeeded() print("📊 tabScrollView contentSize: \(tabScrollView.contentSize)") } // MARK: - Data private func reloadData() { var tempSectionArray = [TPBTableSectionModel]() let section = TPBTableSectionModel() var cellModels = [TPBBaseTableCellModel]() let numItems = 20 let itemsPerRow: CGFloat = 2 let rowHeight: CGFloat = 80 let lineSpacing: CGFloat = 10 let numRows = ceil(CGFloat(numItems) / itemsPerRow) let collectionHeight = numRows * rowHeight + (numRows - 1) * lineSpacing let collectionCellModel = TPBBaseTableCellModel.customContent(with: collectionView) collectionCellModel.height = TPBTableElementHeight.customHeight(collectionHeight) cellModels.append(collectionCellModel) section.cellModelArray = cellModels tempSectionArray.append(section) sectionArray = tempSectionArray tableView.reloadData() } override func viewDidLoad() { super.viewDidLoad() tableView.delegate = self collectionView.dataSource = self collectionView.delegate = self } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() guard view.bounds.width > 0 else { return } if !hasSetupHeader { hasSetupHeader = true layoutTabButtonsInScrollView() reloadData() } } // MARK: - ScrollView Delegate override func scrollViewDidScroll(_ scrollView: UIScrollView) { super.scrollViewDidScroll(scrollView) guard scrollView === tableView else { return } let offsetY = scrollView.contentOffset.y let fixedTitleBottom = fixedTitleView.frame.maxY if offsetY <= 0 { stickyTopConstraint?.update(offset: fixedTitleBottom) } else { let targetY = max(fixedTitleBottom, fixedTitleBottom + offsetY) stickyTopConstraint?.update(offset: targetY - offsetY) } view.layoutIfNeeded() } // MARK: - Actions @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 } private func updateTabSelection(index: Int) { for (i, btn) in tabButtons.enumerated() { btn.isSelected = (i == index) } } } // MARK: - UICollectionViewDataSource extension DeviceListNewViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 20 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DeviceCell", for: indexPath) cell.backgroundColor = .systemBlue return cell } } // MARK: - UICollectionViewDelegateFlowLayout extension DeviceListNewViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let padding: CGFloat = 32 + 10 let width = (collectionView.bounds.width > 0 ? collectionView.bounds.width : UIScreen.main.bounds.width) - padding return CGSize(width: width / 2, height: 80) } }
最新发布
12-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值