Swift:如何在导航栏标题位置添加UICollectionView(仿iOS短信应用)
看起来你正在尝试复刻iOS短信App的导航栏标题横向滚动效果,这个思路非常靠谱!我帮你补全并优化代码,同时点明几个关键细节,让你的实现更贴近原生效果:
1. 补全并优化初始化代码
先修正你代码里的拼写小错误,同时完善初始化逻辑(重点解决布局依赖的问题):
// 如果是在导航控制器中,建议用已有的导航栏而不是新建 let navigationBar = self.navigationController?.navigationBar ?? UINavigationBar() var collectionView: UICollectionView! let layout = UICollectionViewFlowLayout() // 短信App是横向滚动标题,所以设置滚动方向为水平 layout.scrollDirection = .horizontal layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) // 匹配原生左右边距 layout.minimumInteritemSpacing = 8 // 标题之间的间距 // 初始化CollectionView,先给个临时frame,后续用自动布局约束 collectionView = UICollectionView(frame: navigationBar.bounds, collectionViewLayout: layout) collectionView.backgroundColor = .clear // 透明背景和导航栏融合 collectionView.showsHorizontalScrollIndicator = false // 隐藏滚动条,和原生一致 collectionView.isScrollEnabled = true // 添加到导航栏并设置自动布局(*这步非常关键*,避免frame依赖导致的布局错乱) navigationBar.addSubview(collectionView) collectionView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ collectionView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: navigationBar.trailingAnchor), collectionView.topAnchor.constraint(equalTo: navigationBar.topAnchor), collectionView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor) ])
2. 核心功能实现(数据源、代理与样式)
要让这个CollectionView真正工作,还需要实现数据源、代理,并复刻原生的选中样式:
注册Cell与设置代理
在viewDidLoad中添加:
// 自定义Cell的话可以替换成你的自定义类 collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "TitleCell") collectionView.dataSource = self collectionView.delegate = self
实现数据源方法
extension YourViewController: UICollectionViewDataSource { // 替换成你的标题数组 let titleList = ["消息", "置顶", "最近删除"] func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return titleList.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TitleCell", for: indexPath) cell.contentView.subviews.forEach { $0.removeFromSuperview() } // 避免复用重复添加 // 添加标题标签 let titleLabel = UILabel(frame: cell.contentView.bounds) titleLabel.text = titleList[indexPath.item] titleLabel.textAlignment = .center titleLabel.font = UIFont.systemFont(ofSize: 17, weight: .semibold) titleLabel.textColor = indexPath.item == collectionView.indexPathsForSelectedItems?.first?.item ? .systemBlue : .label cell.contentView.addSubview(titleLabel) // 模仿原生选中时的蓝色下划线 let underline = UIView(frame: CGRect(x: 0, y: cell.bounds.height - 2, width: cell.bounds.width, height: 2)) underline.backgroundColor = .systemBlue underline.isHidden = indexPath.item != collectionView.indexPathsForSelectedItems?.first?.item cell.contentView.addSubview(underline) return cell } }
实现代理方法(动态计算Item宽度+选中逻辑)
extension YourViewController: UICollectionViewDelegateFlowLayout { // 根据标题文本长度动态计算Item宽度,比固定尺寸更贴合原生 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let title = titleList[indexPath.item] let textWidth = title.size(withAttributes: [.font: UIFont.systemFont(ofSize: 17, weight: .semibold)]).width return CGSize(width: textWidth + 32, height: navigationBar.frame.height) // 加上左右内边距 } // 选中标题时的逻辑:切换选中状态+滚动到居中位置 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems) // 更新选中样式 } }
3. 适配优化细节
- 屏幕旋转适配:监听屏幕旋转,刷新布局避免Item尺寸错乱:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { _ in self.collectionView.collectionViewLayout.invalidateLayout() }) }
- 自定义Cell封装:建议把标题标签和选中样式封装成自定义
UICollectionViewCell,避免在数据源方法里重复写UI代码。
内容的提问来源于stack exchange,提问作者Alex Ritter
相关产品推荐
相关产品推荐

