ios UI基础瀑布流 顾名思义是将界面以瀑布流水般的展现出来,使用瀑布流,首先对数据进行懒加载,传入数据后,使用UIcollectionView控件在main.storyboard里进行简单的布局
![collectionView的item布局]
collectionView已经设置的和屏幕等宽等高了
然后使用collectionViewDataSource代理 从flowLayOut连一根线到View controller,然后遵循协议,实现代理方法
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
//返回组
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
//返回item
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
//创建UICollectionViewCell
注意在这里我们使用的是原型cell,所以不会出现cell不存在的情况,所以不用担心cell重用的问题,因为系统一加载数据就会创建一个cell,接下来我们要获取cell中的imageView和Label,使用[cell viewWithTag:]方法,这个方法返回值是一个UIview,我们要用一个UIView来接收,所有的控件都继承自UIView,因此image用UIImageView来接收,Label用UI label来接收,(是否碰到报错的情况呢?
```_***dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:],*** /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3505.16/UICollectionView.m:3600
*2016-03-11 11:01:47.920 瀑布流[943:32280] Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier haha - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
First throw call stack```
这个错误很简单,想想自己是否将main.storyboard里的UICollectionViewCell中的identifier和你.m中定义的标识是否一致)
接下来运行看一下数据有没有加载到,背景是黑色的很奇怪吗?UICollectionView控件默认的背景就是黑色,不用担心
****
界面搭建完了发现布局不是我们想要的效果,所以,我们将要自定义一个layout,让它继承自UICollectionViewFlowLayOut(为什么不继承自UICollectionViewLayOut呢?是因为UICollectionViewFlowLayOut里的属性UICollectionViewLayOut里面没有)既然是自定义的layout,那么一上来就有6个方法,这是系统的方法不必纠结
-(void)prepareLayout
//在layout运行加载前调用的方法
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
//根据indexPath获取当前控件的属性
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
//返回可视区域的控件属性,程序一运行我们就能看到的
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
//当界面改变的时候,需要重新更新界面的bounds
-(CGSize)collectionViewContentSize
//
-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
//当界面滚动的时候调用的方法,需要做动画处理的方法
写完这6个方法后,
****
接下来 **框架分析**
第一步:就开始分析首先我们考虑 程序一运行我们所看到的界面是改变后的界面,那么在这6个方法里有一个方法是返回可视区域内的控件属性-(NSArray<UICollectionViewLayoutAttributes *>方法,在这里返回的是改变后的item的集合,也就是说在程序运行之前一定调用了一个方法改变之前的item
第二步:第一步中程序运行,在cmd+R的时候模拟器就给我们展现出了这样瀑布流的界面,在layout加载前就调用这个方法就是-(void)prepareLayout方法,在这里首先我们应该遍历每一个原先的item,然后调用根据indexPath获得当前item属性的方法得到改变后的item添加到一个可变数组中,(在这里,遍历原先的item 需要获取原先的collectionView里的item的数量,indexpath索引值通过NSindexPath方法进行获取
第三步:既然要改变item的属性那么看看这6个方法,我们发现有一个根据indexPath获得当前item的属性方法-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath,我们肯定是要在这个方法里做文章,首先想要改变item的属性,那就要先获取原来的item,然后通过计获取的新的item的坐标和尺寸,将原来的item的frame改变。
****
算法分析
第一步:先计算控件的尺寸:
item的宽 = (屏幕的宽度也就是CollectionView的宽度-item之间的最小的间隙*(每一行的item数量-1)-左间距-右间距)/每一行的item的数量
在这里每一行的item的数量我们是不知道的,这个希望别人自己去定义,所以我们希望别人自己定义,那就不能把这个属性定义在.m文件里,所以我们在.h文件中定义这个属性,不排除有得人乱定义,将每一行的item设置为0的情况,所以在.m文件里需要重写一下这个属性的get方法。(如果为0则返回3)
item的高 = 高/宽*宽
(宽在第一步里是已知的,高/宽的比率需要写一个方法来实现)我们在layout里是无法直接知道控件的宽和高的,所以自己的事自己不能直接去做,那么我们就设置一个代理,代理三部曲就不多说了,在这里判断一下什么时候用@optional和@required,还有dataSource和Delegate ,首先看这个方法会不会影响程序运行,有影响则用dataSource,否则用Delegate,必须实现的话就用required否则用optional,套路就不说了,这里说几个注意点,将这个代理在ViewController中用的时候在获取item宽高数据的时候我们发现需要一个indexPath的索引值,我们看回去在FlowLayOut中计算item尺寸的方法里我们有indexPath,所以需要传一个indexPath的索引值,随之代理中也要传一个IndexPath的索引值,在返回item宽高比率的时候别忘记宽高是NSinteger类型,而代理中返回的是CGFloat类型的,所以要进行强转。
根据九宫格算法
item的X = 左边距+当前item的列数*(item的宽+item之间的间隙)
这里唯一不知道的变量是当前item的列数,我们需要去定义一个当前列(*为什么不用indexPath.item(当前控件cell是处于第几组的第几个item)%这一行的控件的个数,虽然也可以获得当前列,但是它使控件的排列方式是第一行排列完紧接着排列第二行,很死板,这样会导致有可能最后的瀑布流看起来很难看*),然后去获取最小的Y值的当前列,然后排列的时候一行排完后下一行按照上一行最小的Y进行排列,变得很灵活,不管出现什么情况最后的图案也是比较好看的,说到这里应该发现我们应该先去计算Y的值,这样才能获取到最小Y的值
定义一个数组去挨个存放每一个控件的Y值,做更新CGRectMaxY得到每一个控件Y的最大值存放在数组中,然后遍历这个存放Y的最大值的数组,定义一个Y值,重写它的get方法,获取到存放最大Y值数组的当前的Y值,然后用定义的Y与获取到的当前的Y值做比较将最小的那个赋值给定义的Y,就得到我们的最小Y ,同时也能得到我们的当前列
item的Y = 最小的Y+列间距
在这里别忘记要更新存放在数组中的最大的Y值!!!
最后设置滚动范围
容易遇到的不报错Bug:1.定义的数组没有进行初始化操作 2.遍历的时候该遍历谁要明确3.更新Y值的时候,注意将CGFloat转换成NSString类型 4.实现代理方法中记得将NSInteger强转。