63 - Swift之瀑布流(UICollectionView)

一、简介

瀑布流:又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。

二、 瀑布流的实现

1、瀑布流的实现选择

瀑布流的实现可以使用 UITableView 或 UICollectionView 或 UIScrollView 等来实现瀑布流。注意: UITableView 和 UICollectionView 两个在实现瀑布流时相比 UIScrollView 实现瀑布流较为简单。同时 UICollectionView 在实现瀑布流时要比 UITableView 时 Item的样式多样和流动效果也更好。

2、 瀑布流的实现准备

根据各个实现瀑布流的控件的对比,我们选择 UICollectionView 来实现。选择 UICollectionView 就需要自定义流动布局,我们要创建一个布局类继承与 UICollectionViewFlowLayout 。同时要重写UICollectionViewFlowLayout 的下面四个方法:

  • override func prepare() ===》: 作用是重新更改Item的布局。但是在重新更改之前要首先调用 父类的该方法。

  • override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? ==》: 返回每一个 Item 的 LayoutAttribute 。

  • override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? ==》: 返回indexPath位置的 Item的LayoutAttributes

  • override var collectionViewContentSize: CGSize ===》 : 返回 collectionView 的滑动范围

3、 瀑布流的布局重写

1、 定义一些变量
    // 创建一个代理
    var delegate:WaterfallViewFlowLayoutDelegate?
    // 瀑布流的列数
    var WatefallColumns = 2
    // 列间距
    var ColumnsSpacing:CGFloat = 10.0
    // 行间距
    var LineSpacing:CGFloat = 10.0
    // 创建存放当前加载的Cell 的布局属性
    var cellLayoutAttributes : NSMutableArray?
    // 创建存放Cell 所在列的高度
    var cellColumnsHeights : NSMutableArray?
    // cell 内容的高度
    var contentHeight:CGFloat?

2、 func prepare() 方法的重写
// MARK: 重写初始化方法
override func prepare() {
    // 调用父类方法
    super.prepare()
    // MARK: 初始化一些数据
    // TODO: 内容的高度
    contentHeight = 0.0
    // TODO: 数组初始化
    cellColumnsHeights = NSMutableArray.init(capacity: 0)
    cellLayoutAttributes = NSMutableArray.init(capacity: 0)
    // TODO: 我要初始化每列高度的初始值
    for _ in 0 ..< WatefallColumns {
        cellColumnsHeights?.add(self.sectionInset.top)
    }
    // TODO: 获取当前加载的Cell个数
    let loadCellCount = self.collectionView?.numberOfItems(inSection: 0)
    // TODO: 遍历获取的Cell得到每个Cell的LayoutAttributes,并存放到 cellLayoutAttributes 里面
    for i in 0 ..< loadCellCount! {
        // TODO: 获取Cell的位置
        let indexPath = IndexPath.init(row: i, section: 0)
        // TODO: 获取Cell的LayoutAttributes 
        let cellAttribute = self.layoutAttributesForItem(at: indexPath)
        // TODO: 将获取的 cellAttribute 添加到 cellLayoutAttributes
        cellLayoutAttributes?.add(cellAttribute!)
    }
    
}
3、 override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? 方法的重写
// MARK: 设置Cell的位置
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    // TODO: 获取Cell的宽度
    let cellWidth = ((self.collectionView?.bounds.width)! - LineSpacing * CGFloat(WatefallColumns - 1)-self.sectionInset.left - self.sectionInset.right) / CGFloat(WatefallColumns)
    // TODO: 获取Cell的高度
    let cellHight = delegate?.waterFlowLayout(waterFlowLayout: self, indexPath: indexPath ,width: cellWidth);
    // TODO: 默认cellColumnsHeights的第一个对象是高度最低的Cell
    var minColumnsCellHeight = cellColumnsHeights?.firstObject as! CGFloat
    // TODO: 标记第几列是Cell 最低列
    var minColumnCellMark = 0
    // TODO: 遍历每一列的Cell高度,获取得到最小的一个
    for i in 0 ..< WatefallColumns {
        let tempCellHeight = cellColumnsHeights?[i] as! CGFloat
        if minColumnsCellHeight > tempCellHeight {
            minColumnsCellHeight = tempCellHeight
            minColumnCellMark = i
        }
    }
    // TODO: 最低Cell的X轴的位置
    let minCellHeightX =  CGFloat(minColumnCellMark) * (cellWidth + ColumnsSpacing) + self.sectionInset.left
    // TODO: 最低Cell的Y轴的位置
    var cellHeightY = minColumnsCellHeight
    if cellHeightY != self.sectionInset.top {
         cellHeightY += LineSpacing
    }
    // TODO: 设置大小
    let LayoutAttribute = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
    LayoutAttribute.frame = CGRect.init(x: minCellHeightX, y: cellHeightY, width: cellWidth, height: cellHight!)
    // TODO: 设置Cell 高度中,最低的Y轴位置
    cellColumnsHeights?[minColumnCellMark] = LayoutAttribute.frame.maxY
    // TODO: 获取Cell高度数组最小的一个
    let minCellHeightY = cellColumnsHeights?[minColumnCellMark] as! CGFloat
    if contentHeight! < minCellHeightY {
        contentHeight = minCellHeightY
    }
    return LayoutAttribute
}
4、override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? 的方法的重写
// MARK: 返回样式
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return (cellLayoutAttributes as! [UICollectionViewLayoutAttributes])
}
5、override var collectionViewContentSize: CGSize 的方法的重写
// MARK: 设置Cell可滑动的范围。注意:swift3.0废弃了上面这个方法,所以我们改成重写collectionViewContentSize属性
override var collectionViewContentSize: CGSize {
    get {
        return CGSize.init(width: (self.collectionView?.frame.width)!, height: self.maxH(cellHeight: cellColumnsHeights!))
    }
    set {
        self.collectionViewContentSize = newValue
    }
}
6、func maxH(cellHeight:NSMutableArray) -> CGFloat 函数的实现
// TODO: 获取Cell的高度
func maxH(cellHeight:NSMutableArray) -> CGFloat {
    var max = cellHeight.firstObject as! CGFloat
    for i in 0 ..< cellHeight.count {
        if max < (cellHeight[i] as! CGFloat) {
            max = cellHeight[i] as! CGFloat
        }
    }
    return max + self.sectionInset.bottom
}
7、 WaterfallViewFlowLayoutDelegate 的代理的声明
// 创建代理
protocol WaterfallViewFlowLayoutDelegate: NSObjectProtocol {
    // 获取内容的高度
    func waterFlowLayout(waterFlowLayout:WaterfallViewFlowLayout,indexPath: IndexPath,width:CGFloat) -> CGFloat ;
}

