CollectionViewCell 自動(dòng)高度與自動(dòng)布局

CollectionViewCell 自動(dòng)高度

寫在前面:
UITableViewCell的自動(dòng)高度很方便往果,開發(fā)中很多時(shí)候首選tableView的原因也是因?yàn)檫@個(gè)钾埂,可以減少很多高度或者動(dòng)態(tài)高度的計(jì)算過程兢孝。
CollectionViewCell 其實(shí)也是可以自動(dòng)高度的, 需要重寫實(shí)現(xiàn)一個(gè)方法func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes

1. CollectionViewCell實(shí)現(xiàn)自動(dòng)高度

CollctionViewCell 要實(shí)現(xiàn)自動(dòng)高度,除了UI控件在布局上要像在TableViewCell一樣做到用約束撐開contentView之外髓堪,還要實(shí)現(xiàn)func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes方法屯仗, 實(shí)現(xiàn)這個(gè)方法的目的其實(shí)也是通過手動(dòng)計(jì)算的方式來返回真實(shí)的高度搞坝。

比如:假如collectionViewCell中有一個(gè)textView(或者label什么的), 現(xiàn)在需求是這個(gè)cell的高度要隨著textView中的自動(dòng)換行而增大,那么在這個(gè)方法中可以這樣計(jì)算:

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        setNeedsLayout()
        layoutIfNeeded()
        let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        var newFrame = layoutAttributes.frame
        newFrame.size.height = (textView.text?.isSpaceOrEmpty ?? true) ? 0 : ceil(size.height)
        layoutAttributes.frame = newFrame
        return layoutAttributes
}

這樣在textView字?jǐn)?shù)發(fā)生變化時(shí)就會(huì)調(diào)用這個(gè)方法魁袜,實(shí)時(shí)計(jì)算實(shí)際高度并重新布局了桩撮。

2. IGListKit中的CollectionViewCell的高度計(jì)算

在IGListKit中實(shí)現(xiàn)sectionController時(shí),必要實(shí)現(xiàn)sizeForItem這個(gè)方法峰弹,在這個(gè)方法里對(duì)每種cell手動(dòng)計(jì)算它的實(shí)際高度并返回店量,有時(shí)候也要配合上面自動(dòng)布局需要實(shí)現(xiàn)的方法共同計(jì)算并返回真實(shí)高度。

比如鞠呈,CollectionViewCell中有一個(gè)支持多行的label, 在sectionController的sizeForItem 中首先要手動(dòng)計(jì)算這些字所占高度融师,再加上label上下間隙高度。

計(jì)算字所占高度通骋狭撸可通知?jiǎng)?chuàng)建一個(gè)模擬label的實(shí)際計(jì)算旱爆,注意這個(gè)模擬label的字體以及樣式要和真實(shí)的label保持一致。

static func height(text: String,
                       width: CGFloat,
                       topPadding: CGFloat = 20,
                       bottomPadding: CGFloat = 20) -> CGFloat {

        let fakeLabel = XOLabel(frame: CGRect(x: 0,
                                              y: 0,
                                              width: width - 40,
                                              height: .greatestFiniteMagnitude))
        fakeLabel.numberOfLines = 0
        fakeLabel.font = .caption1
        fakeLabel.text = text
        fakeLabel.sizeToFit()

        return fakeLabel.bounds.size.height + topPadding + bottomPadding
}

注意: 因?yàn)檫@個(gè)cell中的實(shí)際label.numbersOfLines = 0窘茁,需要實(shí)現(xiàn)自動(dòng)高度怀伦,所以依然要實(shí)現(xiàn)func preferredLayoutAttributesFitting方法才能實(shí)現(xiàn)完全撐開cell。

算完之后在sizeForItem中就可以為這個(gè)cell返回實(shí)際高度了山林。

3. CollectionView的自動(dòng)高度

