swift详解之二十三------------UICollectionView基础用法和简单自定义

来源:互联网 发布:网络机顶盒接音响 编辑:程序博客网 时间:2024/06/06 05:15

UICollectionView基础用法和简单自定义

注:本文通过几个实例来讲讲UICollectionView基本用法


本次要实现的两个效果。感谢猫神提供的教程 OneV’s Den

这里写图片描述

第一个界面是一个普通的流布局 UICollectionViewFlowLayout, 第二个界面是自定义的一个圆形布局。加了点手势操作和动画。老规矩。后面会附上源码

首先来看下基本用法 。

1、UICollectionView基础用法

简单的UICollectionView 相当于GridView ,一个多列的UItableView ,然而UICollectionViewUItableView 的操作也非常相似 。都是会设置有一个DataSource 和一个delegate 标准的UICollectionView包含三个部分,它们都是UIView的子类:

  • cells 单元格用来展示内容的,可以设置所有的大小 也可以指定不同尺寸和不同的内容

  • Supplementary Views 追加视图 如果你对UITableView比较熟悉的话,可以理解为每个Section的Header或者Footer,用来标记每个section的view

  • Decoration Views 装饰视图 这是每个section的背景

UICollectionView和UITableView最大的不同就是UICollectionViewLayout,UICollectionViewLayout可以说是UICollectionView的大脑和中枢,它负责了将各个cell、Supplementary View和Decoration Views进行组织,为它们设定各自的属性。包括位置、尺寸、层级、形状等等 。。

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

下面我们实现一个最简单的Demo

let layout = UICollectionViewFlowLayout()layout.scrollDirection = UICollectionViewScrollDirection.Vertical  //滚动方向layout.itemSize = CGSizeMake(60, 75) //设置所有cell的size  太重要了 找了半天。(自学就是辛苦呀!!)layout.minimumLineSpacing = 10.0  //上下间隔layout.minimumInteritemSpacing = 5.0 //左右间隔layout.headerReferenceSize = CGSizeMake(20, 20)layout.footerReferenceSize = CGSizeMake(20, 20)

这里创建了基本的流布局 设置了一些基本属性。

然后其他的设置和UITableView差不多

let collect:UICollectionView = UICollectionView(frame: self.view.frame,collectionViewLayout:layout)collect.backgroundColor = UIColor.whiteColor()collect.delegate = selfcollect.dataSource = selfself.view.addSubview(collect)

因为初始的背景色是黑色的,这里指定了背景色

然后实现下面三个基本的方法,就能正常跑了 。最要是cell的显示方法

//设置分区个数func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {        return 1    }//设置每个分区元素个数func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {        return  10    }//设置元素内容func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {        //这里创建cell          return cell }

为了得到高效的View,对于cell的重用是必须的,避免了不断生成和销毁对象的操作,在UICollectionView中使用以下方法进行注册:

registerClass:forCellWithReuseIdentifier:registerClass:forSupplementaryViewOfKind:withReuseIdentifier:registerNib:forCellWithReuseIdentifier:registerNib:forSupplementaryViewOfKind:withReuseIdentifier:

先注册 ,使用一个Identifier ,加入重用队列。要是在重用队列里没有可用的cell的话,runtime将自动帮我们生成并初始化一个可用的cell。

我们这里是自己用xib 画了个cell
这里写图片描述

一个很简单的cell ,把它的class 设置成我们自定义的MyCellContent,MyCellContent 继承自UICollectionViewCell ,把这两个拖成它的成员属性
这里写图片描述

import UIKitclass MyCellContent: UICollectionViewCell {    @IBOutlet  var contentImage: UIImageView!    @IBOutlet  var contentLabel: UILabel!   }

然后,在我们的视图控制器中的viewDidLoad进行注册

let nib = UINib(nibName: "MyCollectionCell", bundle: NSBundle.mainBundle())collect.registerNib(nib, forCellWithReuseIdentifier: "DesignViewCell")

然后在cellForItemAtIndexPath 里面就能这样取了

 let identify:String = "DesignViewCell" let cell =collectionView.dequeueReusableCellWithReuseIdentifier(identify, forIndexPath: indexPath) as! MyCellContent

我们事先创建了个结构体,用来存放cell的img和name

