iOS左對(duì)齊自動(dòng)換行collection樣式

前言

想必大家工作中或多或少會(huì)遇到下圖樣式的UI需求吧


左對(duì)齊樣式UI

像這種cell長(zhǎng)度不固定,以此向右對(duì)齊排列的樣式UI可以說是很常見的

實(shí)現(xiàn)方式

一般的實(shí)現(xiàn)可能主要是分一下兩種:

  • 1、一種是用button依次排列實(shí)現(xiàn),動(dòng)態(tài)計(jì)算text寬度,記錄之前一個(gè)button的位置,和當(dāng)前button的寬度,看是否最終會(huì)超出屏幕的右邊,一旦超出右邊,就換行到下一行
    • 缺點(diǎn)
      • 當(dāng)數(shù)據(jù)量多的時(shí)候,生成很多的button,不能對(duì)button進(jìn)行重用
      • 每次生成一個(gè)button的時(shí)候都要計(jì)算位置,相對(duì)較麻煩
    • 優(yōu)點(diǎn)
      • 適合數(shù)據(jù)少的情況
  • 2、采用collection view,依次從左到右進(jìn)行布局排列cell
    • 優(yōu)點(diǎn)
      • 數(shù)據(jù)量大的時(shí)候,能重用cell,減少cell數(shù)量,增高渲染性能
      • 省去每次cell布局的位置計(jì)算
      • 代碼復(fù)用,實(shí)現(xiàn)一個(gè)UICollectionViewFlowLayout的子類,拿到哪兒都能用
實(shí)現(xiàn)

我們這里實(shí)現(xiàn)主要采用第二種方式,實(shí)現(xiàn)的方式是自定義一個(gè)UICollectionViewFlowLayout的子類,在這個(gè)類里對(duì)cell布局進(jìn)行排列

主要代碼如下:

/// 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 {
        // 如果滾動(dòng)方向是水平的話,就直接返回。這里的水平布局主要適合不換行的那種
        calculatedAttrs[indexPath] = curAttr
        return curAttr
    }

    // 下面主要針對(duì)滾動(dòng)方式是垂直方向的進(jìn)行布局,因?yàn)閷?shí)際開發(fā)中,絕大部分情況也是垂直滾動(dòng)方向
    let sectionInset = calculateSectionInsetForItem(at: indexPath.section)
    let layoutWidth = collectionView?.frame.width ?? 0 - sectionInset.left - sectionInset.right
  
    if indexPath.item == 0 {
        // 如果是當(dāng)前section的第一個(gè)元素,就直接設(shè)置x為sectionInset.left
        curAttr.frame.origin.x = sectionInset.left
        calculatedAttrs[indexPath] = curAttr
        return curAttr
    }
        
    // 計(jì)算非第一個(gè)元素的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
}
  • 這里我對(duì)水平滾動(dòng)方向也進(jìn)行了適配,不過水平滾動(dòng)方向主要適用于不換行的那種從左到右依次排列的樣式,比如如下示例圖:
4f9d619cc922474c998e1bbe8d11370a.jpeg
  • 由于每次重用cell的時(shí)候,會(huì)再次重復(fù)計(jì)算cell的frame,為了減少重復(fù)冗余的計(jì)算,我進(jìn)行了如下的性能優(yōu)化
    • 這是常見的以空間換時(shí)間的解決方式
    • 經(jīng)測(cè)試,這樣子優(yōu)化后,性能提升了將近90%
/// 用字典存儲(chǔ)已經(jīng)計(jì)算過的cell item,常見的以空間換時(shí)間方式
private lazy var calculatedAttrs = [IndexPath: UICollectionViewLayoutAttributes]()
  • 在使用的時(shí)候,只需要將collection.collectionViewLayout的屬性設(shè)置為我們自定義的layout對(duì)象即可,具體代碼如下面的示例代碼:
private lazy var collectionView: UICollectionView = {
  // instance ZLCollectionLeftAlignLayout
  let defaultLayout = ZLCollectionLeftLayout()
  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 ZLCollectionLeftLayout
  let collectionView = UICollectionView(frame: .zero, collectionViewLayout: defaultLayout)
  collectionView.backgroundColor = .magenta
  collectionView.showsVerticalScrollIndicator = false
  return collectionView
}()
  • 之后就是在collection view的代理方法中設(shè)置每個(gè)cell的size就行了,具體示例代碼如下:
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上了,可以直接拿來使用

開源代碼地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容