【iOS】UICollectionView补充

学习文章

UICollectionView补充

一、 UICollectionView

标准的UICollectionView包含三个部分,它们都是UIView的子类:

  • Cells 用于展示内容的主体,对于不同的cell可以指定不同尺寸和不同的内容
  • Supplementary Views 追加视图 如果你对UITableView比较熟悉的话,可以理解为每个Section的Header或者Footer,用来标记每个section的view
  • Decoration Views 装饰视图 这是每个section的背景
CollectionView.jpg
Cells.jpg
SupplementaryViews.jpg
DecorationViews.jpg
二、 UICollectionViewDataSource
  • section的数量 -numberOfSectionsInCollection:
  • 某个section里有多少个item -collectionView:numberOfItemsInSection:
  • 对于某个位置应该显示什么样的cell -collectionView:cellForItemAtIndexPath:

实现以上三个委托方法,基本上就可以保证CollectionView工作正常了。当然,还有提供Supplementary View的方法

  • collectionView:viewForSupplementaryElementOfKind:atIndexPath:

对于Decoration Views,提供方法并不在UICollectionViewDataSource中,而是直接UICollectionViewLayout类中的(因为它仅仅是视图相关,而与数据无关).

三、 UICollectionViewDelegate

数据无关的view的外形啊,用户交互啊什么的,由UICollectionViewDelegate来负责:

  • cell的高亮
  • cell的选中状态
  • 可以支持长按后的菜单

每个cell有独立的高亮事件和选中事件的delegate,用户点击cell的时候,现在会按照以下流程向delegate进行询问:

  1. -collectionView:shouldHighlightItemAtIndexPath: 是否应该高亮?

  2. -collectionView:didHighlightItemAtIndexPath: 如果1回答为是,那么高亮

  3. -collectionView:shouldSelectItemAtIndexPath: 无论1结果如何,都询问是否可以被选中?

  4. -collectionView:didUnhighlightItemAtIndexPath: 如果1回答为是,那么现在取消高亮

  5. -collectionView:didSelectItemAtIndexPath: 如果3回答为是,那么选中cell

四、 关于Cell

UICollectionViewCell不存在各式各样的默认的style,这主要是由于展示对象的性质决定的,因为UICollectionView所用来展示的对象相比UITableView来说要来得灵活,大部分情况下更偏向于图像而非文字,因此需求将会千奇百怪。因此SDK提供给我们的默认的UICollectionViewCell结构上相对比较简单,由下至上:

  • 首先是cell本身作为容器view
  • 然后是一个大小自动适应整个cell的backgroundView,用作cell平时的背景
  • 再其上是selectedBackgroundView,是cell被选中时的背景
  • 最后是一个contentView,自定义内容应被加在这个view上
UICollectionViewCell.jpg
BackgroundView.jpg
SelectedBackgroundView.jpg
ContentView.jpg

这次Apple给我们带来的好康是被选中cell的自动变化,所有的cell中的子view,也包括contentView中的子view,在当cell被选中时,会自动去查找view是否有被选中状态下的改变。比如在contentView里加了一个normal和selected指定了不同图片的imageView,那么选中这个cell的同时这张图片也会从normal变成selected,而不需要额外的任何代码。

五、 UICollectionViewLayout

这是UICollectionView和UITableView最大的不同。
UICollectionViewLayout可以说是UICollectionView的大脑和中枢,它负责了将各个cell、Supplementary View和Decoration Views进行组织,为它们设定各自的属性,包括但不限于:

  • 位置
  • 尺寸
  • 透明度
  • 层级关系
  • 形状
  • 等等等等…

Layout决定了UICollectionView是如何显示在界面上的。在展示之前,一般需要生成合适的UICollectionViewLayout子类对象,并将其赋予CollectionView的collectionViewLayout属性。

六、 UICollectionViewLayoutAttributes

UICollectionViewLayoutAttributes是一个非常重要的类,先来看看property列表

  • @property (nonatomic) CGRect frame
  • @property (nonatomic) CGPoint center
  • @property (nonatomic) CGSize size
  • @property (nonatomic) CATransform3D transform3D
  • @property (nonatomic) CGFloat alpha
  • @property (nonatomic) NSInteger zIndex
  • @property (nonatomic, getter=isHidden) BOOL hidden

可以看到,UICollectionViewLayoutAttributes的实例中包含了诸如边框,中心点,大小,形状,透明度,层次关系和是否隐藏等信息。和DataSource的行为十分类似,当UICollectionView在获取布局时将针对每一个indexPath的部件(包括cell,追加视图和装饰视图),向其上的UICollectionViewLayout实例询问该部件的布局信息,这个布局信息,就以UICollectionViewLayoutAttributes的实例的方式给出。

七、 自定义的UICollectionViewLayout

UICollectionViewLayout的功能为向UICollectionView提供布局信息,不仅包括cell的布局信息,也包括追加视图和装饰视图的布局信息。实现一个自定义layout的常规做法是继承UICollectionViewLayout类,然后重载下列方法:

  • -(CGSize)collectionViewContentSize

    • 返回collectionView的内容的尺寸
  • -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

    • 返回rect中的所有的元素的布局属性
    • 返回的是包含UICollectionViewLayoutAttributes的NSArray
    • UICollectionViewLayoutAttributes可以是cell,追加视图或装饰视图的信息,通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes
      • layoutAttributesForCellWithIndexPath:
      • layoutAttributesForSupplementaryViewOfKind:withIndexPath:
      • layoutAttributesForDecorationViewOfKind:withIndexPath:
  • -(UICollectionViewLayoutAttributes _)layoutAttributesForItemAtIndexPath:(NSIndexPath _)indexPath

    • 返回对应于indexPath的位置的cell的布局属性
  • -(UICollectionViewLayoutAttributes _)layoutAttributesForSupplementaryViewOfKind:(NSString _)kind atIndexPath:(NSIndexPath *)indexPath

    • 返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载
  • -(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString_)decorationViewKind atIndexPath:(NSIndexPath _)indexPath

    • 返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载
  • -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

    • 当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息

另外需要了解的是,在初始化一个UICollectionViewLayout实例后,会有一系列准备方法被自动调用,以保证layout实例的正确。

首先,-(void)prepareLayout将被调用,默认下该方法什么没做,但是在自己的子类实现中,一般在该方法中设定一些必要的layout的结构和初始需要的参数等。

之后,-(CGSize) collectionViewContentSize将被调用,以确定collection应该占据的尺寸。注意这里的尺寸不是指可视部分的尺寸,而应该是所有内容所占的尺寸。collectionView的本质是一个scrollView,因此需要这个尺寸来配置滚动行为。

接下来-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect被调用,这个没什么值得多说的。初始的layout的外观将由该方法返回的UICollectionViewLayoutAttributes来决定。

另外,在需要更新layout时,需要给当前layout发送 -invalidateLayout,该消息会立即返回,并且预约在下一个loop的时候刷新当前layout,这一点和UIView的setNeedsLayout方法十分类似。在-invalidateLayout后的下一个collectionView的刷新loop中,又会从prepareLayout开始,依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局。

实现DecorationView

一、 效果
DecorationView.gif
二、 源码

Defination.swift

import UIKit

let screenWidth = UIScreen.mainScreen().bounds.width  

DecorationView.swift

import UIKit

let decorationIdentifier = "DecorationIdentifier"

class DecorationView: UICollectionReusableView {
    
    var imageView : UIImageView?
    
    override init(frame: CGRect) {
        
        super.init(frame: frame)
        
        imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: screenWidth, height: 120))
        self.imageView!.image = UIImage(named: "SingleBookShelf")
        
        self.addSubview(imageView!)
    }

    required init?(coder aDecoder: NSCoder) {
        
        fatalError("init(coder:) has not been implemented")
    }
}  

DemoCollectionViewCell.swift

import UIKit

let cellIdentifier = "CellIdentifier"

class DemoCollectionViewCell: UICollectionViewCell {
    
    var imageView : UIImageView!
    
    override init(frame: CGRect) {
        
        super.init(frame: frame)
        
        self.imageView = UIImageView(frame: self.bounds)
        
        self.addSubview(self.imageView)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}  

DemoLayout.swift

import UIKit

class DemoLayout: UICollectionViewFlowLayout {