有時(shí)候需要實(shí)現(xiàn)在tableViewCell中嵌入collectionView, 雖然tableViewCell可以實(shí)現(xiàn)自動(dòng)高度房待,但因?yàn)閏ollectionView是可以滾動(dòng)的,雖然可以像textView一樣禁掉滾動(dòng)屬性驼抹,但畢竟是collectionView, 有時(shí)候加載的東西比較多桑孩,實(shí)現(xiàn)不了像textView一樣實(shí)時(shí)更新高度。
除非CollectionViewCell中的item大小是可以確定的砂蔽,這種情況下可以實(shí)現(xiàn)整個(gè)tableViewCell的自動(dòng)高度洼怔。

如果collectionViewCell的item大小不確定署惯,有什么辦法實(shí)現(xiàn)tableViewCell的自動(dòng)高度呢左驾?
網(wǎng)上查到的方法是大概是這樣說的:在TabelViewCell的layoutSubviews重新方法中加入以下代碼

self.contentView.frame = bounds  // 先為contentView設(shè)置frame,使得子視圖有布局依據(jù),使得contentView不會(huì)被忽略而被線程渲染。
self.collectionView.reloadData()
let contentSize = collectionView.contentSize
self.frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)

這樣看起來挺合理的极谊,但實(shí)現(xiàn)運(yùn)行起來出問題了诡右,高度不對(duì),還出現(xiàn)死循環(huán)轻猖》牵可能是不適用于我當(dāng)時(shí)的demo。

tableViewCell中嵌入collectionView

所幸我需要實(shí)現(xiàn)的collectionView的item比較單一咙边,這樣可以實(shí)際去計(jì)算實(shí)際高度猜煮,于是將所有間隙找出次员,一個(gè)一個(gè)item計(jì)算其寬度,當(dāng)前行放不下了就換行+高:

private func heightForCell(withFilters filters: [String]) -> CGFloat {
        var widthRemaining = LayoutParameter.collectionWidth
        var height: CGFloat = LayoutParameter.collectionViewTopPadding
        let fakeLabel = XOLabel()
        fakeLabel.font = .body

        filters.enumerated().forEach { (index, filter) in
            fakeLabel.text = filter
            if index == 0 {
                let firstLineHeightOfItems = LayoutParameter.itemHeight
                height += firstLineHeightOfItems
            }
            fakeLabel.sizeToFit()
            let itemWidth = LayoutParameter.labelLeftPad + fakeLabel.width() + LayoutParameter.labelRightPad

            //當(dāng)當(dāng)前行不足以再放多一個(gè)item時(shí)換行王带,+高度
            if widthRemaining - itemWidth - LayoutParameter.itemSpace <= 0 {
                height += (LayoutParameter.verticalSpace + LayoutParameter.itemHeight)
                widthRemaining = LayoutParameter.collectionWidth
                // 換行結(jié)束將行寬還原
            }
            widthRemaining -= (LayoutParameter.itemSpace + itemWidth)
        }
        return height
}

這樣就可以計(jì)算出整個(gè)collectionView的高度了淑蔚,只要間隙的值準(zhǔn)備,計(jì)算的結(jié)果幾乎是正確的愕撰。

4. collectionView 自定義布局

collectionView 橫向布局刹衫、縱向布局、 items 左右對(duì)齊搞挣、上下對(duì)齊

1. items 左對(duì)齊

collectionView items 默認(rèn)是分散對(duì)齊的带迟,如果需要實(shí)現(xiàn)類似以下的左對(duì)齊效果需要自定義布局。


items 左對(duì)齊

左對(duì)齊的實(shí)現(xiàn)方式比較簡(jiǎn)單囱桨,直接重寫UICollectionViewFlowLayout下的layoutAttributesForElements( )

