You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中CollectionView视差滚动实现及滚动时顶部视图隐藏需求咨询

嘿,我来帮你搞定这两个Swift CollectionView的常见问题,都是实际开发中经常遇到的场景,我给你一步步拆解实现方法:

问题1:在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)来调整视差效果的强度,数值越大,背景图移动的幅度就越大,视差感越强。

问题2:滚动时隐藏顶部视图,让搜索栏移至顶部

这个场景需要监听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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:10:13