swift:自定义UICollectionViewFlowLayout

UICollection是iOS6的时节引入的,它是跟UITableview共享一仿API设计,都是根据datasource和delegate,都蝉联自UIScrollView。但其又和UITableview有格外特别不同,它进行了更为的虚幻,将其的装有的子视图的位置、大小、transform委托为了一个独的布局对象:UICollectionViewLayout。这是一个抽象类,我们可以继续它们来兑现其他想要之布局,系统为为我们提供了一个开箱即食的实现UICollectionViewFlowLayout。在我看来,没有任何布局是UICollenctionViewLayout不能够落实之,如果起那便于定义一个。

行文目的

UICollectionView凡ios中一个分外强有力的控件,利用它能充分简短的落实有百般难堪的效能。UICollectionView的功力还要凭借让UICollectionViewLayout抑或它的子类UICollectionViewFlowLayout。而至于从定义UICollectionViewFlowLayout网上介绍的较少。出于这等同目的,写下就边文章,希望会帮初学者(我吧是)实现有大概的流水布局功用。下面的演示就是本篇文章的目标。最终版代码和所有图片素材(图片名和花色蒙有接触不等同)已经上传至Github,大家可下载学习。

hg0088皇冠 1

UITableview只能提供竖直滑动的布局,而且默认情况下cell的大幅度和tableView的大幅度一致,而且cell的排列顺序吗是各个排列。UICollectionView则也我们提供了别样一样种可能:它能够提供竖直滑动的布局也会提供竖屏滑动的布局,而且cell的职位、大小相等完全出于而自己主宰。所以当我们用到水平滑动的布局时,不要忙在用UIScrollView去落实,可以先行考虑UICollectionView能不能够满足要求,还有一个好处是你不要自己考虑滑动视图cell的选用问题。

差一点个大概的概念

  • UICollectionViewLayout与UICollectionViewFlowLayout

UICollectionView的显示效果几乎全部由UICollectionViewLayout担当(甚至是cell的分寸)。所以,一般开发中所说之自定义UICollectionView也便是自从定义UICollectionViewLayout。而UICollectionViewFlowLayout凡累自UICollectionViewLayout的,由苹果官方实现之湍流布局功能。如果想协调实现有水流布局功能好延续自最老UICollectionViewLayout从头写,也堪持续自UICollectionViewFlowLayout进行改动。文本是持续自UICollectionViewFlowLayt*

  • UICollectionViewLayoutAttributes

第二点即说了UICollectionView的来得效果几乎任何由UICollectionViewLayout担负,而真正存储在每一个cell的职、大小等属性的凡UICollectionViewLayoutAttributes。每一个cell对许在一个属于自己之UICollectionViewLayoutAttributes,而UICollectionViewLayout正是以UICollectionViewLayoutAttributes里存在的音讯对各级一个cel进行布局。

  • 流水布局

所谓流水布局就是:就是cell以一定的原理进行如同流水般的来规律的一个继一个底排。�最经典的流水布局就是九宫格布局,绝大部分底图样选择器也是流水布局。

当下篇稿子会怎样从定义UICollectionViewLayout来贯彻自由布局,默认你都会下系统提供的UICollectionViewFlowLayout来拓展规范的Grid
View布局了。

预备干活

  • xcode7.0
  • swift2.0
  • 友善本身提供的材料并在控制器中上加如下代码

class ViewController: UIViewController,UICollectionViewDelegate, UICollectionViewDataSource {

    lazy var imageArray: [String] = {

        var array: [String] = []

        for i in 1...20 {
            array.append("\(i)-1")
        }

        return array
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        let collectionView =  UICollectionView(frame: CGRectMake(0, 100, self.view.bounds.width, 200), collectionViewLayout: UICollectionViewFlowLayout())
        collectionView.backgroundColor = UIColor.blackColor()
        collectionView.dataSource  = self
        collectionView.delegate = self

        collectionView.registerClass(ImageTextCell.self, forCellWithReuseIdentifier: "ImageTextCell")
        self.view.addSubview(collectionView)
    }

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

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ImageTextCell", forIndexPath: indexPath) as! ImageTextCell
        cell.imageStr = self.imageArray[indexPath.item]

        return cell
    }

}
//这里是自定义cell的代码
class ImageTextCell: UICollectionViewCell {