struct CellContent{    var img:String    var name:String}

然后在控制器中声明了一个var dic = Array<CellContent>()

viewDidLoad中初始化。

for i in 1...9{            dic.append(CellContent(img: "f"+String(i), name: "歪脖子"+String(i)))}

我图片存放的名字就是f1-----f9

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {        return  self.dic.count}

这里返回元素个数就可以这么写了

//设置元素内容func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {    let identify:String = "DesignViewCell"    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(identify, forIndexPath: indexPath) as! MyCellContent    cell.contentView.backgroundColor = UIColor.grayColor()    cell.contentView.alpha = 0.2    let img = UIImage(named: (self.dic[indexPath.row] as CellContent).img)    cell.contentImage.image = img    cell.contentLabel.text = (self.dic[indexPath.row] as CellContent).name    return cell}

这个就可以很简单的设置了 。现在运行,第一个页面的效果就有了

但让还有向UITableView中样很多的方法去设置别的,比如单个cell的大小

 func collectionView(collectionView: UICollectionView!,        layout collectionViewLayout: UICollectionViewLayout!,        sizeForItemAtIndexPath indexPath: NSIndexPath!) -> CGSize {            return CGSizeMake(150, 150)    }

点击cell

 //点击元素    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){      print("点击了第\(indexPath.section) 分区 ,第\(indexPath.row) 个元素")    }

还有很多,自己慢慢玩吧 。下面看看自定义的

2、自定义UICollectionViewLayout

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

-(CGSize)collectionViewContentSize //返回collectionView的内容的尺寸-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect//返回rect中的所有的元素的布局属性-(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,这一点和UIViewsetNeedsLayout方法十分类似。在-invalidateLayout后的下一个collectionView的刷新loop中,又会从prepareLayout开始,依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局。

以上都是猫神的巨作,他写的很好直接拿来用了

下面看下demo

首先创建一个类继承自UICollectionViewLayou 然后声明一些基本的属性

    private var _cellCount:Int?    private var _collectSize:CGSize?    private var _center:CGPoint?    private var _radius:CGFloat?

按照上面的步骤

//一般在该方法中设定一些必要的layout的结构和初始需要的参数等override func prepareLayout() {    super.prepareLayout()    _collectSize = self.collectionView?.frame.size    _cellCount = self.collectionView?.numberOfItemsInSection(0)    _center = CGPointMake(_collectSize!.width / 2.0, _collectSize!.height / 2.0);    _radius = min(_collectSize!.width, _collectSize!.height)/2.5}

这个方法初始化了一些基本信息

 //内容区域的总大小 (不是可见区域)    override func collectionViewContentSize() -> CGSize {        return _collectSize!  //这里不用可见区域吧    }

可见区域

