美文网首页花落√莫相思
63 - Swift之瀑布流(UICollectionView)

63 - Swift之瀑布流(UICollectionView)

作者: NetWork小贱 | 来源:发表于2017-08-24 16:15 被阅读44次

    一、简介

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

    二、 瀑布流的实现

    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、四列的情况下显示

    4.gif

    相关文章

      网友评论

        本文标题:63 - Swift之瀑布流(UICollectionView)

        本文链接:https://www.haomeiwen.com/subject/ehhedxtx.html