本文作者:99ANYc3cd6

ios小红书的瀑布流

99ANYc3cd6 01-05 13
ios小红书的瀑布流摘要: 小红书的瀑布流是其最具代表性的 UI 特征之一,它不仅仅是一种布局方式,更是其产品体验的核心,下面我将从 设计理念、技术实现、性能优化和用户体验 四个方面来详细解析,设计理念与用户...

小红书的瀑布流是其最具代表性的 UI 特征之一,它不仅仅是一种布局方式,更是其产品体验的核心,下面我将从 设计理念、技术实现、性能优化和用户体验 四个方面来详细解析。

ios小红书的瀑布流
(图片来源网络,侵删)

设计理念与用户体验

在谈论技术之前,首先要理解小红书为什么要用瀑布流。

  • 最大化信息密度: 在有限的屏幕空间内,瀑布流可以展示比传统的网格布局(如 Instagram)更多的内容,它利用了屏幕高度,让用户可以快速浏览大量帖子。
  • 视觉吸引力: 每个卡片的高度不一,错落有致,打破了规整布局的单调感,更像一本杂志或灵感墙,具有很强的视觉冲击力,能激发用户的浏览兴趣。
  • “发现”式体验: 瀑布流非常适合展示图文混排的内容,用户在上下滑动时,会被不同尺寸、不同内容的图片和标题所吸引,这种“所见即所得”的探索感,非常符合小红书“标记我的生活”和“发现美好生活”的定位。
  • 内容驱动: 布局完全服务于内容,一张高质量的竖版图片会占据更大的空间,获得更多展示;而一张横版图片或包含大量文字的笔记则会自动调整高度,确保所有信息都能被完整看到。

技术实现方案

在 iOS 上实现瀑布流,主要有三种主流方案,各有优劣。

使用 UICollectionViewUICollectionViewFlowLayout(最推荐)

这是苹果官方提供的、最标准、最强大的实现方式,也是绝大多数 App(包括小红书)的首选。

核心原理: UICollectionView 通过其数据源方法 collectionView(_:layout:sizeForItemAt:) 来动态计算每个 UICollectionViewCell 的大小。

ios小红书的瀑布流
(图片来源网络,侵删)

实现步骤:

  1. 创建 UICollectionViewUICollectionViewFlowLayout

    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: CustomFlowLayout())
    collectionView.register(NoteCell.self, forCellWithReuseIdentifier: "NoteCell")
  2. 自定义 UICollectionViewFlowLayout 这是实现瀑布流的关键,你需要重写 UICollectionViewFlowLayout 的几个核心方法。

    class CustomFlowLayout: UICollectionViewFlowLayout {
        // 定义列数,比如2列
        private let numberOfColumns = 2
        // 重写此方法来计算每个 item 的尺寸
        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            // 先调用父类方法获取默认属性
            let attributes = super.layoutAttributesForElements(in: rect)
            guard attributes != nil else { return nil }
            // 创建一个数组来记录每一列的当前 Y 坐标(累加高度)
            var columnHeights = [CGFloat](repeating: 0, count: numberOfColumns)
            // 遍历所有属性
            for attributes in attributes! {
                if attributes.representedElementCategory == .cell {
                    // 找出当前高度最小的那一列
                    let minHeight = columnHeights.min()!
                    let columnIndex = columnHeights.firstIndex(of: minHeight)!
                    // 计算 item 的 x, y 坐标
                    let x = CGFloat(columnIndex) * (itemSize.width + minimumInteritemSpacing)
                    let y = minHeight + minimumLineSpacing
                    // 更新布局属性
                    attributes.frame = CGRect(x: x, y: y, width: itemSize.width, height: 0) // 高度先设为0,下面会计算
                    // 更新该列的高度
                    columnHeights[columnIndex] = attributes.frame.maxY
                }
            }
            return attributes
        }
        // 重写此方法来返回 content size
        override var collectionViewContentSize: CGSize {
            // 找出所有列中最高的那一列的高度
            let maxHeight = columnHeights.max() ?? 0
            return CGSize(width: collectionView!.bounds.width, height: maxHeight)
        }
    }
  3. UICollectionViewDataSource 中提供数据:cellForItemAt 中,根据数据模型配置 Cell 的内容,在 sizeForItemAt 中返回一个估算的宽度,高度设为 0 或一个估算值。

    ios小红书的瀑布流
    (图片来源网络,侵删)
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NoteCell", for: indexPath) as! NoteCell
        let note = notes[indexPath.item]
        cell.configure(with: note) // 配置 cell 的图片和文字
        return cell
    }
    // 关键:返回 Cell 的估算尺寸
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let note = notes[indexPath.item]
        let width = (collectionView.bounds.width - (numberOfColumns - 1) * spacing) / numberOfColumns
        // 这里可以做一个简单的估算,比如根据图片的宽高比计算
        // 或者直接返回一个固定宽度,高度在 layoutAttributesForElements 中动态计算
        // 更常见的做法是:在 sizeForItemAt 中只返回宽度,高度为0,然后在 CustomFlowLayout 的 prepare() 或 layoutAttributesForElements 中根据内容计算高度。
        // 为了性能,通常在 sizeForItemAt 中计算一个近似高度
        let estimatedHeight = calculateHeight(for: note, width: width)
        return CGSize(width: width, height: estimatedHeight)
    }

优点:

  • 性能优异: UICollectionView 是为高性能滚动而生的原生组件。
  • 功能强大: 支持复用、动画、多选、分区等复杂功能。
  • 官方支持: 稳定可靠,有完善的文档和社区支持。

