iOS左对齐自动换行collection样式

前言

想必大家工作中或多或少会遇到下图样式的UI需求吧

像这种cell长度不固定,以此向右对齐排列的样式UI可以说是很常见的

实现方式

一般的实现可能主要是分一下两种:

  • 1、一种是用button依次排列实现,动态计算text宽度,记录之前一个button的位置,和当前button的宽度,看是否最终会超出屏幕的右边,一旦超出右边,就换行到下一行
    • 缺点
      • 当数据量多的时候,生成很多的button,不能对button进行重用
      • 每次生成一个button的时候都要计算位置,相对较麻烦
    • 优点
      • 适合数据少的情况
  • 2、采用collection view,依次从左到右进行布局排列cell
    • 优点
      • 数据量大的时候,能重用cell,减少cell数量,增高渲染性能
      • 省去每次cell布局的位置计算
      • 代码复用,实现一个UICollectionViewFlowLayout的子类,拿到哪儿都能用
实现

我们这里实现主要采用第二种方式,实现的方式是自定义一个UICollectionViewFlowLayout的子类,在这个类里对cell布局进行排列

主要代码如下:

swift 复制代码
/// main method for layout cell
/// - Parameter indexPath: indexpath
/// - Returns: layouted UICollectionViewLayoutAttributes
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    if let attr = calculatedAttrs[indexPath] { return attr }

    guard let curAttr = super.layoutAttributesForItem(at: indexPath) else { return nil }

    if isHorizontal {
      	// 如果滚动方向是水平的话,就直接返回。这里的水平布局主要适合不换行的那种
        calculatedAttrs[indexPath] = curAttr
        return curAttr
    }

  	// 下面主要针对滚动方式是垂直方向的进行布局,因为实际开发中,绝大部分情况也是垂直滚动方向
    let sectionInset = calculateSectionInsetForItem(at: indexPath.section)
    let layoutWidth = collectionView?.frame.width ?? 0 - sectionInset.left - sectionInset.right
  
    if indexPath.item == 0 {
      	// 如果是当前section的第一个元素,就直接设置x为sectionInset.left
        curAttr.frame.origin.x = sectionInset.left
        calculatedAttrs[indexPath] = curAttr
        return curAttr
    }
		
  	// 计算非第一个元素的frame布局
    let prevIP = IndexPath(item: indexPath.item - 1, section: indexPath.section)
    let prevRect = layoutAttributesForItem(at: prevIP)?.frame ?? .zero
    let prevRectRightPoint = prevRect.origin.x + prevRect.size.width
    let stretchedCurRect = CGRect(x: sectionInset.left,
                                  y: curAttr.frame.origin.y,
                                  width: layoutWidth,
                                  height: curAttr.frame.size.height)

    if !prevRect.intersects(stretchedCurRect) {
        curAttr.frame.origin.x = sectionInset.left
        calculatedAttrs[indexPath] = curAttr
        return curAttr
    }

    curAttr.frame.origin.x = prevRectRightPoint + calculateMinimumInteritemSpacingForSection(at: indexPath.section)
    calculatedAttrs[indexPath] = curAttr
    return curAttr
}
  • 这里我对水平滚动方向也进行了适配,不过水平滚动方向主要适用于不换行的那种从左到右依次排列的样式,比如如下示例图:
  • 由于每次重用cell的时候,会再次重复计算cell的frame,为了减少重复冗余的计算,我进行了如下的性能优化
    • 这是常见的以空间换时间的解决方式
    • 经测试,这样子优化后,性能提升了将近90%
swift 复制代码
/// 用字典存储已经计算过的cell item,常见的以空间换时间方式
private lazy var calculatedAttrs = [IndexPath: UICollectionViewLayoutAttributes]()
  • 在使用的时候,只需要将collection.collectionViewLayout的属性设置为我们自定义的layout对象即可,具体代码如下面的示例代码:
swift 复制代码
private lazy var collectionView: UICollectionView = {
  // instance ZLCollectionLeftAlignLayout
  let defaultLayout = ZLCollectionLeftAlignLayout()
  defaultLayout.minimumLineSpacing = 10.0
  defaultLayout.minimumInteritemSpacing = 10.0
  defaultLayout.scrollDirection = .vertical
  defaultLayout.sectionInset = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 20.0, right: 10.0)
  // set collectionViewLayout to a instance of ZLCollectionLeftAlignLayout
  let collectionView = UICollectionView(frame: .zero, collectionViewLayout: defaultLayout)
  collectionView.backgroundColor = .magenta
  collectionView.showsVerticalScrollIndicator = false
  return collectionView
}()
  • 之后就是在collection view的代理方法中设置每个cell的size就行了,具体示例代码如下:
swift 复制代码
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 
  let w = CGFloat.random(in: 20.0 ... 50.0)
  return CGSize(width: 30.0 + w, height: 25.0)
}
开源代码地址

我已经将代码开源到github上了,可以直接拿来使用

开源代码地址

相关推荐
与火星的孩子对话8 小时前
Unity进阶课程【六】Android、ios、Pad 终端设备打包局域网IP调试、USB调试、性能检测、控制台打印日志等、C#
android·unity·ios·c#·ip
恋猫de小郭1 天前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
点金石游戏出海2 天前
每周资讯 | Krafton斥资750亿日元收购日本动画公司ADK;《崩坏:星穹铁道》新版本首日登顶iOS畅销榜
游戏·ios·业界资讯·apple·崩坏星穹铁道
旷世奇才李先生2 天前
Swift 安装使用教程
开发语言·ios·swift
90后的晨仔2 天前
Xcode16报错: SDK does not contain 'libarclite' at the path '/Applicati
ios
finger244802 天前
谈一谈iOS线程管理
ios·objective-c
Digitally2 天前
如何将大型视频文件从 iPhone 传输到 PC
ios·iphone
梅名智2 天前
IOS 蓝牙连接
macos·ios·cocoa
美狐美颜sdk2 天前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭2 天前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin