Swift中CollectionView视差滚动实现及滚动时顶部视图隐藏需求咨询
嘿,我来帮你搞定这两个Swift CollectionView的常见问题,都是实际开发中经常遇到的场景,我给你一步步拆解实现方法:
视差滚动的核心思路就是随着CollectionView的滚动,让cell内的某个视图(比如背景图)以比滚动速度慢的节奏移动,营造出层次感。我常用的实现方式是通过UIScrollView的代理方法动态调整视图位置,具体步骤如下:
第一步:自定义CollectionViewCell,预留视差空间
先创建一个包含背景图的自定义Cell,让背景图的上下范围比cell本身大一些,这样滚动时才有移动的余地:class ParallaxCollectionViewCell: UICollectionViewCell { static let identifier = "ParallaxCollectionViewCell" let backgroundImageView: UIImageView = { let iv = UIImageView() iv.contentMode = .scaleAspectFill iv.clipsToBounds = true iv.translatesAutoresizingMaskIntoConstraints = false return iv }() override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { contentView.addSubview(backgroundImageView) // 让背景图比cell上下各多出20pt,给视差移动留空间 NSLayoutConstraint.activate([ backgroundImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: -20), backgroundImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 20), backgroundImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), backgroundImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) ]) } }第二步:在ViewController中监听滚动,动态调整视图位置
当CollectionView滚动时,获取可见cell,计算每个cell的滚动偏移比例,然后调整背景图的位置:class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { private var collectionView: UICollectionView! override func viewDidLoad() { super.viewDidLoad() setupCollectionView() } private func setupCollectionView() { let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: view.frame.width, height: 300) layout.minimumLineSpacing = 0 collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView.delegate = self collectionView.dataSource = self collectionView.register(ParallaxCollectionViewCell.self, forCellWithReuseIdentifier: ParallaxCollectionViewCell.identifier) view.addSubview(collectionView) } // MARK: - UICollectionView 数据源方法 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 10 // 示例数据数量 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ParallaxCollectionViewCell.identifier, for: indexPath) as! ParallaxCollectionViewCell // 替换成你的图片资源 cell.backgroundImageView.image = UIImage(named: "parallax_image_\(indexPath.item)") return cell } // MARK: - UIScrollView 代理方法 func scrollViewDidScroll(_ scrollView: UIScrollView) { // 遍历所有可见的视差cell for cell in collectionView.visibleCells as! [ParallaxCollectionViewCell] { // 转换cell的frame到CollectionView的父视图坐标系 let cellFrame = collectionView.convert(cell.frame, to: collectionView.superview) // 计算偏移比例:cell顶部与CollectionView顶部的距离 / cell高度 let parallaxRatio = (scrollView.contentOffset.y - cellFrame.origin.y) / cellFrame.size.height // 调整背景图的y轴偏移,数值20对应我们之前预留的上下空间,可自定义强度 cell.backgroundImageView.transform = CGAffineTransform(translationX: 0, y: parallaxRatio * 20) } } }你可以修改最后的乘数(比如把20改成30)来调整视差效果的强度,数值越大,背景图移动的幅度就越大,视差感越强。
这个场景需要监听CollectionView的滚动方向,通过约束动画来平滑调整视图位置,具体实现如下:
第一步:布局界面元素,绑定关键约束
先在ViewController中定义顶部视图、搜索栏和CollectionView,并且给顶部视图的高度和顶部约束添加IBOutlet,方便后续动态修改:class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource { // 需要隐藏的顶部视图 let topView: UIView = { let view = UIView() view.backgroundColor = .systemBlue view.translatesAutoresizingMaskIntoConstraints = false return view }() // 搜索栏 let searchBar: UISearchBar = { let bar = UISearchBar() bar.placeholder = "搜索内容..." bar.backgroundColor = .systemGray6 bar.translatesAutoresizingMaskIntoConstraints = false return bar }() private var collectionView: UICollectionView! // 顶部视图的关键约束 private var topViewHeightConstraint: NSLayoutConstraint! private var topViewTopConstraint: NSLayoutConstraint! // 记录上次滚动偏移量,用于判断滚动方向 private var lastScrollOffset: CGFloat = 0 override func viewDidLoad() { super.viewDidLoad() setupAllViews() } private func setupAllViews() { view.addSubview(topView) view.addSubview(searchBar) setupCollectionView() // 设置约束 topViewTopConstraint = topView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor) topViewHeightConstraint = topView.heightAnchor.constraint(equalToConstant: 80) // 顶部视图初始高度80pt NSLayoutConstraint.activate([ topViewTopConstraint, topViewHeightConstraint, topView.leadingAnchor.constraint(equalTo: view.leadingAnchor), topView.trailingAnchor.constraint(equalTo: view.trailingAnchor), searchBar.topAnchor.constraint(equalTo: topView.bottomAnchor), searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), collectionView.topAnchor.constraint(equalTo: searchBar.bottomAnchor), collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } private func setupCollectionView() { let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: view.frame.width, height: 100) layout.minimumLineSpacing = 10 collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.delegate = self collectionView.dataSource = self collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "BasicCell") collectionView.backgroundColor = .white collectionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(collectionView) } // MARK: - UICollectionView 数据源方法 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 20 // 示例数据数量 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BasicCell", for: indexPath) cell.backgroundColor = .systemGray5 return cell } }第二步:监听滚动方向,执行视图动画
在scrollViewDidScroll方法中判断滚动方向,向上滚动时隐藏顶部视图,向下滚动时恢复:// MARK: - UIScrollView 代理方法 func scrollViewDidScroll(_ scrollView: UIScrollView) { let currentOffset = scrollView.contentOffset.y let scrollDifference = currentOffset - lastScrollOffset // 计算顶部视图的目标高度和顶部位置 var targetHeight = topViewHeightConstraint.constant var targetTop = topViewTopConstraint.constant if scrollDifference > 0 { // 向上滚动:逐步隐藏顶部视图 targetHeight = max(0, targetHeight - abs(scrollDifference)) targetTop = min(0, targetTop - abs(scrollDifference)) } else if scrollDifference < 0 { // 向下滚动:逐步恢复顶部视图 targetHeight = min(80, targetHeight + abs(scrollDifference)) targetTop = max(0, targetTop + abs(scrollDifference)) } // 如果需要调整约束,执行动画 if targetHeight != topViewHeightConstraint.constant || targetTop != topViewTopConstraint.constant { topViewHeightConstraint.constant = targetHeight topViewTopConstraint.constant = targetTop UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) { self.view.layoutIfNeeded() } } // 更新上次滚动偏移量 lastScrollOffset = currentOffset }这段代码会根据滚动方向平滑调整顶部视图的高度和位置,动画时长0.3秒保证过渡自然,你可以根据需求调整动画参数或者顶部视图的初始高度。
内容的提问来源于stack exchange,提问作者Kannan Thandayuthapani

