你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS瀑布流的簡單實現(Swift)

iOS瀑布流的簡單實現(Swift)

編輯:IOS開發綜合

這段時間突然想到一個很久之前用到的知識-瀑布流,本來想用一個簡單的方法,發現自己走入了歧途,最終只能狠下心來重寫UICollectionViewFlowLayout.下面我將用兩種方法實現瀑布流,以及會介紹第一種實現的bug.

<1>第一種

效果圖如下所示:

這種實現方法的思路: 

1)首先調用隨機函數,產生隨機高度,並把它保存到數組中

 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
  CGFloat cellW = 100;
  CGFloat cellH = 100 + (arc4random() % 80);
  [self.heightArrayM addObject:@(cellH)];
  
  return CGSizeMake(cellW, cellH);
  
}

2)在設置cell的frame的地方,通過取余,取整確定cell的高度,並設定cell的frame

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  
  UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
  //當前處於多少行
  NSInteger num1 = indexPath.row / count;
  //當前處於多少列
  int num2 = indexPath.row % count;
  CGFloat cellX = num2 * 100 + (num2 + 1) * margin;
  CGFloat cellY = 0;
  for (int i = 0; i < num1; i++) {
    NSInteger position = num2 + i * 3;
    cellY += [self.heightArrayM[position] floatValue] + margin;
  }
  CGFloat cellW = 100;
  CGFloat cellH = cellHeight;
  cell.frame = CGRectMake(cellX, cellY, cellW, cellH);
//  cell.backgroundColor = [UIColor redColor];
  cell.backgroundColor = [UIColor colorWithRed:(arc4random() % 250) / 250.0 green:(arc4random() % 250) / 250.0 blue:(arc4random() % 250) / 250.0 alpha:1.0];
  
//  NSLog(@"%@", NSStringFromCGRect(cell.frame)); 
  return cell;
}

弊端 : 其實這種方法的弊端,相信從上面的動態圖中可以看出來,當往上面滑的時候,由於cell的循環機制,下面的cell的會消失,但是由於高度不一致,同時撤銷的是最後一行的cell,所以下面的cell在屏幕上就會消失.

下面附上第一種方法的源代碼:

#import "ViewController.h"

#define margin 10
#define count 3
#define cellHeight [self.heightArrayM[indexPath.row] floatValue]
static NSString * const ID = @"cell";
@interface ViewController ()<UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@property (nonatomic, strong) NSMutableArray *heightArrayM;

@end

@implementation ViewController

- (NSMutableArray *)heightArrayM {
  if (_heightArrayM == nil) {
    _heightArrayM = [NSMutableArray array];
  }
  return _heightArrayM;
}

- (void)viewDidLoad {
  [super viewDidLoad];
  
  [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:ID];
  self.collectionView.dataSource = self;
  self.collectionView.delegate = self;
  //設置collectionView
  [self setupCollectionView];
}

//設置collectionView的布局
- (UICollectionViewFlowLayout *)setupCollectionLayout {
  UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
  
  flowLayout.minimumInteritemSpacing = margin;
  flowLayout.minimumLineSpacing = margin;
  flowLayout.sectionInset = UIEdgeInsetsMake(margin, margin, margin, margin);
  return flowLayout;
}

//設置collectionView
- (void)setupCollectionView {
  self.collectionView.collectionViewLayout =[self setupCollectionLayout];
  
}

#pragma mark - UICollectionViewDataSouce
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  return 60;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  
  UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
  //當前處於多少行
  NSInteger num1 = indexPath.row / count;
  //當前處於多少列
  int num2 = indexPath.row % count;
  CGFloat cellX = num2 * 100 + (num2 + 1) * margin;
  CGFloat cellY = 0;
  for (int i = 0; i < num1; i++) {
    NSInteger position = num2 + i * 3;
    cellY += [self.heightArrayM[position] floatValue] + margin;
  }
  CGFloat cellW = 100;
  CGFloat cellH = cellHeight;
  cell.frame = CGRectMake(cellX, cellY, cellW, cellH);