    var imageView: UIImageView?
    var imageStr: NSString? {

        didSet {
            self.imageView!.image = UIImage(named: self.imageStr as! String)
        }

    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.imageView = UIImageView()
        self.addSubview(self.imageView!)

    }

    override func layoutSubviews() {
        super.layoutSubviews()
        self.imageView?.frame = self.bounds
    }

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

}

成效应是这般的

hg0088皇冠 2

1、UICollectuonViewFlowLayout

系统也咱提供了一个自定义的布局实现:UICollectionViewFlowLayout,通过其我们可实现Grid
View类型的布局,也不怕是诸如一个一个格子挨次排列的布局,对于大多数之场面下,使用它们便能够满足我们的渴求了。系统啊我们提供了布局所用的参数,我们在用的时光只是待去确认这些参数就推行:

NS_CLASS_AVAILABLE_IOS(6_0) @interface UICollectionViewFlowLayout :
UICollectionViewLayout

@property (nonatomic) CGFloat minimumLineSpacing;

@property (nonatomic) CGFloat minimumInteritemSpacing;

@property (nonatomic) CGSize itemSize;

@property (nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0);
// defaults to CGSizeZero – setting a non-zero size enables cells that
self-size via -preferredLayoutAttributesFittingAttributes:

@property (nonatomic) UICollectionViewScrollDirection scrollDirection;
// default is UICollectionViewScrollDirectionVertical

@property (nonatomic) CGSize headerReferenceSize;

@property (nonatomic) CGSize footerReferenceSize;

@property (nonatomic) UIEdgeInsets sectionInset;

// Set these properties to YES to get headers that pin to the top of the
screen and footers that pin to the bottom while scrolling (similar to
UITableView).

@property (nonatomic) BOOL sectionHeadersPinToVisibleBounds
NS_AVAILABLE_IOS(9_0);

@property (nonatomic) BOOL sectionFootersPinToVisibleBounds
NS_AVAILABLE_IOS(9_0);

@end

hg0088皇冠 3

比方说上面所说之GridView类型的布局不克满足我们的需要,这是就是得由定义一个Layout。

编码

2、UICollectionViewLayout  VS   UICollectionViewFlowLayout

UICollectionViewFlowLayout继承自UICollectionViewLayout,我们可一直行使它们,我们只是需要提供cell的尺寸,以及行间距、列间距,他便会见好计算出每个cell的职务和UICollectionView的滑行范围contentSize。但她只能提供一个大方向的滑行,也就是说我们从定义之好像设连续自UICollectionViewFlowLayout,则只能当一个势头直达滑的布局,要么水平方向或竖直方向。反之,则用后续自UICollectionViewLayout,UICollectionViewLayout是一个抽象类,不能够一直行使。

3、自定义布局要贯彻之道

UICollectionViewLayout文档为咱排有了要实现的艺术:

hg0088皇冠 4

上述列有的就六独道不是都待我们团结实现的,而是基于需要,选择中间的一些方法实现。

collectionViewContentSize

UICollection继承自UIScrollView,我们都掌握UIScrollView的一个重中之重参数:contentSize,如果这参数不针对,那么您布局的内容即非可知完全显示,而collectionViewContentSize就是为着拿走此参数,UICollection就比如一个画板,而collectionViewContentSize则确定了画板的轻重,如果是累自UICollectionViewFlowLayout,而且每个section里面的cell大小是由此UICollectionViewFlowLayout的参数设定的,大小和职位也非以由定义的历程被自由更改,那么collectionViewContentSize是可以免友好再次写的,系统会友善计算contentSize,如果是后续自UICollectionViewLayout,那便待根据你协调之显得布局去提供合适的CGSize给collectionViewContentSize。

layoutAttributesForElementsInRect

其一措施的参数是UICollectionView当前底bounds,也就是视图当前的可见区域,返回值是一个暗含对象也UICollectionViewLayoutAttributes的累累组,UICollectionView的可见区域外含cell、supplementary
view、decoration
view(这里统称cell,因为她都是collectionView的一个子视图),它们的岗位、大小等消息还是因为相应之UICollectionViewLayoutAttributes控制。默认情况下者LayoutAttributes包含indexPath、frame、center、size、transform3D、alpha以及hidden属性。如果您还欲控制其他的性质,你得友善打定义一个UICollectionViewLayoutAttributes的子类,加上自由而想使的性质。

