美文网首页iOS学习Swift学习
Swift瀑布流展示/切换简书列表数据

Swift瀑布流展示/切换简书列表数据

作者: 生无可恋的程序员 | 来源:发表于2018-08-16 15:22 被阅读102次

    上篇文章介绍了HTML解析数据存入模型,今天我们的任务是将把之前拿到的数据可视化展示出来。因为组数据的的标题和摘要的文字多少不一,每个 item高度自然就不一样,我又想用collocationView展示两列数据,首先想到的就是瀑布流。现在很多移动端H5就是用瀑布流展示的,比如花瓣、Pinterest(需要梯子)。

    1 - 花瓣布局
    2 - PinterestAPP布局
    本文采用类似花瓣的布局给大家分享,先上一张最终效果图:
    3 - 最终效果图
    因为不管是OC还是Swift中UICollocationView的UICollectionViewFlowLayout默认都是只能设置统一的布局的,要做到如效果图一样的效果就需要重写UICollectionViewFlowLayout。

    1 - 分析原理

    首先我们迫切需要知道的是每个item高度是多少如何布局。上篇文章我们已经通过HTML解析到的数据计算出了高度,接下来就要通过制定一个规则,让每个item加在特定一列上。假如我们设定有两列数据,很显然我们不能通过列表数组的index的奇偶来确定它们的位置。因为假设数组arr的偶数下标值arr[i]的高度值都很大,奇数下标值arr[i]的高度值都很小,所以通过奇偶布局会出现第一列会比第2列高出很多,第二列下面会留很大的空白,这样肯定是不行。


    4 - 按数组下标从左至右从上到下可能出现的情况

    现在给出一种思路:数据数组findList传入自定义的Layout类,定义一个列高数组columnHeight记录每一列的总高度,然后定义一个记录每一列的总item个数的数组columnItemCount,最后定义一个元素类型为UICollectionViewLayoutAttributes的数组attributesArray此数组就是最终item布局的数组。遍历数据数组findList,拿到当前位置的IndexPath初始化一个UICollectionViewLayoutAttributes对象attributes,找到最短列,把数据追加在最短列,累加高度在columnHeight中的最短列,columnItemCount中最短列的个数+1,拿到前面的数据可以计算每个item的frame,最后把整个对象追加在布局数组attributesArray中。循环结束后,拿到最高列的高度减去最高列每个item间的间隙除以最高列的个数可以计算出平均值,这个值用来设置itemSize的高。最后把attributesArray赋值给self.layoutAttributesArray就可以了。如果要加头部和尾部就把头部尾部的布局数据插入头尾即可。大家可能看的很懵,直接上代码,注释很详细了 :

     //
    //  FindFlowLayout.swift
    //  SwiftApp
    //
    //  Created by leeson on 2018/7/4.
    //  Copyright © 2018年 李斯芃 ---> 512523045@qq.com. All rights reserved.
    //
    
    import UIKit
    
    class HomeFlowLayout: UICollectionViewFlowLayout {
        // 总列数
        var columnCount:Int = 0
        // 数据数组
        var findList = [JianshuModel]()
        // 整个webview的高度
        private var maxH:Int?
        // 头部高度
        var headerH:CGFloat = 100
        //所有item的属性
        fileprivate var layoutAttributesArray = [UICollectionViewLayoutAttributes]()
        
        override func prepare() {
            let contentWidth:CGFloat = (self.collectionView?.bounds.size.width)! - self.sectionInset.left - self.sectionInset.right
            let marginX = self.minimumInteritemSpacing
            let itemWidth = (contentWidth - marginX * CGFloat(self.columnCount - 1)) / CGFloat.init(self.columnCount)
            self.computeAttributesWithItemWidth(CGFloat(itemWidth))
        }
        
        ///根据itemWidth计算布局属性
        func computeAttributesWithItemWidth(_ itemWidth:CGFloat){
            
            // 定义一个列高数组 记录每一列的总高度
            var columnHeight = [Int](repeating: Int(self.sectionInset.top + self.headerH), count: self.columnCount)
            // 定义一个记录每一列的总item个数的数组
            var columnItemCount = [Int](repeating: 0, count: self.columnCount)
            var attributesArray = [UICollectionViewLayoutAttributes]()
            
            // 添加头部属性
            let headerAttr:UICollectionViewLayoutAttributes = UICollectionViewLayoutAttributes.init(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: IndexPath.init(item: 0, section: 0))
            headerAttr.frame = CGRect(x: 0, y: CGFloat(0), width: self.collectionView!.bounds.size.width, height: self.headerH)
            attributesArray.append(headerAttr)
            // 给属性数组设置数值
            //self.layoutAttributesArray = attributesArray
            
            // 遍历数据计算每个item的属性并布局
            var index = 0
            for data in self.findList {
                
                let indexPath = IndexPath.init(item: index, section: 0)
                let attributes = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
                // 找出最短列号
                let minHeight:Int = columnHeight.sorted().first!
                let column = columnHeight.index(of: minHeight)
                // 数据追加在最短列
                columnItemCount[column!] += 1
                let itemX = (itemWidth + self.minimumInteritemSpacing) * CGFloat(column!) + self.sectionInset.left
                let itemY = minHeight
                // 等比例缩放 计算item的高度
                let itemH = Int(Double(data.itemHeight!)!)
                // 设置frame
                attributes.frame = CGRect(x: itemX, y: CGFloat(itemY), width: itemWidth, height: CGFloat(itemH))
                
                attributesArray.append(attributes)
                // 累加列高
                columnHeight[column!] += itemH + Int(self.minimumLineSpacing)
                index += 1
            }
            
            // 找出最高列列号
            let maxHeight:Int = columnHeight.sorted().last!
            let column = columnHeight.index(of: maxHeight)
            // 根据最高列设置itemSize 使用总高度的平均值
            let itemH = (maxHeight - Int(self.minimumLineSpacing) * (columnItemCount[column!] + 1)) / columnItemCount[column!]
            self.itemSize = CGSize(width: itemWidth, height: CGFloat(itemH))
            // 添加尾部属性
            let footerIndexPath:IndexPath = IndexPath.init(item: 0, section: 0)
            let footerAttr:UICollectionViewLayoutAttributes = UICollectionViewLayoutAttributes.init(forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, with: footerIndexPath)
            footerAttr.frame = CGRect(x: 0, y: CGFloat(maxHeight), width: self.collectionView!.bounds.size.width, height: 30)
            attributesArray.append(footerAttr)
            // 给属性数组设置数值
            self.layoutAttributesArray = attributesArray
            self.maxH = maxHeight + 30
        }
        
        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            
            return self.layoutAttributesArray
        }
        
        ///重写设置contentSize,一定要写上这个方法,不然可能拉到最底部的时候可能会有很多空白
        override var collectionViewContentSize: CGSize {
            get {
                return CGSize(width: (collectionView?.bounds.width)!, height: CGFloat(self.maxH!))
            }
            set {
                self.collectionViewContentSize = newValue
            }
        }
    
    }
    
    
    5 - 按如上代码布局后的位置

    2 - 数据展示

    知道如何布局了就是常规的collocationView展示了,只是我这里加上了头部 尾部视图,还写了个简单的上下拉刷新。头部、尾部、cell我都是用xib写的,注册的时候记得用nib注册就行了。控制器里设置好自定layout的一些属性:

    //MARK: - --- 设置item的布局
        func setHomeFlowLayouts(){
            //通过layout的一些参数设置item的宽度
            let inset = UIEdgeInsetsMake(10, 10, 10, 10)
            let minLine:CGFloat = 10.0
            self.itemWidth = (SCREEN_WIDTH - inset.left - inset.right - minLine * (CGFloat(self.columnCount - 1))) / CGFloat(self.columnCount)
            
            //设置布局属性
            self.flowLayout.columnCount = self.columnCount
            self.flowLayout.sectionInset = inset
            self.flowLayout.minimumLineSpacing = minLine
        }
    

    还有就是调用网络请求的方法,这里是没有用到网络请求接口,是用的我上篇文章提到的HTML解析得到的数据模型,我在请求模型类JianshuRequestModel.swift里暴露了个方法,待解析完网页数据存入模型后用闭包(相当于OC的block)返回数组数据:

            //网络请求回调
            //参数:self.index是页码;self.itemWidth是透过计算得到的item的宽度
            JianshuRequestModel.jianshuRequestDataWithPage(self.index, Float(self.itemWidth), { (headInfo) in
                  //返回headInfo头部信息,只有在page等于1的时候返回,因为每一页的头部信息都是一样的。
            }) { (dataArr) in
                  //dataArr文章列表数据
            }
    

    至于上面提到的上下拉刷新,也就不赘述了,监听scrollView.contentOffset.y 和self.collectionView.contentInset设置个临界点做相应的操作即可,有疑惑的可以参考下文末的Demo。

    3 - 切换布局

    有运行过demo看过效果的同学可能会看到我在头部放了一个切换的按钮。此按钮的作用是用来切换布局的。默认情况下是两列,不停点击会在1列和2列中切换。self.columnCount这个成员变量就是设置列数的,理论上还可以设置3甚至更大的整数,不过这里我不推荐这么做,因为2列以上item的宽度会很窄,数据会挤在一起相当难看。


    6 - 切换布局

    我在头部视图类写了个按钮点击事件的闭包回调,在初始化头部视图的地方写上如下逻辑可切换布局:

                //点击切换布局
                self.headerView?.switchBack = { (click) in
                    print(click)
                    //切换列数
                    self.columnCount = (click == true) ? 1 : 2
                    self.setHomeFlowLayouts()
                    //遍历数组 重新计算高度
                    var num = 0
                    for model in self.dataArr {
                        //计算标题和摘要的高度
                        model.imgW = Float(self.itemWidth - 16)
                        model.imgH = model.wrap!.count > 0 ? model.imgW! * 120 / 150 : nil
                        model.titleH = GETSTRHEIGHT(fontSize: 20, width: CGFloat(model.imgW!) , words: model.title!) + 1
                        model.abstractH = GETSTRHEIGHT(fontSize: 14, width: CGFloat(model.imgW!) , words: model.abstract!) + 1
                        
                        //item高度
                        var computeH:CGFloat = 8 + 25 + 3 + 10 + 8 + (model.imgH != nil ? CGFloat(model.imgH!) : 0) + 8 + model.titleH! + 8 + model.abstractH! + 8 + 10 + 8
                        //如果没有图片减去一个间隙8
                        computeH = computeH - (model.wrap!.count > 0 ? 0 : 8)
                        model.itemHeight = String(format: "%.f", computeH)
                        self.dataArr[num] = model;
                        num += 1
                    }
                    //重新赋值改变布局
                    self.flowLayout.findList = self.dataArr
                    //刷新视图
                    self.collectionView?.reloadData()
                }
    
    7 - 切换布局效果图
    以上就是本文的全部内容,不懂的可以下载demo自行运行看下源码或者可以留言我。
    下篇文章我将介绍文章详情的webview与js的交互,感兴趣的可以关注我,有更新会有提醒。
    👉猛戳右侧链接下载 本文GitHub源码
    上一篇文章:Swfit爬虫通过作者ID无接口获取简书文章列表,正则匹配HTML标签存储模型数据

    相关文章

      网友评论

        本文标题:Swift瀑布流展示/切换简书列表数据

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