神刀安全网

手把手教你使用Layout写瀑布流

思路:
0.明确自定义布局的核心方法:layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes],他是用来显示cell的布局的,所有的cell,但是那,这个方法可能多次调用,所以,创建的时候要在prepare方法中写,但是,返回attribute有专门的方法,计算设置attire的各种属性–方法是layoutAttributesForItemAtIndexPath,我们需要啥属性,滴啊用他,然后在prepare获取每一个属性就好

1.继承自UICollectionViewLayout创建一个新的布局对象WFWaterFlowLayout
2.写出数据源方法,给定colletionView这个布局
3.重写WFWaterFlowLayout中的四个方法,显示出基本的样式
4.重构WFWaterFlowLayout方法,让其性能更高
5.计算cell的尺寸,核心计算
6.显示数据
7.对项目的接口在做处理,优化项目

具体实现步骤


1.继承自UICollectionViewLayout创建一个新的布局对象WFWaterFlowLayout
import UIKit  class WFWaterFlowLayout: UICollectionViewLayout {  }
2.写出数据源方法,给定colletionView这个布局
手把手教你使用Layout写瀑布流
在storyBoard上设置colletionView和layout

//MARK : - 数据源方法 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中的四个方法,显示出基本的样式
import UIKit  class WFWaterFlowLayout: UICollectionViewLayout {       /**      *  1.初始化调用的方法      */     override func prepareLayout() {         super.prepareLayout()     }      /**      *  2.决定cell展示布局的数组      */     override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {         return nil     }      /**      *  3.如果你是继承自“UICollectionViewLayout”的话,那么最好实现方法,否则可能出错          该方法的作用是返回当前indexPath位置的布局属性      */     override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {         return nil     }      /**      *  4.当我们继承自"UICollectionViewLayout",那么他是不会滑动的,所以我们要给他设置一个contenSize来确定滑动的范围      */     override func collectionViewContentSize() -> CGSize {         return CGSizeMake(0, 100)     }  }
4.重构WFWaterFlowLayout方法,让其性能更高
  //MARK: - 创建一个数组,用来盛放属性对象     private lazy var attributes = [UICollectionViewLayoutAttributes]()      /**      *  1.初始化调用的方法      */     override func prepareLayout() {         super.prepareLayout()         //每一次调用reload方法,如果数组不删除,那么会越来越多数据,所以我们要去清空         attributes.removeAll()          //2.1 创建含有属性的数组         //流水布局一般是有1组,我们直接获取个数就好         let count = collectionView?.numberOfItemsInSection(0)          for index in 0 ..< count!         {             //2.2 创建位置             let indexPath = NSIndexPath.init(forItem: index, inSection: 0)             //2.3 创建布局属性             let  attri = UICollectionViewLayoutAttributes(forCellWithIndexPath:indexPath)             //2.4 设置属性,给frame一个随机数             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展示布局的数组      */     override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {         return attributes     }      /**      *  3.如果你是继承自“UICollectionViewLayout”的话,那么最好实现方法,否则可能出错          该方法的作用是返回当前indexPath位置的布局属性      */     override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {         return attributes[indexPath.row]     }      /**      *  4.当我们继承自"UICollectionViewLayout",那么他是不会滑动的,所以我们要给他设置一个contenSize来确定滑动的范围      */     override func collectionViewContentSize() -> CGSize {         return CGSizeMake(10, 100)     }

刚才搞错了一个方法let indexPath = NSIndexPath.init(forItem: index, inSection: 0),写错成了let indexPath = NSIndexPath(index:index)一直报错

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}'

一定要注意哈

手把手教你使用Layout写瀑布流
现在的样子

注意:今天没有重写shouldInvalidateLayoutForBoundsChange这个方法,是因为,我们继承的是collectionViewLayout,默认是真,之前调用,是因为继承的是UICollectionViewFlowLayout,设置的是假

5.计算cell的尺寸,计算每一列的高度

步骤
1.获取collectionView的内边距,item之间的间距等
2.计算cell的宽度,随机给他一个高度
3.通过一个数组,保存所有列的高度,用于比较最小的y值和更新 contentSize