缺点:

  • 实现复杂: 需要理解 UICollectionViewLayout 的工作原理,实现瀑布流逻辑相对繁琐。

使用第三方库(如 IGListKit

IGListKit 是 Instagram 开源的一个数据驱动、模块化的 UI 框架,它内部也基于 UICollectionView,但提供了更高级的抽象。

实现原理: 它将数据源和 UI 分离,通过 ListAdapterSectionController 来管理,每个瀑布流都可以看作一个 SectionController

优点:

  • 架构清晰: 数据和 UI 解耦,代码结构更清晰,适合大型复杂项目。
  • 高性能: 同样基于 UICollectionView,性能有保障。
  • 易于扩展: 添加新的数据类型或布局非常方便。

缺点:

  • 学习成本: 需要学习 IGListKit 自己的一套架构和数据绑定方式。
  • 增加依赖: 引入第三方库会增加项目的体积和维护成本。

使用 UITableView(不推荐,但可行)

早期一些应用会使用 UITableView 来模拟瀑布流。

实现原理:

  • 单列瀑布流: 每一行就是一个 Cell,高度根据内容动态计算。
  • 多列瀑布流: 创建多个 UITableView 并排,每个 UITableView 负责一列,通过代理方法 tableView(_:heightForRowAt:) 计算每个 Cell 的高度,并同步滚动。

优点:

  • 实现简单,特别是对于单列情况。

缺点:

  • 性能差: 无法像 UICollectionView 那样高效地复用 Cell,内存占用高,滚动流畅度远不如 UICollectionView
  • 功能受限: 实现多列同步滚动、动画等非常复杂。
  • 架构混乱: 违背了 UITableView 的设计初衷。

除非是极其简单的场景,否则在现代 iOS 开发中,应避免使用 UITableView 实现瀑布流。


性能优化(关键)

小红书的瀑布流有成千上万条内容,性能优化至关重要。

  1. 图片加载与缓存:

    • 异步加载: 必须在后台线程(如 GCDOperationQueue)下载图片。
    • 内存缓存: 使用 NSCache 或第三方库(如 Kingfisher, SDWebImage)的内存缓存,避免重复下载。
    • 磁盘缓存: 将图片缓存到本地,避免重复网络请求,加快二次加载速度。
    • 图片解码: 避免主线程进行图片解码,防止卡顿。SDWebImage 等库已经处理了这一点。
  2. Cell 高度计算:

    • 避免主线程计算: Cell 的高度计算(特别是图文混排的高度)是比较耗时的操作,应该在后台线程(如 GlobalQueue)中计算好高度,再更新到主线程。
    • 估算高度 vs. 精确高度:
      • 估算高度:sizeForItemAt 中返回一个估算值(如 200pt),可以让 UICollectionView 快速计算出初始布局,实现“所见即所得”的流畅滚动,这是 iOS 10+ 的 self-sizing cell 的优势。
      • 精确高度: 当 Cell 实际渲染出来后,再通过代理方法 collectionView(_:layout:didEndDisplaying:forItemAt:) 或 KVO 获取到精确高度,并更新数据源,这样下次滚动回来时,就能显示精确的尺寸。
    • 缓存高度: 将计算好的 Cell 高度缓存起来(例如用 NSCache 或一个字典),避免重复计算。
  3. 数据加载策略:

    • 分页加载: 每次只请求和渲染一屏或两屏的数据,滚动到底部时再加载下一页。
    • 预加载: 在用户即将滚动到底部之前,提前发起下一页数据的网络请求,无缝衔接,提升用户体验。
  4. 复用与布局优化:

    • 确保 Cell 复用: UICollectionView 的复用机制是其高性能的核心,要确保 cellForItemAt 逻辑正确。
    • 避免在 layoutSubviews 中做复杂计算: layoutSubviews 会被频繁调用,里面的逻辑要尽量轻量。

小红书瀑布流的细节

小红书的瀑布流之所以体验好,除了上述技术点,还有很多细节打磨:

  • 动态宽度与固定列数: 通常采用固定的列数(如2列或3列,根据屏幕宽度自适应),Cell 的宽度是动态计算的,高度则根据内容自适应。
  • 圆角与阴影: 每个 Cell 都有圆角和轻微的阴影,增加了卡片的立体感和精致感。
  • 渐进式加载: 图片会先显示一个低分辨率的占位图,然后清晰度逐渐提升,而不是等待全部加载完成。
  • 吸顶/悬浮效果: 当用户滚动时,顶部的搜索栏或分类栏可能会固定在屏幕顶部,方便用户随时操作,这可以通过 UIScrollViewcontentOffsetUIVisualEffectView 来实现。
  • 无限滚动: 用户滚动到底部时,会自动加载更多内容,形成一种“刷不完”的感觉。
特性/方案 UICollectionViewFlowLayout (推荐) UITableView (不推荐) 第三方库
性能 极高 较低
实现复杂度 中等 简单 中等 (需学习新框架)
功能丰富度 非常丰富 有限 非常丰富
架构灵活性 一般 极高
适用场景 绝大多数瀑布流场景 简单单列列表 大型、复杂、模块化项目

对于 iOS 掌握使用 UICollectionView 和自定义 UICollectionViewFlowLayout 来实现瀑布流是一项必备的核心技能,它不仅能让你的应用拥有类似小红书那样优秀的视觉效果,更能保证其在海量数据下的流畅性能。

文章版权及转载声明

作者:99ANYc3cd6本文地址:https://www.chumoping.net/post/7077.html发布于 01-05
文章转载或复制请以超链接形式并注明出处初梦运营网

阅读
分享