一吵聪、簡(jiǎn)介
瀑布流:又稱瀑布流式布局劲装。是比較流行的一種網(wǎng)站頁(yè)面布局胧沫,視覺(jué)表現(xiàn)為參差不齊的多欄布局,隨著頁(yè)面滾動(dòng)條向下滾動(dòng)占业,這種布局還會(huì)不斷加載數(shù)據(jù)塊并附加至當(dāng)前尾部绒怨。
二、 瀑布流的實(shí)現(xiàn)
1谦疾、瀑布流的實(shí)現(xiàn)選擇
瀑布流的實(shí)現(xiàn)可以使用 UITableView 或 UICollectionView 或 UIScrollView 等來(lái)實(shí)現(xiàn)瀑布流南蹂。注意: UITableView 和 UICollectionView 兩個(gè)在實(shí)現(xiàn)瀑布流時(shí)相比 UIScrollView 實(shí)現(xiàn)瀑布流較為簡(jiǎn)單。同時(shí) UICollectionView 在實(shí)現(xiàn)瀑布流時(shí)要比 UITableView 時(shí) Item的樣式多樣和流動(dòng)效果也更好念恍。
2六剥、 瀑布流的實(shí)現(xiàn)準(zhǔn)備
根據(jù)各個(gè)實(shí)現(xiàn)瀑布流的控件的對(duì)比,我們選擇 UICollectionView 來(lái)實(shí)現(xiàn)峰伙。選擇 UICollectionView 就需要自定義流動(dòng)布局疗疟,我們要?jiǎng)?chuàng)建一個(gè)布局類繼承與 UICollectionViewFlowLayout 。同時(shí)要重寫UICollectionViewFlowLayout 的下面四個(gè)方法:
override func prepare() ===》: 作用是重新更改Item的布局瞳氓。但是在重新更改之前要首先調(diào)用 父類的該方法策彤。
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? ==》: 返回每一個(gè) Item 的 LayoutAttribute 。
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? ==》: 返回indexPath位置的 Item的LayoutAttributes
override var collectionViewContentSize: CGSize ===》 : 返回 collectionView 的滑動(dòng)范圍
3匣摘、 瀑布流的布局重寫
1店诗、 定義一些變量
// 創(chuàng)建一個(gè)代理
var delegate:WaterfallViewFlowLayoutDelegate?
// 瀑布流的列數(shù)
var WatefallColumns = 2
// 列間距
var ColumnsSpacing:CGFloat = 10.0
// 行間距
var LineSpacing:CGFloat = 10.0
// 創(chuàng)建存放當(dāng)前加載的Cell 的布局屬性
var cellLayoutAttributes : NSMutableArray?
// 創(chuàng)建存放Cell 所在列的高度
var cellColumnsHeights : NSMutableArray?
// cell 內(nèi)容的高度
var contentHeight:CGFloat?
2、 func prepare() 方法的重寫
// MARK: 重寫初始化方法
override func prepare() {
// 調(diào)用父類方法
super.prepare()
// MARK: 初始化一些數(shù)據(jù)
// TODO: 內(nèi)容的高度
contentHeight = 0.0
// TODO: 數(shù)組初始化
cellColumnsHeights = NSMutableArray.init(capacity: 0)
cellLayoutAttributes = NSMutableArray.init(capacity: 0)
// TODO: 我要初始化每列高度的初始值
for _ in 0 ..< WatefallColumns {
cellColumnsHeights?.add(self.sectionInset.top)
}
// TODO: 獲取當(dāng)前加載的Cell個(gè)數(shù)
let loadCellCount = self.collectionView?.numberOfItems(inSection: 0)
// TODO: 遍歷獲取的Cell得到每個(gè)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: 設(shè)置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: 默認(rèn)cellColumnsHeights的第一個(gè)對(duì)象是高度最低的Cell
var minColumnsCellHeight = cellColumnsHeights?.firstObject as! CGFloat
// TODO: 標(biāo)記第幾列是Cell 最低列
var minColumnCellMark = 0
// TODO: 遍歷每一列的Cell高度庞瘸,獲取得到最小的一個(gè)
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: 設(shè)置大小
let LayoutAttribute = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
LayoutAttribute.frame = CGRect.init(x: minCellHeightX, y: cellHeightY, width: cellWidth, height: cellHight!)
// TODO: 設(shè)置Cell 高度中,最低的Y軸位置
cellColumnsHeights?[minColumnCellMark] = LayoutAttribute.frame.maxY
// TODO: 獲取Cell高度數(shù)組最小的一個(gè)
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: 設(shè)置Cell可滑動(dòng)的范圍。注意:swift3.0廢棄了上面這個(gè)方法梅割,所以我們改成重寫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 函數(shù)的實(shí)現(xiàn)
// 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 的代理的聲明
// 創(chuàng)建代理
protocol WaterfallViewFlowLayoutDelegate: NSObjectProtocol {
// 獲取內(nèi)容的高度
func waterFlowLayout(waterFlowLayout:WaterfallViewFlowLayout,indexPath: IndexPath,width:CGFloat) -> CGFloat ;
}
三 户辞、 瀑布流的實(shí)現(xiàn)
1泌类、 plist 文件的加載獲取數(shù)據(jù)
// MARK: 獲取展示的數(shù)據(jù)
func getShowData() -> Void {
dataSource = NSArray.init()
let plist = Bundle.main.path(forResource: "loadData", ofType: "plist", inDirectory: nil)
dataSource = NSArray.init(contentsOfFile: plist!)
}
2、 UICollecionView 的創(chuàng)建 (重點(diǎn))
// MARK: 創(chuàng)建CollectionView
func createCollectionView() -> Void {
// TODO: 設(shè)置布局對(duì)象
let flowLayout = WaterfallViewFlowLayout.init()
// TODO: 設(shè)置有多少列
flowLayout.WatefallColumns = 1
// TODO: 設(shè)置代理
flowLayout.delegate = self
// TODO: 設(shè)置Section的偏移
flowLayout.sectionInset = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)
// TODO: 設(shè)置滑動(dòng)的方向
flowLayout.scrollDirection = .vertical
// TODO: 創(chuàng)建 CollectionView 對(duì)象
let collectionView = UICollectionView.init(frame: self.view.frame, collectionViewLayout: flowLayout)
collectionView.backgroundColor = UIColor.white
collectionView.delegate = self
collectionView.dataSource = self
// TODO:注冊(cè) Cell
collectionView .register(UICollectionViewCell.self, forCellWithReuseIdentifier: "NetWork小賤")
self.view.addSubview(collectionView)
}
3、 func waterFlowLayout(waterFlowLayout: WaterfallViewFlowLayout, indexPath: IndexPath, width: CGFloat) 函數(shù)的實(shí)現(xiàn)
// MARK: 代理事件
func waterFlowLayout(waterFlowLayout: WaterfallViewFlowLayout, indexPath: IndexPath, width: CGFloat) -> CGFloat {
// TODO: 獲取數(shù)據(jù)對(duì)象
let dict = dataSource![indexPath.row] as! NSDictionary
// TODO: 獲取圖像數(shù)據(jù)刃榨,為獲取高度做準(zhǔn)備
let image = UIImage.init(named: dict["image"] as! String)
// TODO: 計(jì)算各個(gè)元素的高度(圖像高 + 標(biāo)題高 + 內(nèi)容高)
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 的布局創(chuàng)建
// TODO: UICollectionViewCell的創(chuàng)建
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// TODO: 獲取Cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NetWork小賤", for: indexPath)
// TODO: 設(shè)置Cell 的背景色
cell.contentView.backgroundColor = UIColor.white
// TODO: 防止Cell 內(nèi)的元素復(fù)用
for item in cell.contentView.subviews {
item.removeFromSuperview()
}
// TODO: 獲取數(shù)據(jù)
let dict = dataSource![indexPath.row] as! NSDictionary
let image = UIImage.init(named: dict["image"] as! String)
// TODO: 創(chuàng)建圖像對(duì)象
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)
// 添加標(biāo)題
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)
// 添加內(nèi)容
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 的選擇處理(處理對(duì)象放大顯示)
// TODO: 選擇的是哪個(gè)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)
// 創(chuàng)建圖像點(diǎn)擊放大的對(duì)象
let tempView = WaterfallZoomComponentsView.init(frame: self.view.frame)
tempView.waterfallZoom(initialFrame: rect, image: imageView.image!)
self.view.addSubview(tempView)
}
6枢希、 獲取圖像高度的函數(shù)和文本高度的函數(shù)
1桌吃、 圖像的高度的獲取
// MARK: 獲取圖像的高度
func getImageHeight(image:UIImage,width:CGFloat) -> CGFloat {
// 獲取圖像對(duì)象的寬高比
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
}
四苞轿、圖像點(diǎn)擊放大的實(shí)現(xiàn)
UICollectionView 中的Cell 點(diǎn)擊放大茅诱。我們的實(shí)現(xiàn)是使用一個(gè)View 和 一個(gè) UIImageView 來(lái)展示的。所以搬卒,我們要?jiǎng)?chuàng)建一個(gè)繼承與UIView的類 WaterfallZoomComponentsView瑟俭。
WaterfallZoomComponentsView 類的一些方法的實(shí)現(xiàn)
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)
// 添加一個(gè)手勢(shì)
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 方法的實(shí)現(xiàn)
// MARK: 點(diǎn)擊圖像摆寄,原地放大顯示圖像
func waterfallZoom(initialFrame:CGRect ,image:UIImage ) -> Void {
// 設(shè)置圖像在原始View上的位置
self.imageView.frame = initialFrame
self.imageView.image = image
// 創(chuàng)建圖像變化后的寬與高的變量
var imageHeight :CGFloat
var imageWidth :CGFloat
// 判斷圖像是橫向還是豎向的
if image.size.width / image.size.height > self.bounds.width / self.bounds.height {
// 判斷圖像的實(shí)際寬度與 self的實(shí)際寬度對(duì)比
if image.size.width > self.bounds.width {
// 獲取現(xiàn)在的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{
// 判斷圖像的實(shí)際高度與self的實(shí)際高度對(duì)比
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)
}
}
// 大小變化的過(guò)程動(dòng)畫
UIView.animate(withDuration: 1.0) {
self.imageView.frame = self.changeRect
}
}
3 、手勢(shì)方法的實(shí)現(xiàn)
// MARK: 清除
func disMiss() -> Void {
self.removeFromSuperview()
}