定义几个常量

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};
// 每一次更新,我们都要记得删除过去的缓存,重新计算     override func prepareLayout() {         super.prepareLayout()          //流水布局一般是有1组,我们直接获取个数就好         let count = collectionView?.numberOfItemsInSection(0)          //每一次调用reload方法,如果数组不删除,那么会越来越多数据,所以我们要去清空         attributes.removeAll()           /// 1.1 每一次更新,都要先去出缓存的列的高度         colunmsHeightArr .removeAllObjects()          /// 1.2 清除之后,还要给他们一个默认的高度         for _  in 0 ..< count!         {                colunmsHeightArr.addObject(WFEdgeInsets.top)         } }
 /**      *  3.如果你是继承自“UICollectionViewLayout”的话,那么最好实现方法,否则可能出错          该方法的作用是返回当前indexPath位置的布局属性      */     override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {         let  attri = UICollectionViewLayoutAttributes(forCellWithIndexPath:indexPath)          //1.计算frame          //2.4 设置属性,给frame一个随机数                 /// 2.4.1设置x,y值的根据就是讲cell放置到最小的那一列中           /// 保存最短列的列号          var colunmIndex = 0 //默认0         var colunmMinHeight = colunmsHeightArr[colunmIndex] as! CGFloat//默认最短的列高度是第一列         for col in 1..<WFDefaultColunmsNum {             let currentColHeight = (colunmsHeightArr[col] as! CGFloat)             if colunmMinHeight > currentColHeight             {                 colunmMinHeight = currentColHeight                 colunmIndex = col             }         }           //几个间距的和         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)          //更新保存高度的数组         colunmsHeightArr.replaceObjectAtIndex(colunmIndex, withObject: CGRectGetMaxY(attri.frame))          return attri     }
    /**      *  4.当我们继承自"UICollectionViewLayout",那么他是不会滑动的,所以我们要给他设置一个contenSize来确定滑动的范围      */     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)     } }
手把手教你使用Layout写瀑布流
最后的效果

6.设置数据

使用pod,设置框架

platform:ios,'8.0' use_frameworks! pod 'MJRefresh' pod 'SDWebImage' pod 'MJExtension'

1.生成一个cellSFImageCell
2.通过plist文件来加载一个数组的模型 shops = WFShopModel.mj_objectArrayWithFilename("1.plist")
3.设置数据
4.设置上啦刷新,下啦加载
5.根据图片的宽度,设置等比例高度

设置下啦刷新,上啦加载,注意使用的对象,和延迟两秒的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: {             //要延迟几秒,才会有小菊花             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     }
手把手教你使用Layout写瀑布流
加载之后,合并数据的时候还是有问题,是因为我们没有根据图片比例设置宽度

现在去根据图片的比例设置cell 的高度
过去的高度是 let aH = CGFloat(arc4random_uniform(60)) + 30,所以是不对的

在layout勒种天机一个属性

//计算cell高度         let shop = shops?[indexPath.row] as? WFShopModel         var iHeight:CGFloat =  0         if shop != nil {              iHeight =  aW * (shop?.h)!/(shop?.w)!         }         let aH = iHeight

在加载数据的时候,我们都要更新一下shops数组

//layout 是我从storyBoard上拉线过来的,属于colletionView         self.layout.shops = self.shops
手把手教你使用Layout写瀑布流
这就基本写好了

但是,现在的只是能够显示WFShopModel,在项目中,我们称之为,模块,并不能当做开源库使用,因为他的功能太单一。
思考?为毛线UITableView功能那么强大,什么格式都能显示,他们如何做的这么强大?因为有代理和数据源,现在我们看看如何通过代理,给瀑布流拓展成能让所有人使用的开源库


本身可以将所有的方法全部归类到代理中,但是还是决定使用一个数据源方法,更加直观。


先写出来数据源和代理方法,水平有限,google了一些option和必须实现的方法,但是感觉麻烦,就不写了,其实tableView就有必须实现,和可实现的方法,你们自己找吧~

protocol WFWaterFlowLayoutDataSource:NSObjectProtocol{      /**      :param: waterFlowLayout self      :param: width           提供给外边,cell的宽度      :returns:返回来cell 的高度      */     func waterFlowLayout(waterFlowLayout: WFWaterFlowLayout, itemWidth width: CGFloat,indexPath:NSIndexPath) -> CGFloat?     /**      :param: waterFlowLayout self       :returns: 一共几列      */     func columnOfWaterFlowLayout(waterFlowLayout: WFWaterFlowLayout) -> NSInteger? }  protocol WFWaterFlowLayoutDelegate:NSObjectProtocol {     /**      通过代理返回过来colletionView的内边距      :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?  }

定义一个代理变量和数据源变量,以及快速获取变量的值的函数

    weak var dataSource:WFWaterFlowLayoutDataSource?     weak var delegate:WFWaterFlowLayoutDelegate?      //MARK - get 方法,获取具体的数据     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         }     }

然后将那些东西全部替换,实现代理方法和数据源方法

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)     }  }
手把手教你使用Layout写瀑布流
最后变成了这样,实现了高度的自定义话,其实和属性差不多

代码地址

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 手把手教你使用Layout写瀑布流

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址