三 、 瀑布流的实现

1、 plist 文件的加载获取数据

// MARK: 获取展示的数据
func getShowData() -> Void {
    dataSource = NSArray.init()
    let plist = Bundle.main.path(forResource: "loadData", ofType: "plist", inDirectory: nil)
    dataSource = NSArray.init(contentsOfFile: plist!)
}

2、 UICollecionView 的创建 (重点)

// MARK: 创建CollectionView
func createCollectionView() -> Void {
    // TODO: 设置布局对象
    let flowLayout = WaterfallViewFlowLayout.init()
    // TODO: 设置有多少列
    flowLayout.WatefallColumns = 1
    // TODO: 设置代理
    flowLayout.delegate = self
    // TODO: 设置Section的偏移
    flowLayout.sectionInset = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)
    // TODO: 设置滑动的方向
    flowLayout.scrollDirection = .vertical
    // TODO: 创建 CollectionView 对象
    let collectionView = UICollectionView.init(frame: self.view.frame, collectionViewLayout: flowLayout)
    collectionView.backgroundColor = UIColor.white
    collectionView.delegate = self
    collectionView.dataSource = self
    // TODO:注册 Cell
    collectionView .register(UICollectionViewCell.self, forCellWithReuseIdentifier: "NetWork小贱")
    self.view.addSubview(collectionView)
}

3、 func waterFlowLayout(waterFlowLayout: WaterfallViewFlowLayout, indexPath: IndexPath, width: CGFloat) 函数的实现

// MARK: 代理事件
func waterFlowLayout(waterFlowLayout: WaterfallViewFlowLayout, indexPath: IndexPath, width: CGFloat) -> CGFloat {
    // TODO: 获取数据对象
    let dict = dataSource![indexPath.row] as! NSDictionary
    // TODO: 获取图像数据,为获取高度做准备
    let image = UIImage.init(named: dict["image"] as! String)
    // TODO: 计算各个元素的高度(图像高 + 标题高 + 内容高)
    return self.getImageHeight(image: image!, width:width) + self.getTextHeight(param: dict["title"] as! String, width: width, fontSize: 18) + self.getTextHeight(param: dict["content"] as! String, width: width, fontSize: 10)
}

4、 UICollectionViewCell 的布局创建

// TODO: UICollectionViewCell的创建
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    // TODO: 获取Cell
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NetWork小贱", for: indexPath)
    // TODO: 设置Cell 的背景色
    cell.contentView.backgroundColor = UIColor.white
    // TODO: 防止Cell 内的元素复用
    for item in cell.contentView.subviews {
        item.removeFromSuperview()
    }
    // TODO: 获取数据
    let dict = dataSource![indexPath.row] as! NSDictionary
    let image = UIImage.init(named: dict["image"] as! String)
    // TODO: 创建图像对象
    let imageView = UIImageView.init(frame: CGRect.init(x: 0, y: 0, width: cell.contentView.bounds.width, height: self.getImageHeight(image: image!, width: cell.contentView.bounds.width)))
    imageView.tag = 100
    imageView.image = image
    cell.contentView.addSubview(imageView)
    
    // 添加标题
    let TitleLable = UILabel.init(frame: CGRect.init(x: 0, y: imageView.frame.maxY, width: cell.contentView.bounds.size.width, height: self.getTextHeight(param: dict["title"] as! String, width: cell.contentView.bounds.width, fontSize: 18)))
    TitleLable.text = (dict["title"] as! String)
    TitleLable.font = UIFont.systemFont(ofSize: 18)
    TitleLable.numberOfLines = 0
    cell.contentView.addSubview(TitleLable)
    
    // 添加内容
    let contentLable = UILabel.init(frame: CGRect.init(x: 0, y: TitleLable.frame.maxY, width: cell.contentView.bounds.width, height: self.getTextHeight(param: dict["content"] as! String, width: cell.contentView.bounds.width, fontSize: 10)))
    contentLable.text = dict["content"] as? String
    contentLable.font = UIFont.systemFont(ofSize: 10)
    contentLable.numberOfLines = 0
    cell.contentView.addSubview(contentLable)
    return cell
    
    
}