布局属性对象(UICollectionViewLayoutAttributes)通过indexPath和cell关联起来,当collectionView展示cell时,会通过这些布局属性对象将到布局信息。

返回原话题,layoutAttributesForElementsInRect方法的返回值是一个往往组,这个数组里面是传递进入的可见区域外之cell所对应的UICollectionViewLayoutAttributes。

使将到可见区域外之布局属性,通常的做法如下:

如果你是继往开来自UICollectionViewFlowLayout,并且安装好了itemSize、行间距、列间距等信息,那么你通过[super
layoutAttributesForElementsInRect:rect]便能拿到可见区域外之布局属性,反之,则进步奏2。

缔造一个空数组,用于存放可见区域外之布局属性。

打UICollectionView的数据源中取出你用展示的数,然后因你想使的布局计算出什么indexPath在此时此刻可见区域外,通过CGRectIntersectsRect函数可以判两个CGRect是否发生混合来规定。然后循环调用layoutAttributesForItemAtIndexPath:来确定各一个布局属性之frame等数。同样,如果手上区域外出supplementary
view或者decoration
view,你也需要调用:layoutAttributesForSupplementaryViewOfKind:atIndexPath或者layoutAttributesForDecorationViewOfKind:atIndexPath,最后用这些布局属性添加到数组中回到。这里需要多说一些之是,有些布局属性在UICollectionViewLayout的prepareLayout就根据数据源全部计了出去,比如瀑布流样式的布局,这个上你就是独自待回到布局属性的frame和当下可见区域有交集的对象就实施。

layoutAttributesFor…IndexPath

此处用三个点,是盖发三只类似之计:

layoutAttributesForItemAtIndexPath:

layoutAttributesForSupplementaryViewOfKind:atIndexPath:

layoutAttributesForDecorationViewOfKind:atIndexPath:

它各自吗cell、supplementaryView、decorationView返回布局属性,它们的贯彻非是须的,它们才是啊对应之IndexPath返回布局属性,如果你会由此其他方式将到对许indexPath处的布局属性,那就是从未有过必要非要是促成这几个方法。

盖layoutAttributesForItemAtIndexPath:为例,你可以经+[UICollectionViewLayoutAttributes
layoutAttributesForCellWithIndexPath:]术将到一个布局属性对象,然后您可能需要看你的数目源去算有拖欠indexPath处的布局属性的frame等信息,然后赋值给其。

shouldInvalidateLayoutForBoundsChange

夫是故来报告collectionView是否要依据bounds的反而重复计算布局属性,比如横竖屏的转动。通常的写法如下:

– (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

{

CGRect oldBounds = self.collectionView.bounds;

if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) {

return YES;

}

return NO;

}

用留意的凡,当当滑行的经过中,需要针对某些cell的布局进行更改,那么就是需要在这个办法中返回YES,告诉UICollectionView重新计算布局。因为一个cell的改变会挑起上上下下UICollectionView布局的变更。

水平排列

创造一个叫做吧LineLayout.swift的文书(继承自UICollectionViewFlowLayout)。添加如下几行代码

    var itemW: CGFloat = 100
    var itemH: CGFloat = 100

    override init() {
        super.init()

        //设置每一个元素的大小
        self.itemSize = CGSizeMake(itemW, itemH)
        //设置滚动方向
        self.scrollDirection = .Horizontal
        //设置间距
        self.minimumLineSpacing = 0.7 * itemW
    }

    //苹果推荐,对一些布局的准备操作放在这里
    override func prepareLayout() {
        //设置边距(让第一张图片与最后一张图片出现在最中央)ps:这里可以进行优化
        let inset = (self.collectionView?.bounds.width ?? 0)  * 0.5 - self.itemSize.width * 0.5
        self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset)
    }

效果即使成为了这么

hg0088皇冠 5

4、示例一:瀑布流实现

瀑布流的排一般用来图片或商品的来得,它的布局特点是等宽变高,cell的排是找到最好缺少的那么一列,然后将cell放到那个位置,效果如下:

hg0088皇冠 6

下面我们来看看具体的实现,这里的布局行间距和列间距都稳定10,列数固定为3列,如齐图所显示。

系提供于咱的UICollectionViewFlowLayout显然不可知兑现瀑布流的布局,因为其的默认实现是一行一列整齐对一头的,所以我们要新建一个后续自UICollectionViewFlowLayout的接近,然后来教学一下夫近乎的兑现。

