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

上篇文章介绍了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标签存储模型数据

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • iOS流布局UICollectionView系列一——初识与简单使用UICollectionView 一、简介 U...
    我是啊梁阅读 10,468评论 3 10
  • 运营管理本学期第一次复盘,李萌萌小组强势来袭,关于海尔集团定制生产的案例分析。
    AliceTrueLife阅读 126评论 0 1
  • 文/雕琢人生 生活总会变着花样折腾你,或大或小,或强或弱,磨练心性,或许,这才是真实的生活。 梦里,手痒痒的,居然...
    雕琢人生阅读 1,527评论 17 14
  • 姓名 :张海滔 公司:扬州市方圆建筑工程有限公司 380期(哈尔滨)《六项精进》“感谢组”成员 【日精进打卡第 3...
    小楼昨夜又西风_a3c5阅读 102评论 0 0
  • 文/竹之华 时间是一道转轮,寒来暑往,兜兜转转,却在不经意之间磨平了人的棱角。蓦然一惊,已到中年,所有青春的轻狂,...
    大野的竹阅读 359评论 0 2