5、 UICollectionViewCell 的选择处理(处理对象放大显示)

// TODO: 选择的是哪个Cell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    // 获取选中的Cell
    let selecdCell = collectionView.cellForItem(at: indexPath)
    let imageView = selecdCell?.viewWithTag(100) as! UIImageView
    // 获取图像在 self.view 上的位置
    let rect = selecdCell!.convert(imageView.bounds, to: view)
    // 创建图像点击放大的对象
    let tempView = WaterfallZoomComponentsView.init(frame: self.view.frame)
    tempView.waterfallZoom(initialFrame: rect, image: imageView.image!)
    self.view.addSubview(tempView)
}

6、 获取图像高度的函数和文本高度的函数

1、 图像的高度的获取
// MARK: 获取图像的高度
func getImageHeight(image:UIImage,width:CGFloat) -> CGFloat {
     // 获取图像对象的宽高比
     let aspectRatio = image.size.height / image.size.width
     return aspectRatio * width
}
2、文本高度的获取
// MARK: 获取文本的高度
func getTextHeight(param:String,width:CGFloat,fontSize:CGFloat) -> CGFloat {
    let str = param as NSString
    let textSize = str.boundingRect(with: CGSize.init(width: width, height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: fontSize),NSForegroundColorAttributeName:UIColor.black], context: nil)
    return textSize.height
}

四、图像点击放大的实现

UICollectionView 中的Cell 点击放大。我们的实现是使用一个View 和 一个 UIImageView 来展示的。所以,我们要创建一个继承与UIView的类 WaterfallZoomComponentsView

WaterfallZoomComponentsView 类的一些方法的实现

1、 override init(frame: CGRect) 方法的重写

// MARK: 从写初始化方法
override init(frame: CGRect) {
    super.init(frame: frame)
    self.backgroundColor = UIColor.init(colorLiteralRed: 0, green: 0, blue: 0, alpha: 0.5)
    self.imageView = UIImageView.init(frame: CGRect.zero)
    self.imageView.isUserInteractionEnabled = true
    self.addSubview(self.imageView)
    // 添加一个手势
    let tapGestureRecognizer = UITapGestureRecognizer.init(target: self, action: #selector(disMiss))
    self.imageView.addGestureRecognizer(tapGestureRecognizer)
}

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

2、 func waterfallZoom(initialFrame:CGRect ,image:UIImage ) -> Void 方法的实现

// MARK: 点击图像,原地放大显示图像
func waterfallZoom(initialFrame:CGRect ,image:UIImage ) -> Void {
    // 设置图像在原始View上的位置
    self.imageView.frame = initialFrame
    self.imageView.image = image
    // 创建图像变化后的宽与高的变量
    var imageHeight :CGFloat
    var imageWidth :CGFloat
    // 判断图像是横向还是竖向的
    if image.size.width / image.size.height > self.bounds.width / self.bounds.height {
        // 判断图像的实际宽度与 self的实际宽度对比
        if image.size.width > self.bounds.width {
            // 获取现在的self 的中的高度
            imageHeight = image.size.height / image.size.width * self.bounds.width
            self.changeRect = CGRect.init(x: 0, y: (self.bounds.height - imageHeight)/2, width: self.bounds.width, height: imageHeight)
        }else{
            self.changeRect = CGRect.init(x: (self.bounds.width - image.size.width)/2, y: (self.bounds.height - image.size.height)/2, width: image.size.width, height: image.size.height)
        }
    } else{
        // 判断图像的实际高度与self的实际高度对比
        if image.size.height > self.bounds.height {
            imageWidth = self.bounds.height * image.size.width / image.size.height
            self.changeRect = CGRect.init(x: (self.bounds.width - imageWidth)/2, y: 0, width: imageWidth, height: self.bounds.height)
        }else{
            self.changeRect = CGRect.init(x: (self.bounds.width - image.size.width)/2, y: (self.bounds.height - image.size.height)/2, width: image.size.width, height: image.size.height)
        }
    }

    // 大小变化的过程动画
    UIView.animate(withDuration: 1.0) {
        self.imageView.frame = self.changeRect
    }
}

3 、手势方法的实现

// MARK: 清除
func disMiss() -> Void {
    self.removeFromSuperview()
}

五、 本Demo的效果展示

1、 只有一列的显示

1.gif

2、 两列的情况下显示

2.gif

3、 三列的情况下显示

3.gif

4、四列的情况下显示

63 - Swift之瀑布流(UICollectionView)_第1张图片
4.gif

你可能感兴趣的:(63 - Swift之瀑布流(UICollectionView))