prepareLayout

每当授课如何布局瀑布流之前要先证实一下UICollectionViewFlowLayout底prepareLayout方法,他会见于UICollectionView布局之前调用,调用[self.collectionView
reloadData]和[self.collectionView.collectionViewLayout
invalidateLayout]的时候prepareLayout也会进展调用,如果shouldInvalidateLayoutForBoundsChange返回YES,prepareLayout方法同样也会调用。所以这函数是提前开展数据布局计算的绝佳地方。

当展开瀑布流布局的下咱们可于prepareLayout里面冲数据源,计算起有的布局属性并缓存起来:

– (void)prepareLayout {

[super prepareLayout];

//记录布局要之contentSize的惊人

self.contentHeight = 0;

//columnHeights数组会记录各列的当下布局高度

[self.columnHeights removeAllObjects];

//默认高度是sectionEdge.top

for (NSInteger i = 0; i < self.columnCount; i++) {

[self.columnHeights addObject:@(self.edgeInsets.top)];

}

//清除之前所盖之布局属性数据

[self.attrsArray removeAllObjects];

//通过数据源拿到用展示的cell数量

NSInteger count = [self.collectionView numberOfItemsInSection:0];

//开始创办每一个cell对应之布局属性

for (NSInteger index = 0; index < count; index++) {

//创建indexPath

NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index
inSection:0];

//获取cell布局属性,在layoutAttributesForItemAtIndexPath里面计算具体的布局信息

UICollectionViewLayoutAttributes *attrs = [self
layoutAttributesForItemAtIndexPath:indexPath];

[self.attrsArray addObject:attrs];

}

}

每当layoutAttributesForItemAtIndexPath方法中去因参数indexPath拿到数据源里面对应位置的显得数据,根据对等方便的前提,等比例之获取布局属性的冲天,然后因记录每列当前布局到的莫大的数组columnHeights来找到时布局最缺乏的那一列,从而取得到布局属性的origin信息,这样以抵富有的前提下虽得到到了当前indexPath处的布局属性之frame信息。然后更新columnHeights里面的数额,并且给记录布局所需要高度的变量contentHeight等于当前排高度数组里面的极度特别价值。

-(UICollectionViewLayoutAttributes
*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {

//获取一个UICollectionViewLayoutAttributes对象

UICollectionViewLayoutAttributes *attrs = [super
layoutAttributesForItemAtIndexPath:indexPath];

//列数是3,布局属性的升幅是稳定的

CGFloat collectionViewW = self.collectionView.frame.size.width;

CGFloat width = (collectionViewW – self.edgeInsets.left –
self.edgeInsets.right – (self.columnCount – 1) * self.columnMargin) /
self.columnCount;

CGFloat height = 通过数据源以及宽度信息,获取对许位置的布局属性高度;

//找到数组内即高度最好小之那么同样列

NSInteger destColumn = 0;

CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];

for (NSInteger index = 1; index < self.columnCount; index++) {

CGFloat columnHeight = [self.columnHeights[index] doubleValue];

if (minColumnHeight > columnHeight) {

minColumnHeight = columnHeight;

destColumn = index;

break;

}

}

//根据列信息,计算出origin的x

CGFloat x = self.edgeInsets.left + destColumn * (width
+self.columnMargin);

CGFloat y = minColumnHeight;

if (y != self.edgeInsets.top) {//不是第一履行就是增长行间距

y += self.rowMargin;

}

//得到布局属性的frame信息

attrs.frame = CGRectMake(x, y, width, height);

//更新最短那列的冲天

self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));

//更新记录显示布局所要的莫大

CGFloat columnHeight = [self.columnHeights[destColumn] doubleValue];

if (self.contentHeight < columnHeight) {

self.contentHeight = columnHeight;

}

return attrs;

}

滑的长河在,cell会不断用,系统会调用layoutAttributesForElementsInRect方法来取当前可见区域外之布局属性,由于有着的布局属性都缓存了起,则独自需要返回布局属性之frame和当下可见区域发生交集的布局属性就实行。

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

NSMutableArray *rArray = [NSMutableArray array];

for (UICollectionViewLayoutAttributes *cacheAttr in _attrsArray) {

if (CGRectIntersectsRect(cacheAttr.frame, rect)) {

[rArray addObject:cacheAttr];

}

}

