使用 UICollectionView 实现分页滑动效果

发布时间 - 2025-07-23 00:00:00    点击率:

在上一篇博文中,我们展示了如何使用 uicollectionview 实现卡片轮播效果。有网友反馈了一个问题:当 item 的宽度与屏幕宽度一致时,滚动效果正常,但当 item 宽度小于屏幕宽度时,会出现遮挡的 bug。如何解决这个问题呢?

这个问题确实存在,因为 UICollectionView 有一个分页属性 isPagingEnabled,当设置为 true 时,每次滚动的位移量等于屏幕宽度;当设置为 false 时(默认值),滚动没有分页效果。有人可能会问,UICollectionView 是否只能按照屏幕大小进行分页?答案当然是否定的。

要实现自定义的滚动分页,我们需要依赖 UICollectionViewFlowLayout。在 UICollectionViewFlowLayout 中,有一个可以重写的函数:

func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior

这个函数的返回值决定了 UICollectionView 停止滚动时的偏移量。我们可以通过重写这个函数来实现自定义的分页滚动。重写这个函数的逻辑如下:

  1. 定义一个 CGPoint 来记录最新滚动的偏移坐标。
  2. 定义两个值,分别为 UICollectionView 可滚动的最大偏移量和最小偏移量(即 0)。
  3. 每次滚动停止时都会调用上述函数 func targetContentOffset(...),这个函数有一个参数 proposedContentOffset 记录了滚动的目标位移坐标。通过这个坐标和记录的上次滚动的坐标,可以判断出是向左滚动还是向右滚动。
  4. 如果两个坐标的水平方向相减的绝对值大于某个固定值(例如 item 宽度的八分之一),则可以判断发生了分页,然后通过 proposedContentOffset 位移坐标和 item 的宽度大小来计算出当前滚动的页码;如果小于那个固定值,则不发生分页。
  5. 最后记录最新的偏移坐标,然后返回 UICollectionView 停止滚动时的偏移量。

言语空洞,让我们看代码,实现如下:

class RowStyleLayout: UICollectionViewFlowLayout {
    private var lastOffset: CGPoint!
    override init() {
        super.init()
        lastOffset = CGPoint.zero
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    // 初始化
    override func prepare() {
        super.prepare()
        self.collectionView?.decelerationRate = .fast
    }
    // 这个方法的返回值,决定了 CollectionView 停止滚动时的偏移量
    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        // 分页的 width
        let pageSpace = self.stepSpace()
        let offsetMax: CGFloat = self.collectionView!.contentSize.width - (pageSpace + self.sectionInset.right + self.minimumLineSpacing)
        let offsetMin: CGFloat = 0
        // 修改之前记录的位置,如果小于最小的contentsize或者最大的contentsize则重置值
        if lastOffset.x < offsetMin {
            lastOffset.x = offsetMin
        } else if lastOffset.x > offsetMax {
            lastOffset.x = offsetMax
        }
        // 目标位移点距离当前点距离的绝对值
        let offsetForCurrentPointX: CGFloat = abs(proposedContentOffset.x - lastOffset.x)
        let velocityX = velocity.x
        // 判断当前滑动方向,向左 true, 向右 false
        let direction: Bool = (proposedContentOffset.x - lastOffset.x) > 0
        var newProposedContentOffset: CGPoint = CGPoint.zero
        if (offsetForCurrentPointX > pageSpace/8.0) && (lastOffset.x >= offsetMin) && (lastOffset.x <= offsetMax) {
            if direction {
                newProposedContentOffset.x = ceil((proposedContentOffset.x - self.sectionInset.left) / pageSpace) * pageSpace + self.sectionInset.left
            } else {
                newProposedContentOffset.x = floor((proposedContentOffset.x - self.sectionInset.left) / pageSpace) * pageSpace + self.sectionInset.left
            }
        } else {
            newProposedContentOffset.x = lastOffset.x
        }
        lastOffset = newProposedContentOffset
        return newProposedContentOffset
    }
    func stepSpace() -> CGFloat {
        return self.itemSize.width + self.minimumLineSpacing
    }
}

运行效果如下:

至此,本文结束。按照惯例,文章末尾会提供 demo 工程的地址。


# ios  # red  # bug  # 分页  # 偏移量  # 重写  # 有一个  # 自定义  # 设置为  # 返回值  # 决定了  # 让我们  # 这个问题 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: java获取注册ip实例  Laravel怎么返回JSON格式数据_Laravel API资源Response响应格式化【技巧】  Laravel distinct去重查询_Laravel Eloquent去重方法  如何快速启动建站代理加盟业务?  如何快速生成橙子建站落地页链接?  如何用花生壳三步快速搭建专属网站?  如何用JavaScript实现文本编辑器_光标和选区怎么处理  ,在苏州找工作,上哪个网站比较好?  Python文件操作最佳实践_稳定性说明【指导】  三星、SK海力士获美批准:可向中国出口芯片制造设备  HTML5空格和margin有啥区别_空格与外边距的使用场景【说明】  标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?  Mybatis 中的insertOrUpdate操作  Linux系统命令中tree命令详解  Laravel如何为API编写文档_Laravel API文档生成与维护方法  Laravel如何创建自定义Artisan命令?(代码示例)  EditPlus 正则表达式 实战(3)  如何快速辨别茅台真假?关键步骤解析  悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤  如何挑选高效建站主机与优质域名?  Windows10如何删除恢复分区_Win10 Diskpart命令强制删除分区  Laravel如何实现邮箱地址验证功能_Laravel邮件验证流程与配置  Laravel如何创建自定义中间件?(Middleware代码示例)  微信小程序 require机制详解及实例代码  如何快速登录WAP自助建站平台?  Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】  如何快速查询网址的建站时间与历史轨迹?  详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)  做企业网站制作流程,企业网站制作基本流程有哪些?  Laravel怎么解决跨域问题_Laravel配置CORS跨域访问  大型企业网站制作流程,做网站需要注册公司吗?  WEB开发之注册页面验证码倒计时代码的实现  深圳网站制作的公司有哪些,dido官方网站?  如何用手机制作网站和网页,手机移动端的网站能制作成中英双语的吗?  如何用AI一键生成爆款短视频文案?小红书AI文案写作指令【教程】  JS去除重复并统计数量的实现方法  jQuery 常见小例汇总  Laravel如何实现邮件验证激活账户_Laravel内置MustVerifyEmail接口配置【步骤】  Laravel软删除怎么实现_Laravel Eloquent SoftDeletes功能使用教程  如何快速完成中国万网建站详细流程?  JS实现鼠标移上去显示图片或微信二维码  Laravel怎么发送邮件_Laravel Mail类SMTP配置教程  js代码实现下拉菜单【推荐】  浅谈Javascript中的Label语句  如何在IIS服务器上快速部署高效网站?  WordPress 子目录安装中正确处理脚本路径的完整指南  Laravel Octane如何提升性能_使用Laravel Octane加速你的应用  JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)  历史网站制作软件,华为如何找回被删除的网站?  php 三元运算符实例详细介绍