//  cell.backgroundColor = [UIColor redColor];
  cell.backgroundColor = [UIColor colorWithRed:(arc4random() % 250) / 250.0 green:(arc4random() % 250) / 250.0 blue:(arc4random() % 250) / 250.0 alpha:1.0];
  
//  NSLog(@"%@", NSStringFromCGRect(cell.frame)); 
  return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
  CGFloat cellW = 100;
  CGFloat cellH = 100 + (arc4random() % 80);
  [self.heightArrayM addObject:@(cellH)];
  
  return CGSizeMake(cellW, cellH);
  
}
@end

<2>下面介紹第二種(Swift實現)

效果圖如下所示:

這種實現方法就是比較成熟的了,我把它封裝成一個類.其實主要是實現三個函數

1)重寫父類的prepare方法,准備所有cell的樣式

 extension WaterfallLayout {
  // prepare准備所有Cell的布局樣式
  override func prepare() {
    super.prepare()
    
    // 0.獲取item的個數
    let itemCount = collectionView!.numberOfItems(inSection: 0)
    
    // 1.獲取列數
    let cols = dataSource?.numberOfColsInWaterfallLayout?(self) ?? 2
    
    // 2.計算Item的寬度
    let itemW = (collectionView!.bounds.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing * CGFloat((cols - 1))) / CGFloat(cols)
    
    // 3.計算所有的item的屬性
    for i in startIndex..<itemCount {
      // 1.設置每一個Item位置相關的屬性
      let indexPath = IndexPath(item: i, section: 0)
      
      // 2.根據位置創建Attributes屬性
      let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)
      
      // 3.隨機一個高度
      guard let height = dataSource?.waterfallLayout(self, indexPath: indexPath) else {
        fatalError("請設置數據源,並且實現對應的數據源方法")
      }
      
      // 4.取出最小列的位置
      var minH = colHeights.min()!
      let index = colHeights.index(of: minH)!
      minH = minH + height + minimumLineSpacing
      colHeights[index] = minH
      
      // 5.設置item的屬性
      attrs.frame = CGRect(x: self.sectionInset.left + (self.minimumInteritemSpacing + itemW) * CGFloat(index), y: minH - height - self.minimumLineSpacing, width: itemW, height: height)
      attrsArray.append(attrs)
    }
    
    // 4.記錄最大值
    maxH = colHeights.max()!
    
    // 5.給startIndex重新復制
    startIndex = itemCount
  }
}

2)返回設置cell樣式的數組

 override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return attrsArray
  }

3)返回當前的contentSize

override var collectionViewContentSize: CGSize {
    return CGSize(width: 0, height: maxH + sectionInset.bottom - minimumLineSpacing)
  }

總結:

在下面我封裝的這個類中,只需要遵守我的數據代理源協議並且實現我的協議中的兩個方法,傳給我對應得高度(我這裡是傳的隨機的),可選的方法,若是不實現,會有一個默認值,就可以實現該功能.協議如下:

@objc protocol WaterfallLayoutDataSource : class {
  func waterfallLayout(_ layout : WaterfallLayout, indexPath : IndexPath) -> CGFloat
  @objc optional func numberOfColsInWaterfallLayout(_ layout : WaterfallLayout) -> Int
}

完成代碼如下所示:

ViewController.swift中的代碼:

import UIKit

extension UIColor {
  class func randomColor() -> UIColor {
    return UIColor(colorLiteralRed: Float(arc4random_uniform(256)) / 255.0, green: Float(arc4random_uniform(256)) / 255.0, blue: Float(arc4random_uniform(256)) / 255.0, alpha: 1.0)
  }
}

private let kWaterCellID = "kWaterCellID"

class ViewController: UIViewController {
  