return rArray;

}

最后由我们于定义了每个cell的万丈及布局,所以系统是勿掌握UICollectionView当前之contentSize的分寸,所以我们需要以collectionViewContentSize方法里返回正确的size以保险所以cell都能健康滑动到可见区域里来。

-(CGSize)collectionViewContentSize {

return CGSizeMake(CGRectGetWidth(self.collectionView.frame),
self.contentHeight + self.edgeInsets.bottom);

}

迄今为止,瀑布流的布局就完了了,实现起来非常简单,最重大之地方就是测算布局属性之frame信息。

shouldInvalidateLayoutForBoundsChange方法和layoutAttributesForElementsInRect方法关系

题所形容有之凡坏要的星星方法,先押本身长的如下测试代码

    /**
    返回true只要显示的边界发生改变就重新布局:(默认是false)
    内部会重新调用prepareLayout和调用
    layoutAttributesForElementsInRect方法获得部分cell的布局属性
    */
    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
        print(newBounds)
        return true
    }

    /**
    用来计算出rect这个范围内所有cell的UICollectionViewLayoutAttributes,
    并返回。
    */
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        print("layoutAttributesForElementsInRect==\(rect)")
        let ret = super.layoutAttributesForElementsInRect(rect)
//        print(ret?.count)
        return ret
    }

为了诠释,我上加了几乎只打印语句,在shouldInvalidateLayoutForBoundsChange返回值设置为true后,会发现layoutAttributesForElementsInRect方式调用十分往往,几乎是各级滑动一点便会见调用一差。观察打印信息可以窥见多秘密

  • 起先程序来如下打印

layoutAttributesForElementsInRect==(0.0, 0.0, 568.0, 568.0)

仿佛看无极端清楚,没事,尝试滑动。

  • 滑动

(0.5, 0.0, 320.0, 200.0) //这个是shouldInvalidateLayoutForBoundsChange方法的打印的newBounds
layoutAttributesForElementsInRect==(0.0, 0.0, 568.0, 568.0)//这个是layoutAttributesForElementsInRect打印的rect
(1.5, 0.0, 320.0, 200.0) 
layoutAttributesForElementsInRect==(0.0, 0.0, 568.0, 568.0)
(3.5, 0.0, 320.0, 200.0)
layoutAttributesForElementsInRect==(0.0, 0.0, 568.0, 568.0)
...

容易窥见,shouldInvalidateLayoutForBoundsChange的参数newBounds的意是UICollectionView的可见矩形。什么叫可见矩阵?,因为UICollectionView也是UIScrollView的子类,所以它们真的的“内容”远远不止我们屏幕及视的那么多(这里不再说话时持续说可见矩阵)。那类layoutAttributesForElementsInRect打印出的事物没有吗转是怎么回事?不着急继续滑动。

  • 解密

继承滑动后出这些信,经过删除一些无用信息,显示如下。(注意看有注释的尽)

...
(248.0, 0.0, 320.0, 200.0)
layoutAttributesForElementsInRect==(0.0, 0.0, 568.0, 568.0)
(249.0, 0.0, 320.0, 200.0)  //这里是可见矩阵
layoutAttributesForElementsInRect==(0.0, 0.0, 1136.0, 568.0)  //这里变化了1136.0是568.0的2倍(1136代表的是宽度的意思应该知道不需要解释吧)
(250.0, 0.0, 320.0, 200.0)
layoutAttributesForElementsInRect==(0.0, 0.0, 1136.0, 568.0)
...
(567.5, 0.0, 320.0, 200.0)
layoutAttributesForElementsInRect==(0.0, 0.0, 1136.0, 568.0)
(568.5, 0.0, 320.0, 200.0)//这里是可见矩阵
layoutAttributesForElementsInRect==(568.0, 0.0, 568.0, 568.0)  // 这里又变化了,x变成了568,宽度变成了568
(571.0, 0.0, 320.0, 200.0)
layoutAttributesForElementsInRect==(568.0, 0.0, 568.0, 568.0)
...
(815.0, 0.0, 320.0, 200.0)
layoutAttributesForElementsInRect==(568.0, 0.0, 568.0, 568.0)
(817.0, 0.0, 320.0, 200.0)
layoutAttributesForElementsInRect==(568.0, 0.0, 1136.0, 568.0) //还有这里
...
(1135.0, 0.0, 320.0, 200.0)
layoutAttributesForElementsInRect==(568.0, 0.0, 1136.0, 568.0)
(1136.0, 0.0, 320.0, 200.0)
layoutAttributesForElementsInRect==(1136.0, 0.0, 568.0, 568.0)  //还有这里