override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {        var attributesArray = [UICollectionViewLayoutAttributes]()        if let count = self._cellCount {            for i in 0 ..< count{                //这里利用了-layoutAttributesForItemAtIndexPath:来获取attributes                let indexPath = NSIndexPath(forItem: i, inSection: 0)                let attributes =  self.layoutAttributesForItemAtIndexPath(indexPath)                attributesArray.append(attributes!)            }        }        return attributesArray    }override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {        let attrs = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)        attrs.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE)        let x = Double(_center!.x) + Double(_radius!) * cos(Double(2 * indexPath.item) * M_PI/Double(_cellCount!))        let y = Double(_center!.y) + Double(_radius!) * sin(Double(2 * indexPath.item) * M_PI/Double(_cellCount!))        attrs.center = CGPointMake( CGFloat(x) , CGFloat(y));        return attrs    }

这个方法layoutAttributesForItemAtIndexPathUICollectionViewLayoutAttributes 的一些属性进行设置 ,前面列出过 ,然后layoutAttributesForElementsInRect 方法返回所有UICollectionViewLayoutAttributes , 以数组的方式

然后再使用的时候把基本用法里的layout换掉

  layout = MyCollectionViewLayout()  collect = UICollectionView(frame: self.view.frame,collectionViewLayout:layout)  collect.backgroundColor = UIColor.whiteColor()  collect.delegate = self  collect.dataSource = self

这样运行 , 圆就出现了

  if(layout is MyCollectionViewLayout){            layout = UICollectionViewFlowLayout()            (layout as! UICollectionViewFlowLayout).scrollDirection = UICollectionViewScrollDirection.Vertical  //滚动方向            (layout as! UICollectionViewFlowLayout).itemSize = CGSizeMake(60, 75)        }else{            layout = MyCollectionViewLayout()        }        self.collect.setCollectionViewLayout(layout, animated: true)

可以通过setCollectionViewLayout 方法来切换layout
这里写图片描述

然后给这个界面添加手势

//注册tap手势事件let tapRecognizer = UITapGestureRecognizer(target: self, action: "handleTap:")collect.addGestureRecognizer(tapRecognizer)
    func handleTap(sender:UITapGestureRecognizer){        if sender.state == UIGestureRecognizerState.Ended{            let tapPoint = sender.locationInView(self.collect)            if let  indexPath = self.collect.indexPathForItemAtPoint(tapPoint)            {                //点击了cell                //这个方法可以用来对collectionView中的元素进行批量的插入,删除,移动等操作,同时将触发collectionView所对应的layout的对应的动画。                print("------")                self.collect.performBatchUpdates({ () -> Void in                    self.collect.deleteItemsAtIndexPaths([indexPath])                    self.dic.removeAtIndex(indexPath.row)                }, completion: nil)            }else{                let val =  arc4random_uniform(8)+1                self.dic.append(CellContent(img: "f"+String(val), name: "歪脖子"+String(val)))                self.collect.insertItemsAtIndexPaths([NSIndexPath(forItem: Int(val) , inSection: 0)])//                dispatch_async(dispatch_get_global_queue(0, 0), { () -> Void in//                     let val =  arc4random_uniform(9)//                    self.dic.append(CellContent(img: "f"+String(val), name: "歪脖子"+String(val)))//                    dispatch_async(dispatch_get_main_queue()) {//                            self.collect.reloadData()//                    //                    }//                })                //点击了不是cell的区域                print("+++++++")            }        }    }

我注释掉这段GCD的代码也是可以执行的 ,就是没有动画 。
这个方法performBatchUpdates:completion 可以用来对collectionView中的元素进行批量的插入,删除,移动等操作,同时将触发collectionView所对应的layout的对应的动画。相应的动画由layout中的下列四个方法来定义:

initialLayoutAttributesForAppearingItemAtIndexPath:initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:finalLayoutAttributesForDisappearingItemAtIndexPath:finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:

默认的动画是这样的
这里写图片描述

我们可以自定义动画

每次重新给出layout时都会调用prepareLayout,这样在以后如果有collectionView大小变化的需求时也可以自动适应变化。

 override func initialLayoutAttributesForAppearingItemAtIndexPath(itemIndexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {        // Must call super        var attributes = super.initialLayoutAttributesForAppearingItemAtIndexPath(itemIndexPath)        if self.insertIndexPaths.contains(itemIndexPath) {            if let _ = attributes{                attributes = self.layoutAttributesForItemAtIndexPath(itemIndexPath)            }            // Configure attributes ...            attributes!.alpha = 0.0;            attributes!.center =  CGPointMake(_center!.x, _center!.y);            //attributes?.size = CGSizeMake(1000, 1000)        }        return attributes;    }    override func prepareForCollectionViewUpdates(updateItems: [UICollectionViewUpdateItem]) {        super.prepareForCollectionViewUpdates(updateItems)        self.insertIndexPaths = [NSIndexPath]()        for update in updateItems{            if update.updateAction == UICollectionUpdateAction.Insert{                self.insertIndexPaths.append(update.indexPathAfterUpdate)            }    }

首先会调用prepareForCollectionViewUpdates,我们在这里拿到那个新增的NSIndexPath ,然后initialLayoutAttributesForAppearingItemAtIndexPath 在这个方法中设置一些初始位置。

看下效果
这里写图片描述

这个是从中间散出去的 ,同理也可以搞一些别的效果 。大概就这些吧。当然UICollectionView可以玩的还很多,期待大家一起探索。多分享哦!
(本实例使用xcode 7 bate , swift 2.0)
最后附上源码:https://github.com/smalldu/SwiftStudy

学习iOS,有他就够了,小码哥视频,传智、黑马、各种swift书籍

0 0
原创粉丝点击