public final class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
    override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)

        var leftMargin = sectionInset.left
        var maxY: CGFloat = -1.0  //設(shè)置負(fù)值仓犬,保證第一次滿足條件
        attributes?.forEach { layoutAttribute in    //item
            if layoutAttribute.frame.origin.y >= maxY {  //如果還沒換行則不滿足條件,換行了才滿足條件使得leftMargin回到0
                leftMargin = sectionInset.left
            }

            layoutAttribute.frame.origin.x = leftMargin

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing  //左對(duì)齊
            maxY = max(layoutAttribute.frame.maxY, maxY)
        }
        return attributes
    }
}
item.y
//然后在collectionView的layout中可能這樣使用
private let layout: LeftAlignedCollectionViewFlowLayout = {
        let layout = LeftAlignedCollectionViewFlowLayout()
        layout.scrollDirection = .vertical
        return layout
}( )

collectionView.collectionViewLayout = layout
//collectionView.isScrollEnabled = false
2. 右對(duì)齊

右對(duì)齊類似蝇摸,只是計(jì)算中有點(diǎn)點(diǎn)不一樣

public final class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
    override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)

        var rightMargin = rect.maxX
        var maxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in
            if layoutAttribute.frame.origin.y >= maxY {
                rightMargin = rect.maxX - 20
            }

            layoutAttribute.frame.origin.x = rightMargin - layoutAttribute.frame.width

            rightMargin -= layoutAttribute.frame.width + minimumInteritemSpacing
            maxY = max(layoutAttribute.frame.maxY, maxY)
        }
        return attributes
    }
}
右對(duì)齊
3. 底部對(duì)齊

在做一些聊天頁面時(shí)婶肩,當(dāng)剛開始建立對(duì)話,只有一兩個(gè)cell時(shí)貌夕,希望像Slack 軟件一樣做到底部對(duì)齊律歼,可以這樣做。

 public final class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
    override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)

        if let items = attributes,
            !items.isEmpty,
            let lastItem = items.reversed().first,
            let collectionView = collectionView,
            lastItem.frame.maxY < collectionView.bounds.maxY {
            let bottomPadding = collectionView.bounds.maxY - lastItem.frame.maxY
            items.forEach {
                $0.frame.origin.y += bottomPadding
            }
            return items
        } else {
            return attributes
        }
    }
}
底部對(duì)齊
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末啡专,一起剝皮案震驚了整個(gè)濱河市险毁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌们童,老刑警劉巖畔况,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異慧库,居然都是意外死亡跷跪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門齐板,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吵瞻,“玉大人,你說我怎么就攤上這事甘磨∠鹦撸” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵济舆,是天一觀的道長(zhǎng)卿泽。 經(jīng)常有香客問我,道長(zhǎng)滋觉,這世上最難降的妖魔是什么签夭? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任齐邦,我火速辦了婚禮,結(jié)果婚禮上第租,老公的妹妹穿的比我還像新娘侄旬。我一直安慰自己,他們只是感情好煌妈,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布儡羔。 她就那樣靜靜地躺著,像睡著了一般璧诵。 火紅的嫁衣襯著肌膚如雪汰蜘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天之宿,我揣著相機(jī)與錄音族操,去河邊找鬼。 笑死比被,一個(gè)胖子當(dāng)著我的面吹牛色难,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播等缀,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼枷莉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了尺迂?” 一聲冷哼從身側(cè)響起笤妙,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎噪裕,沒想到半個(gè)月后蹲盘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡膳音,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年召衔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祭陷。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苍凛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颗胡,到底是詐尸還是另有隱情毫深,我是刑警寧澤吩坝,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布毒姨,位于F島的核電站,受9級(jí)特大地震影響钉寝,放射性物質(zhì)發(fā)生泄漏弧呐。R本人自食惡果不足惜闸迷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望俘枫。 院中可真熱鬧腥沽,春花似錦、人聲如沸鸠蚪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茅信。三九已至盾舌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蘸鲸,已是汗流浹背妖谴。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酌摇,地道東北人膝舅。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像窑多,于是被迫代替她去往敵國(guó)和親仍稀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361