地方的底数显示实在已经够用解释一切了。读到此,推荐而自己去追寻找规律,通过友好意识的奥秘绝对比直接扣自己写起答案来义之多!下面就张图例已经认证了通

hg0088皇冠 7

关于缘何会是568之翻番。。因为自是因此底5s模拟器。你换成4s便改成480了。至于这样设计之说辞,我猜想是为方便开展限定之规定。

5、示例二:卡片吸顶布局

卡片吸顶布局之功效如下:

hg0088皇冠 8

好看来滑到顶部的cell本应当移出当前可见区域,但我们落实之效应是易到顶部继即便停,并且可于新兴底cell覆盖。

贯彻的规律非常简单,cell的布局使用UICollectionViewFlowLayout就可知实现,我们新建一个连续自UICollectionViewFlowLayout的子类,利用这子类创建布局,可以采取UICollectionViewFlowLayout提供的参数来构建一个勿吸顶展示的collectionView:

hg0088皇冠 9

单独待提供于UICollectionViewFlowLayoutitemSize和minimumLineSpacing就行,行间距minimumLineSpacing设置为一个负数就可知成立于相互叠加的机能。

只要白手起家吸顶的功能,只待以原来的布局基础及,判断布局属性frame小于布局顶部的y值,就以布局属性之frame的y值设置为顶部的y值就推行,这样滑到顶部的cell都见面于顶部悬停下来。

@implementation CardCollectionViewFlowLayout

– (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

{

//拿到手上可见区域外的布局属性

NSArray *oldItems = [super layoutAttributesForElementsInRect:rect];

//处理当下可见区域外之布局属性吸顶

[oldItems enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes
*attributes, NSUInteger idx, BOOL *stop) {

[self recomputeCellAttributesFrame:attributes];

}];

return oldItems;

}

– (void)recomputeCellAttributesFrame:(UICollectionViewLayoutAttributes
*)attributes

{

//获取悬停处的y值

CGFloat minY = CGRectGetMinY(self.collectionView.bounds) +
self.collectionView.contentInset.top;

//拿到布局属性应该出现的职

CGFloat finalY = MAX(minY, attributes.frame.origin.y);

CGPoint origin = attributes.frame.origin;

origin.y = finalY;

attributes.frame = (CGRect){origin, attributes.frame.size};

//根据IndexPath设置zIndex能起顶部已的cell被新兴的cell覆盖的层级关系

attributes.zIndex = attributes.indexPath.row;

}

