上篇文章介紹了HTML解析數據存入模型,今天我們的任務是將把之前拿到的數據可視化展示出來。因為組數據的的標題和摘要的文字多少不一,每個 item高度自然就不一樣,我又想用collocationView展示兩列數據,首先想到的就是瀑布流?,F在很多移動端H5就是用瀑布流展示的,比如花瓣、Pinterest(需要梯子)。
本文采用類似花瓣的布局給大家分享,先上一張最終效果圖:
因為不管是OC還是Swift中UICollocationView的UICollectionViewFlowLayout默認都是只能設置統一的布局的,要做到如效果圖一樣的效果就需要重寫UICollectionViewFlowLayout。
1 - 分析原理
首先我們迫切需要知道的是每個item高度是多少如何布局。上篇文章我們已經通過HTML解析到的數據計算出了高度,接下來就要通過制定一個規則,讓每個item加在特定一列上。假如我們設定有兩列數據,很顯然我們不能通過列表數組的index的奇偶來確定它們的位置。因為假設數組arr的偶數下標值arr[i]的高度值都很大,奇數下標值arr[i]的高度值都很小,所以通過奇偶布局會出現第一列會比第2列高出很多,第二列下面會留很大的空白,這樣肯定是不行。
現在給出一種思路:數據數組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
}
}
}
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的寬度會很窄,數據會擠在一起相當難看。
我在頭部視圖類寫了個按鈕點擊事件的閉包回調,在初始化頭部視圖的地方寫上如下邏輯可切換布局:
//點擊切換布局
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()
}
以上就是本文的全部內容,不懂的可以下載demo自行運行看下源碼或者可以留言我。
下篇文章我將介紹文章詳情的webview與js的交互,感興趣的可以關注我,有更新會有提醒。
??猛戳右側鏈接下載 本文GitHub源碼
上一篇文章:Swfit爬蟲通過作者ID無接口獲取簡書文章列表,正則匹配HTML標簽存儲模型數據