思路:
0.明確自定義布局的核心方法:layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]
,他是用來(lái)顯示cell的布局的惧浴,所有的cell妹田,但是那稽屏,這個(gè)方法可能多次調(diào)用欠动,所以镐躲,創(chuàng)建的時(shí)候要在prepare
方法中寫骆捧,但是权均,返回attribute
有專門的方法顿膨,計(jì)算設(shè)置attire的各種屬性--方法是layoutAttributesForItemAtIndexPath
,我們需要啥屬性,滴啊用他叽赊,然后在prepare
獲取每一個(gè)屬性就好
1.繼承自
UICollectionViewLayout
創(chuàng)建一個(gè)新的布局對(duì)象WFWaterFlowLayout
2.寫出數(shù)據(jù)源方法恋沃,給定colletionView
這個(gè)布局
3.重寫WFWaterFlowLayout
中的四個(gè)方法,顯示出基本的樣式
4.重構(gòu)WFWaterFlowLayout
方法必指,讓其性能更高
5.計(jì)算cell的尺寸囊咏,核心計(jì)算
6.顯示數(shù)據(jù)
7.對(duì)項(xiàng)目的接口在做處理,優(yōu)化項(xiàng)目
具體實(shí)現(xiàn)步驟
1.繼承自UICollectionViewLayout
創(chuàng)建一個(gè)新的布局對(duì)象WFWaterFlowLayout
import UIKit
class WFWaterFlowLayout: UICollectionViewLayout {
}
2.寫出數(shù)據(jù)源方法取劫,給定colletionView
這個(gè)布局
//MARK : - 數(shù)據(jù)源方法
extension WFViewController:UICollectionViewDataSource{
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 50
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(SFImageCellIdent, forIndexPath: indexPath)
return cell
}
}
3.重寫WFWaterFlowLayout中的四個(gè)方法,顯示出基本的樣式
import UIKit
class WFWaterFlowLayout: UICollectionViewLayout {
/**
* 1.初始化調(diào)用的方法
*/
override func prepareLayout() {
super.prepareLayout()
}
/**
* 2.決定cell展示布局的數(shù)組
*/
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return nil
}
/**
* 3.如果你是繼承自“UICollectionViewLayout”的話研侣,那么最好實(shí)現(xiàn)方法谱邪,否則可能出錯(cuò)
該方法的作用是返回當(dāng)前indexPath位置的布局屬性
*/
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
return nil
}
/**
* 4.當(dāng)我們繼承自"UICollectionViewLayout",那么他是不會(huì)滑動(dòng)的,所以我們要給他設(shè)置一個(gè)contenSize來(lái)確定滑動(dòng)的范圍
*/
override func collectionViewContentSize() -> CGSize {
return CGSizeMake(0, 100)
}
}
4.重構(gòu)WFWaterFlowLayout方法庶诡,讓其性能更高
//MARK: - 創(chuàng)建一個(gè)數(shù)組惦银,用來(lái)盛放屬性對(duì)象
private lazy var attributes = [UICollectionViewLayoutAttributes]()
/**
* 1.初始化調(diào)用的方法
*/
override func prepareLayout() {
super.prepareLayout()
//每一次調(diào)用reload方法,如果數(shù)組不刪除,那么會(huì)越來(lái)越多數(shù)據(jù)扯俱,所以我們要去清空
attributes.removeAll()
//2.1 創(chuàng)建含有屬性的數(shù)組
//流水布局一般是有1組书蚪,我們直接獲取個(gè)數(shù)就好
let count = collectionView?.numberOfItemsInSection(0)
for index in 0 ..< count!
{
//2.2 創(chuàng)建位置
let indexPath = NSIndexPath.init(forItem: index, inSection: 0)
//2.3 創(chuàng)建布局屬性
let attri = UICollectionViewLayoutAttributes(forCellWithIndexPath:indexPath)
//2.4 設(shè)置屬性,給frame一個(gè)隨機(jī)數(shù)
let aX = CGFloat(arc4random_uniform(300))
let aY = CGFloat(arc4random_uniform(300))
let aW = CGFloat(arc4random_uniform(300))
let aH = CGFloat(arc4random_uniform(300))
attri.frame = CGRectMake( aX, aY, aW, aH)
attributes.append(attri)
}
}
/**
* 2.決定cell展示布局的數(shù)組
*/
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return attributes
}
/**
* 3.如果你是繼承自“UICollectionViewLayout”的話迅栅,那么最好實(shí)現(xiàn)方法殊校,否則可能出錯(cuò)
該方法的作用是返回當(dāng)前indexPath位置的布局屬性
*/
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
return attributes[indexPath.row]
}
/**
* 4.當(dāng)我們繼承自"UICollectionViewLayout",那么他是不會(huì)滑動(dòng)的,所以我們要給他設(shè)置一個(gè)contenSize來(lái)確定滑動(dòng)的范圍
*/
override func collectionViewContentSize() -> CGSize {
return CGSizeMake(10, 100)
}
剛才搞錯(cuò)了一個(gè)方法
let indexPath = NSIndexPath.init(forItem: index, inSection: 0)
读存,寫錯(cuò)成了let indexPath = NSIndexPath(index:index)
一直報(bào)錯(cuò)
2016-09-16 14:33:08.890 WaterFlow[2721:225067] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView received layout attributes for a cell with an index path that does not exist: <NSIndexPath: 0x7febfa61c680> {length = 1, path = 0}'
一定要注意哈
注意:今天沒有重寫
shouldInvalidateLayoutForBoundsChange
這個(gè)方法为流,是因?yàn)椋覀兝^承的是collectionViewLayout
让簿,默認(rèn)是真敬察,之前調(diào)用,是因?yàn)槔^承的是UICollectionViewFlowLayout
尔当,設(shè)置的是假
5.計(jì)算cell的尺寸莲祸,計(jì)算每一列的高度
步驟
1.獲取collectionView
的內(nèi)邊距,item之間的間距等
2.計(jì)算cell的寬度椭迎,隨機(jī)給他一個(gè)高度
3.通過(guò)一個(gè)數(shù)組锐帜,保存所有列的高度,用于比較最小的y值和更新contentSize
定義幾個(gè)常量
let WFVerticalMargin:CGFloat = 10
let WFHorMargin:CGFloat = 10
let WFEdgeInsets:UIEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10)
//oc中這寫
/** 邊緣間距 */
static const UIEdgeInsets WFDefaultEdgeInsets = {10, 10, 10, 10};
// 每一次更新侠碧,我們都要記得刪除過(guò)去的緩存抹估,重新計(jì)算
override func prepareLayout() {
super.prepareLayout()
//流水布局一般是有1組,我們直接獲取個(gè)數(shù)就好
let count = collectionView?.numberOfItemsInSection(0)
//每一次調(diào)用reload方法弄兜,如果數(shù)組不刪除药蜻,那么會(huì)越來(lái)越多數(shù)據(jù),所以我們要去清空
attributes.removeAll()
/// 1.1 每一次更新替饿,都要先去出緩存的列的高度
colunmsHeightArr .removeAllObjects()
/// 1.2 清除之后语泽,還要給他們一個(gè)默認(rèn)的高度
for _ in 0 ..< count!
{
colunmsHeightArr.addObject(WFEdgeInsets.top)
}
}
/**
* 3.如果你是繼承自“UICollectionViewLayout”的話,那么最好實(shí)現(xiàn)方法视卢,否則可能出錯(cuò)
該方法的作用是返回當(dāng)前indexPath位置的布局屬性
*/
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let attri = UICollectionViewLayoutAttributes(forCellWithIndexPath:indexPath)
//1.計(jì)算frame
//2.4 設(shè)置屬性踱卵,給frame一個(gè)隨機(jī)數(shù)
/// 2.4.1設(shè)置x,y值的根據(jù)就是講cell放置到最小的那一列中
/// 保存最短列的列號(hào)
var colunmIndex = 0 //默認(rèn)0
var colunmMinHeight = colunmsHeightArr[colunmIndex] as! CGFloat//默認(rèn)最短的列高度是第一列
for col in 1..<WFDefaultColunmsNum {
let currentColHeight = (colunmsHeightArr[col] as! CGFloat)
if colunmMinHeight > currentColHeight
{
colunmMinHeight = currentColHeight
colunmIndex = col
}
}
//幾個(gè)間距的和
let totalMagin = CGFloat(WFDefaultColunmsNum - 1)*WFHorMargin
let aW = (WFScreenWidth - WFEdgeInsets.left - WFEdgeInsets.right - totalMagin)/CGFloat(WFDefaultColunmsNum)
let aH = CGFloat(arc4random_uniform(60)) + 30
let aX = WFEdgeInsets.left + CGFloat(colunmIndex) * (WFHorMargin + aW)
var aY = colunmMinHeight + WFVerticalMargin
if aY != WFEdgeInsets.top {
aY = aY + WFVerticalMargin
}
attri.frame = CGRectMake( aX, aY, aW, aH)
//更新保存高度的數(shù)組
colunmsHeightArr.replaceObjectAtIndex(colunmIndex, withObject: CGRectGetMaxY(attri.frame))
return attri
}
/**
* 4.當(dāng)我們繼承自"UICollectionViewLayout",那么他是不會(huì)滑動(dòng)的据过,所以我們要給他設(shè)置一個(gè)contenSize來(lái)確定滑動(dòng)的范圍
*/
override func collectionViewContentSize() -> CGSize {
var maxY = colunmsHeightArr[0] as! CGFloat
for col in 1..<colunmsHeightArr.count {
let currentColHeight = (colunmsHeightArr[col] as! CGFloat)
if maxY < currentColHeight
{
maxY = currentColHeight
}
}
return CGSizeMake(WFScreenWidth, maxY + WFEdgeInsets.bottom)
}
}
6.設(shè)置數(shù)據(jù)
使用pod惋砂,設(shè)置框架
platform:ios,'8.0'
use_frameworks!
pod 'MJRefresh'
pod 'SDWebImage'
pod 'MJExtension'
1.生成一個(gè)
cell
-SFImageCell
2.通過(guò)plist文件來(lái)加載一個(gè)數(shù)組的模型shops = WFShopModel.mj_objectArrayWithFilename("1.plist")
3.設(shè)置數(shù)據(jù)
4.設(shè)置上啦刷新,下啦加載
5.根據(jù)圖片的寬度绳锅,設(shè)置等比例高度
設(shè)置下啦刷新西饵,上啦加載,注意使用的對(duì)象鳞芙,和延遲兩秒的GCD用法
private func setupRefreshView(){
collectionView.mj_header = MJRefreshNormalHeader.init(refreshingBlock: {
self.shops.removeAllObjects()
let data = WFShopModel.mj_objectArrayWithFilename("1.plist")
self.shops.addObjectsFromArray(data as [AnyObject])
self.collectionView.reloadData()
self.collectionView.mj_header.endRefreshing()
})
collectionView.mj_footer = MJRefreshAutoNormalFooter.init(refreshingBlock: {
//要延遲幾秒眷柔,才會(huì)有小菊花
let time: NSTimeInterval = 2.0
let delay = dispatch_time(DISPATCH_TIME_NOW,
Int64(time * Double(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_main_queue()) {
let data = WFShopModel.mj_objectArrayWithFilename("1.plist")
self.shops .addObjectsFromArray(data as [AnyObject])
self.collectionView.reloadData();
self.collectionView.mj_footer.endRefreshing()
}
});
collectionView.mj_header.beginRefreshing()
self.collectionView.mj_footer.hidden = false
}
現(xiàn)在去根據(jù)圖片的比例設(shè)置cell 的高度
過(guò)去的高度是let aH = CGFloat(arc4random_uniform(60)) + 30
驯嘱,所以是不對(duì)的
在layout勒種天機(jī)一個(gè)屬性
//計(jì)算cell高度
let shop = shops?[indexPath.row] as? WFShopModel
var iHeight:CGFloat = 0
if shop != nil {
iHeight = aW * (shop?.h)!/(shop?.w)!
}
let aH = iHeight
在加載數(shù)據(jù)的時(shí)候镶苞,我們都要更新一下shops數(shù)組
//layout 是我從storyBoard上拉線過(guò)來(lái)的,屬于colletionView
self.layout.shops = self.shops
但是鞠评,現(xiàn)在的只是能夠顯示
WFShopModel
,在項(xiàng)目中茂蚓,我們稱之為,模塊谢澈,并不能當(dāng)做開源庫(kù)使用煌贴,因?yàn)樗墓δ芴珕我弧?br> 思考?為毛線UITableView
功能那么強(qiáng)大锥忿,什么格式都能顯示牛郑,他們?nèi)绾巫龅倪@么強(qiáng)大?因?yàn)橛写砗蛿?shù)據(jù)源敬鬓,現(xiàn)在我們看看如何通過(guò)代理淹朋,給瀑布流拓展成能讓所有人使用的開源庫(kù)
本身可以將所有的方法全部歸類到代理中,但是還是決定使用一個(gè)數(shù)據(jù)源方法钉答,更加直觀础芍。
先寫出來(lái)數(shù)據(jù)源和代理方法,水平有限数尿,google了一些
option
和必須實(shí)現(xiàn)的方法仑性,但是感覺麻煩,就不寫了右蹦,其實(shí)tableView
就有必須實(shí)現(xiàn)诊杆,和可實(shí)現(xiàn)的方法,你們自己找吧~
protocol WFWaterFlowLayoutDataSource:NSObjectProtocol{
/**
:param: waterFlowLayout self
:param: width 提供給外邊何陆,cell的寬度
:returns:返回來(lái)cell 的高度
*/
func waterFlowLayout(waterFlowLayout: WFWaterFlowLayout, itemWidth width: CGFloat,indexPath:NSIndexPath) -> CGFloat?
/**
:param: waterFlowLayout self
:returns: 一共幾列
*/
func columnOfWaterFlowLayout(waterFlowLayout: WFWaterFlowLayout) -> NSInteger?
}
protocol WFWaterFlowLayoutDelegate:NSObjectProtocol {
/**
通過(guò)代理返回過(guò)來(lái)colletionView的內(nèi)邊距
:param: waterFlowLayout self
*/
func marginOfSectionInsert(waterFlowLayout: WFWaterFlowLayout) -> UIEdgeInsets?
/**
:param: waterFlowLayout self
:returns: 返回item之間豎直間距
*/
func itemVerticalMargin(waterFlowLayout: WFWaterFlowLayout) -> CGFloat?
/**
:param: waterFlowLayout self
:returns: 返回item之間水平的間距
*/
func itemHorMargin(waterFlowLayout: WFWaterFlowLayout) -> CGFloat?
}
定義一個(gè)代理變量和數(shù)據(jù)源變量晨汹,以及快速獲取變量的值的函數(shù)
weak var dataSource:WFWaterFlowLayoutDataSource?
weak var delegate:WFWaterFlowLayoutDelegate?
//MARK - get 方法,獲取具體的數(shù)據(jù)
private func verticalMarign() -> CGFloat
{
if ((delegate?.itemVerticalMargin(self)) != nil)
{
return (delegate?.itemVerticalMargin(self))!
} else{
return WFVerticalMargin
}
}
private func horMargin() -> CGFloat
{
if ((delegate?.itemHorMargin(self)) != nil) {
return (delegate?.itemHorMargin(self))!
}else{
return WFHorMargin
}
}
private func sectionInset() -> UIEdgeInsets{
if ((delegate?.marginOfSectionInsert(self)) != nil) {
return (delegate?.marginOfSectionInsert(self))!
}else{
return WFEdgeInsets
}
}
private func numberOfSection() -> NSInteger{
if ((dataSource?.columnOfWaterFlowLayout(self)) != nil) {
return (dataSource?.columnOfWaterFlowLayout(self))!
}else{
return WFDefaultColunmsNum
}
}
然后將那些東西全部替換,實(shí)現(xiàn)代理方法和數(shù)據(jù)源方法
extension WFViewController:WFWaterFlowLayoutDataSource,WFWaterFlowLayoutDelegate{
func waterFlowLayout(waterFlowLayout: WFWaterFlowLayout,
itemWidth width: CGFloat,
indexPath: NSIndexPath) -> CGFloat? {
let shop = shops[indexPath.row] as! WFShopModel
return width / (shop.w/shop.h)
}
func itemHorMargin(waterFlowLayout: WFWaterFlowLayout) -> CGFloat? {
return 20
}
func itemVerticalMargin(waterFlowLayout: WFWaterFlowLayout) -> CGFloat? {
return 30
}
func columnOfWaterFlowLayout(waterFlowLayout: WFWaterFlowLayout) -> NSInteger? {
return 3
}
func marginOfSectionInsert(waterFlowLayout: WFWaterFlowLayout) -> UIEdgeInsets? {
return UIEdgeInsetsMake(12, 34, 10, 20)
}
}