– (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

{

//由于cell在滑过程遭到见面不停改cell的职,所以需要不断重复计算有所布局属性之音

return YES;

}

@end

每当落实中未待-(CGSize)collectionViewContentSize方法的原故是,对于用UICollectionViewFlowLayout来开展布局,而未是从定义之布局,系统会自行根据你设置的itemSize等消息计算出contentSize。

缩放效果

问询了上面shouldInvalidateLayoutForBoundsChange方法与layoutAttributesForElementsInRect方法关系后,可以继承开展编码了。因为要的情就教了,剩下的就是不过是部分动画片的盘算,所以不再继续教授,直接贴发出代码。

class LineLayout: UICollectionViewFlowLayout {

    var itemW: CGFloat = 100
    var itemH: CGFloat = 100

    lazy var inset: CGFloat = {
        //这样设置,inset就只会被计算一次,减少了prepareLayout的计算步骤
        return  (self.collectionView?.bounds.width ?? 0)  * 0.5 - self.itemSize.width * 0.5
        }()

    override init() {
        super.init()

        //设置每一个元素的大小
        self.itemSize = CGSizeMake(itemW, itemH)
        //设置滚动方向
        self.scrollDirection = .Horizontal
        //设置间距
        self.minimumLineSpacing = 0.7 * itemW
    }

    //苹果推荐,对一些布局的准备操作放在这里
    override func prepareLayout() {

        //设置边距(让第一张图片与最后一张图片出现在最中央)
        self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset)
    }

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

    /**
    返回true只要显示的边界发生改变就重新布局:(默认是false)
    内部会重新调用prepareLayout和调用
    layoutAttributesForElementsInRect方法获得部分cell的布局属性
    */
    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
        return true
    }

    /**
    用来计算出rect这个范围内所有cell的UICollectionViewLayoutAttributes,
    并返回。
    */
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        //取出rect范围内所有的UICollectionViewLayoutAttributes,然而
        //我们并不关心这个范围内所有的cell的布局,我们做动画是做给人看的,
        //所以我们只需要取出屏幕上可见的那些cell的rect即可
        let array = super.layoutAttributesForElementsInRect(rect)

        //可见矩阵
        let visiableRect = CGRectMake(self.collectionView!.contentOffset.x, self.collectionView!.contentOffset.y, self.collectionView!.frame.width, self.collectionView!.frame.height)

        //接下来的计算是为了动画效果
        let maxCenterMargin = self.collectionView!.bounds.width * 0.5 + itemW * 0.5;
        //获得collectionVIew中央的X值(即显示在屏幕中央的X)
        let centerX = self.collectionView!.contentOffset.x + self.collectionView!.frame.size.width * 0.5;
        for attributes in array! {
            //如果不在屏幕上,直接跳过
            if !CGRectIntersectsRect(visiableRect, attributes.frame) {continue}
            let scale = 1 + (0.8 - abs(centerX - attributes.center.x) / maxCenterMargin)
            attributes.transform = CGAffineTransformMakeScale(scale, scale)
        }

        return array
    }

    /**
    用来设置collectionView停止滚动那一刻的位置

    - parameter proposedContentOffset: 原本collectionView停止滚动那一刻的位置
    - parameter velocity:              滚动速度

    - returns: 最终停留的位置
    */
    override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        //实现这个方法的目的是:当停止滑动,时刻有一张图片是位于屏幕最中央的。

        let lastRect = CGRectMake(proposedContentOffset.x, proposedContentOffset.y, self.collectionView!.frame.width, self.collectionView!.frame.height)
        //获得collectionVIew中央的X值(即显示在屏幕中央的X)
        let centerX = proposedContentOffset.x + self.collectionView!.frame.width * 0.5;
        //这个范围内所有的属性
        let array = self.layoutAttributesForElementsInRect(lastRect)

        //需要移动的距离
        var adjustOffsetX = CGFloat(MAXFLOAT);
        for attri in array! {
            if abs(attri.center.x - centerX) < abs(adjustOffsetX) {
                adjustOffsetX = attri.center.x - centerX;
            }
        }

        return CGPointMake(proposedContentOffset.x + adjustOffsetX, proposedContentOffset.y)
    }
}

要是以控制器中进入下面两个方式,在您点击控制器,或者点击某个cell会发非常炫的动画产生,这都是苹果帮我们召开好之。

    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {

        self.imageArray.removeAtIndex(indexPath.item)

        collectionView.deleteItemsAtIndexPaths([indexPath])
    }

        override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {

        if self.collectionView!.collectionViewLayout.isKindOfClass(LineLayout.self) {
            self.collectionView!.setCollectionViewLayout(UICollectionViewFlowLayout(), animated: true)
        }else {
            self.collectionView!.setCollectionViewLayout(LineLayout(), animated: true)
        }

    }

6、总结

透过上面的例子我们可看,UICollectionView相到于一个画板,而UICollectionViewLayout则足以助我们团队画板的分寸,以及画板内容之团伙形态。在日常开支需要面临,我们也急需重视UICollectionView,利用好她好达到事半功倍的职能。

总结

本篇文章记录了自我在起定义UICollectionViewFlowLayout过程被相遇的有的题目同缓解措施(其实有一部分坑爹的题材自己从不排有,怕误导大家)。上面的全部都是基于UICollectionViewFlowLayout展开的反。而己当GitHub上面上传的吗时有发生一致客继承自UICollectionViewLayout的非流水布局。效果如下,因为原理性的物还多,就不再进行辨析(代码也闹注释)。感兴趣之可就Github上面下载。如果文章被出什么错误或再次好之艺术、建议等等,感谢您的指出。我们一起学习!O(∩_∩)O!

hg0088皇冠 10

后记

本身非会见告诉你,介绍UICollectionView的自定义布局就篇稿子,是自下一个实验的前传。不过最近叫教师强迫帮他们失去描绘文档,估计进度得慢。

相关文章