63 - Swift之瀑布流(UICollectionView)

一吵聪、簡(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()
}

五坯门、 本Demo的效果展示

1微饥、 只有一列的顯示

1.gif

2、 兩列的情況下顯示

2.gif

3田盈、 三列的情況下顯示

3.gif

4畜号、四列的情況下顯示

4.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缴阎,一起剝皮案震驚了整個(gè)濱河市允瞧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛮拔,老刑警劉巖述暂,帶你破解...
    沈念sama閱讀 222,865評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異建炫,居然都是意外死亡畦韭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門肛跌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)艺配,“玉大人,你說(shuō)我怎么就攤上這事衍慎∽Γ” “怎么了?”我有些...
    開封第一講書人閱讀 169,631評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵稳捆,是天一觀的道長(zhǎng)赠法。 經(jīng)常有香客問(wèn)我,道長(zhǎng)乔夯,這世上最難降的妖魔是什么砖织? 我笑而不...
    開封第一講書人閱讀 60,199評(píng)論 1 300
  • 正文 為了忘掉前任款侵,我火速辦了婚禮,結(jié)果婚禮上侧纯,老公的妹妹穿的比我還像新娘新锈。我一直安慰自己,他們只是感情好眶熬,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,196評(píng)論 6 398
  • 文/花漫 我一把揭開白布壕鹉。 她就那樣靜靜地躺著,像睡著了一般聋涨。 火紅的嫁衣襯著肌膚如雪晾浴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,793評(píng)論 1 314
  • 那天牍白,我揣著相機(jī)與錄音脊凰,去河邊找鬼。 笑死茂腥,一個(gè)胖子當(dāng)著我的面吹牛狸涌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播最岗,決...
    沈念sama閱讀 41,221評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼帕胆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了般渡?” 一聲冷哼從身側(cè)響起懒豹,我...
    開封第一講書人閱讀 40,174評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驯用,沒(méi)想到半個(gè)月后脸秽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,699評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝴乔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,770評(píng)論 3 343
  • 正文 我和宋清朗相戀三年记餐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薇正。...
    茶點(diǎn)故事閱讀 40,918評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡片酝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挖腰,到底是詐尸還是另有隱情雕沿,我是刑警寧澤,帶...
    沈念sama閱讀 36,573評(píng)論 5 351
  • 正文 年R本政府宣布曙聂,位于F島的核電站晦炊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜断国,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,255評(píng)論 3 336
  • 文/蒙蒙 一贤姆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稳衬,春花似錦霞捡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至街夭,卻和暖如春砰碴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背板丽。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工呈枉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人埃碱。 一個(gè)月前我還...
    沈念sama閱讀 49,364評(píng)論 3 379
  • 正文 我出身青樓猖辫,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親砚殿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子啃憎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,926評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容