  var count : Int = 20
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    // 1.設置布局
    let layout = WaterfallLayout()
    layout.minimumLineSpacing = 10
    layout.minimumInteritemSpacing = 10
    layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    layout.dataSource = self
    
    // 2.創建UICollectionView
    let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
    collectionView.dataSource = self
    collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: kWaterCellID)
    view.addSubview(collectionView)
  }
  
}

extension ViewController : UICollectionViewDataSource {
  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return count
  }
  
  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kWaterCellID, for: indexPath)
    
    cell.backgroundColor = UIColor.randomColor()
    
    if indexPath.item == count - 1 {
      count += 20
      
      collectionView.reloadData()
    }
    
    return cell
  }
}


extension ViewController : WaterfallLayoutDataSource {
  func waterfallLayout(_ layout: WaterfallLayout, indexPath: IndexPath) -> CGFloat {
    return CGFloat(arc4random_uniform(80) + 100)
  }
  
  func numberOfColsInWaterfallLayout(_ layout: WaterfallLayout) -> Int {
    return 3
  }
}

封裝自定義布局中的WaterfallLayout.swift代碼如下:

import UIKit

@objc protocol WaterfallLayoutDataSource : class {
  func waterfallLayout(_ layout : WaterfallLayout, indexPath : IndexPath) -> CGFloat
  @objc optional func numberOfColsInWaterfallLayout(_ layout : WaterfallLayout) -> Int
}

class WaterfallLayout: UICollectionViewFlowLayout {
  
  // MARK: 對外提供屬性
  weak var dataSource : WaterfallLayoutDataSource?
  
  // MARK: 私有屬性
  fileprivate lazy var attrsArray : [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()
  
  fileprivate var totalHeight : CGFloat = 0
  fileprivate lazy var colHeights : [CGFloat] = {
    let cols = self.dataSource?.numberOfColsInWaterfallLayout?(self) ?? 2
    var colHeights = Array(repeating: self.sectionInset.top, count: cols)
    return colHeights
  }()
  fileprivate var maxH : CGFloat = 0
  fileprivate var startIndex = 0
}


extension WaterfallLayout {
  // prepare准備所有Cell的布局樣式
  override func prepare() {
    super.prepare()
    
    // 0.獲取item的個數
    let itemCount = collectionView!.numberOfItems(inSection: 0)
    
    // 1.獲取列數
    let cols = dataSource?.numberOfColsInWaterfallLayout?(self) ?? 2
    
    // 2.計算Item的寬度
    let itemW = (collectionView!.bounds.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing * CGFloat((cols - 1))) / CGFloat(cols)
    
    // 3.計算所有的item的屬性
    for i in startIndex..<itemCount {
      // 1.設置每一個Item位置相關的屬性
      let indexPath = IndexPath(item: i, section: 0)
      
      // 2.根據位置創建Attributes屬性
      let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)
      
      // 3.隨機一個高度
      guard let height = dataSource?.waterfallLayout(self, indexPath: indexPath) else {
        fatalError("請設置數據源,並且實現對應的數據源方法")
      }
      
      // 4.取出最小列的位置
      var minH = colHeights.min()!
      let index = colHeights.index(of: minH)!
      minH = minH + height + minimumLineSpacing
      colHeights[index] = minH
      
      // 5.設置item的屬性
      attrs.frame = CGRect(x: self.sectionInset.left + (self.minimumInteritemSpacing + itemW) * CGFloat(index), y: minH - height - self.minimumLineSpacing, width: itemW, height: height)
      attrsArray.append(attrs)
    }
    
    // 4.記錄最大值
    maxH = colHeights.max()!
    
    // 5.給startIndex重新復制
    startIndex = itemCount
  }
}

extension WaterfallLayout {
  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return attrsArray
  }
  
  override var collectionViewContentSize: CGSize {
    return CGSize(width: 0, height: maxH + sectionInset.bottom - minimumLineSpacing)
  }
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved