UICollectionViewCell间距

我们都知道UICollectionViewFlowLayout有一个minimumInteritemSpacing属性可以控制cell之间水平的间距,但是这个属性并不是你设置成多少它的间距一定是多少,从这个单词的字面意思就可以看出来它指的是cell之间的最小间距。也就是说cell的间距是大大于或者等于这个属性的。于是乎,最近就碰到了测试丢过来的问题-UICollectionViewCell之间的布局。

问题重现

关键代码:

let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 8.0
layout.minimumInteritemSpacing = 8.0
layout.sectionInset = UIEdgeInsets(top: 25, left: 15, bottom: 30, right: 15)
let collection = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
collection.backgroundColor = UIColor.white
collection.delegate = self
collection.dataSource = self
collection.register(UINib(nibName: "CollectionCell", bundle: Bundle.main),forCellWithReuseIdentifier: "CollectionCell")
self.view.addSubview(collection)

以上关键代码是layout.minimumInteritemSpacing = 8.0,把cell间距设置成8之后,本以为就可以高枕无忧了,但是载入数据之后数据效果如下图:

如图中红框内的间距明显不是我们想要。

明确需求

现在想要达到的效果是,cell全部向左对齐,间距一定要控制在8.0,不够就换一行显示。

如何解决

要达到这样的效果就只能是修改布局了。先来看一下布局的实现:

//
//  DVMaximumSpacingLayout.swift
//  Amall
//  一个可以设置cell之间最大间距的布局,用于商品详情的属性
//  Created by David Yu on 2018/4/26.
//  Copyright © 2018年 David. All rights reserved.
//

import UIKit

// MARK:-   DVMaximumSpacingLayout代理
@objc protocol DVMaximumSpacingLayoutDelegate {
    
    func collectionView(_ collectionView: UICollectionView?, layout collectionViewLayout: DVMaximumSpacingLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
    @objc optional func collectionView(_ collectionView: UICollectionView?, layout collectionViewLayout: DVMaximumSpacingLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
    @objc optional func collectionView(_ collectionView: UICollectionView?, layout collectionViewLayout: DVMaximumSpacingLayout, referenceSizeForFooterInSection section: Int) -> CGSize
    
}

class DVMaximumSpacingLayout: UICollectionViewLayout {

    /// cell最大水平间距
    var MaximumSpacing: CGFloat = 0.0
    /// cell竖直间距
    var minimumLineSpacingForSection: CGFloat = 0.0
    /// 间距
    var sectionEdgeInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    /// cell布局
    var cellAttributes = [IndexPath: UICollectionViewLayoutAttributes]()
    /// 头视图布局
    var headerAttributes = [IndexPath: UICollectionViewLayoutAttributes]()
    /// 尾视图布局
    var footerAttributes = [IndexPath: UICollectionViewLayoutAttributes]()
    /// 代理
    var delegate: DVMaximumSpacingLayoutDelegate?
    /// 当前Y坐标
    var currentY : CGFloat = 0
    
    // MARK:- prepareLayout是一个必须要实现的方法,该方法的功能是为布局提供一些必要的初始化参数
    override func prepare() {
        super.prepare()
        cellAttributes.removeAll()
        headerAttributes.removeAll()
        footerAttributes.removeAll()
        currentY = 0
        
        //  一共有多少section
        let sectionNum = self.collectionView?.numberOfSections ?? 0
        for i in 0..<sectionNum {
            let supplementaryViewIndex = IndexPath(row: 0, section: i)
            //  计算设置每个header的布局对象
            let headerAttribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: supplementaryViewIndex)
            let headerSize = delegate?.collectionView?(collectionView, layout: self, referenceSizeForHeaderInSection: i) ?? CGSize(width: 0, height: 0)
            headerAttribute.frame = CGRect(origin: CGPoint(x: 0, y: currentY), size: headerSize)
            headerAttributes[supplementaryViewIndex] = headerAttribute
            currentY = headerAttribute.frame.maxY + sectionEdgeInsets.top
            
            //  计算设置每个cell的布局对象
            //  该section一共有多少row
            let rowNum = self.collectionView?.numberOfItems(inSection: i) ?? 0
            var currentX = sectionEdgeInsets.left
            for j in 0..<rowNum {
                let cellIndex = IndexPath(row: j, section: i)
                let cellAttribute = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
                let cellSize = delegate?.collectionView(collectionView, layout: self, sizeForItemAt: cellIndex) ?? CGSize(width: 0, height: 0)
                if currentX + cellSize.width + sectionEdgeInsets.right > collectionView?.frame.width ?? 0 {
                    //  超过collectview换行,并且collectionview的高度增加
                    currentX = sectionEdgeInsets.left
                    currentY = currentY + cellSize.height + minimumLineSpacingForSection
                }
                cellAttribute.frame = CGRect(origin: CGPoint(x: currentX, y: currentY), size: cellSize)
                currentX = currentX + cellSize.width + MaximumSpacing
                cellAttributes[cellIndex] = cellAttribute
                
                if j == rowNum - 1 {
                    currentY = currentY + cellSize.height + sectionEdgeInsets.bottom
                }
            }
            
            //  计算每个footer的布局对象
            let footerAttribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, with: supplementaryViewIndex)
            let footerSize = delegate?.collectionView?(collectionView, layout: self, referenceSizeForFooterInSection: i) ?? CGSize(width: 0, height: 0)
            footerAttribute.frame = CGRect(origin: CGPoint(x: 0, y: currentY), size: footerSize)
            footerAttributes[supplementaryViewIndex] = footerAttribute
        }
        
    }
    
    // MARK:- 当前屏幕可见的cell、header、footer的布局
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var attributes = [UICollectionViewLayoutAttributes]()
        //  添加当前屏幕可见的cell的布局
        for element in cellAttributes.values {
            if rect.contains(element.frame) {
                attributes.append(element)
            }
        }
        //  添加当前屏幕可见的头视图的布局
        for element in headerAttributes.values {
            if rect.contains(element.frame) {
                attributes.append(element)
            }
        }
        //  添加当前屏幕可见的尾部的布局
        for element in footerAttributes.values {
            if rect.contains(element.frame) {
                attributes.append(element)
            }
        }
        return attributes
    }
    
    // MARK:- 该方法是为每个Cell返回一个对应的Attributes,我们需要在该Attributes中设置对应的属性,如Frame等
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cellAttributes[indexPath]
    }
    
    // MARK:- 该方法是为每个头和尾返回一个对应的Attributes,我们需要在该Attributes中设置对应的属性,如Frame等
    override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        var attr: UICollectionViewLayoutAttributes?
        if elementKind == UICollectionElementKindSectionHeader {
            attr = headerAttributes[indexPath]
        } else {
            attr = footerAttributes[indexPath]
        }
        return attr
    }
    
    // MARK:- 设置滚动范围
    override var collectionViewContentSize: CGSize {
        let width = collectionView?.frame.width ?? 0
        return CGSize(width: width, height: currentY)
    }
  
}

然后使用几乎和原先差不多,只是布局的代理方法需要更换成自定义布局的代理方法

//
//  ViewController.swift
//  UICollectionLayout
//
//  Created by David Yu on 2018/4/27.
//  Copyright © 2018年 David Yu. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    let titles = ["测试测试测试测试","测试测试测试测试","试测试试测试试测试试测试","试测试试测试试测试试测试试测试试测试","试测试试测试试测试试测试","试测试试测试试测试试测试试测试","试测试试测试试测试试测试试测试试测试","试测试试测试试测试试测试","试测试试测试试测试","试测试试测试试测试试测试试测试","试测试试测试试测试试测试","试测试试测试试测试","试测试","试测试试测试试测试试测试试测试试测试试测试","试测试试测试试测试试测试试测试","试测试试测试试测试"]
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        setView()
    }
    
    func setView() {
        self.view.backgroundColor = UIColor.white
        let layout = DVMaximumSpacingLayout()
        layout.MaximumSpacing = 12.0
        layout.minimumLineSpacingForSection = 8.0
        layout.sectionEdgeInsets = UIEdgeInsets(top: 12, left: 15, bottom: 12, right: 15)
        layout.delegate = self
        let collection = UICollectionView(frame: CGRect(x: 0, y: 64, width: self.view.frame.width, height: self.view.frame.height-64), collectionViewLayout: layout)
        collection.backgroundColor = UIColor.white
        collection.delegate = self
        collection.dataSource = self
        collection.register(UINib(nibName: "CollectionCell", bundle: Bundle.main), forCellWithReuseIdentifier: "CollectionCell")
        self.view.addSubview(collection)
    }

}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, DVMaximumSpacingLayoutDelegate {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return titles.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
        cell.title = titles[indexPath.row]
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView?, layout collectionViewLayout: DVMaximumSpacingLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let title = titles[indexPath.row]
        let size = title.getSize(UIFont.systemFont(ofSize: 17), size: CGSize(width: UIScreen.main.bounds.width, height: 26))
        return CGSize(width: size.width + 20, height: 26)
    }
    
}

来看看运行效果图:



确实是达到了想要的效果,希望可以帮助到有同样需求的同学。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容