    var cellCount    : Int?
    var sectionCount : Int?
    
    override func prepareLayout() {
        
        super.prepareLayout()
        
        self.cellCount               = self.collectionView?.numberOfItemsInSection(0)
        self.sectionCount            = self.collectionView?.numberOfSections()
        self.itemSize                = CGSize(width: 100, height: 100)
        self.sectionInset            = UIEdgeInsets(top: 25, left: 25, bottom: 25, right: 25)
        self.minimumInteritemSpacing = 40
        self.minimumLineSpacing      = 20
        
        self.registerClass(DecorationView.classForCoder(), forDecorationViewOfKind: decorationIdentifier)
    }
    
    override func layoutAttributesForDecorationViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        
        let attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, withIndexPath: indexPath)
        
        attributes.frame  = CGRect(x: 0, y: indexPath.row * 120 + 12, width: Int(screenWidth), height: 120)
        attributes.zIndex = -1
        
        return attributes
    }
    
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        // 方法1: 通过主动调用 self.layoutAttributesForItemAtIndexPath(indexPath) 和 self.layoutAttributesForDecorationViewOfKind(decorationIdentifier, atIndexPath: indexPath) 生成全部 UICollectionViewLayoutAttributes
        var attributesArray = [UICollectionViewLayoutAttributes]()
        
        for i : Int in 0..<cellCount! {
        
            let indexPath = NSIndexPath(forItem: i, inSection: 0)
            let attributes = self.layoutAttributesForItemAtIndexPath(indexPath)
            attributesArray.append(attributes!)
        }
        
        for i : Int in 0..<cellCount!/2 {
        
            let indexPath = NSIndexPath(forRow: i, inSection: 0)
            let attributes = self.layoutAttributesForDecorationViewOfKind(decorationIdentifier, atIndexPath: indexPath)
            attributesArray.append(attributes!)
        }
        
        return attributesArray
        
        // 方法2: 通过调用 super.layoutAttributesForElementsInRect(rect) ,获得已经拥有的 UICollectionViewLayoutAttributes,根据已经拥有的,增添你需要的UICollectionViewLayoutAttributes
//        let attributes : [UICollectionViewLayoutAttributes]? = super.layoutAttributesForElementsInRect(rect)
//        
//        var allAttibutes = attributes
//        
//        for attribute in attributes! {
//            
//            if attribute.frame.origin.x == 25 {
//                
//                print(attribute.indexPath)
//                let decorationAttributes : UICollectionViewLayoutAttributes =
//                UICollectionViewLayoutAttributes(forDecorationViewOfKind: decorationIdentifier, withIndexPath: attribute.indexPath)
//                
//                decorationAttributes.frame =
//                    CGRect(x: 0, y: attribute.frame.origin.y - 10, width: screenWidth, height: 120)
//                
//                decorationAttributes.zIndex = attribute.zIndex - 1
//                
//                allAttibutes?.append(decorationAttributes)
//            }
//        }
//
//        return allAttibutes
    }
}

ViewController.swift


import UIKit

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

    var collectionView : UICollectionView!
    var dataArray      : [UIImage]?
    var demoLayout     : DemoLayout!
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        self.buildData()
        self.buildCollectionView()

    }
    
    func buildData() {
    
        self.dataArray = [UIImage]()
        
        for imageIndex in 1...16 {
        
            let image = UIImage(named: "\(imageIndex)")
            self.dataArray?.append(image!)
        }
    }
    
    func buildCollectionView() {
    
        demoLayout = DemoLayout()
        
        self.collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: demoLayout)
        self.collectionView.registerClass(DemoCollectionViewCell.classForCoder(), forCellWithReuseIdentifier: cellIdentifier)
        self.collectionView.dataSource = self
        self.collectionView.delegate   = self
        
        self.view.addSubview(self.collectionView)
    }
    
    // MARK: UICollectionViewDataSource
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        return (self.dataArray?.count)!
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        
        let cell =
        collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! DemoCollectionViewCell
        
        cell.imageView.image = self.dataArray![indexPath.row] 
        
        return cell
    }
}

下载源码

下载地址

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

推荐阅读更多精彩内容