天天看點

自定義UICollectionViewLayout—實作瀑布流效果

最終效果:

自定義UICollectionViewLayout—實作瀑布流效果

用到的第三方架構:

自定義UICollectionViewLayout—實作瀑布流效果

1.自定義UICollectionViewLayout實作瀑布流布局效果

//
//  WaterflowLayout.h
//  0104-瀑布流


#import <UIKit/UIKit.h>

@class WaterflowLayout;

@protocol WaterflowLayoutDelegate <NSObject>
- (CGFloat)waterflowLayout:(WaterflowLayout *)waterflowLayout heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath;
@end

@interface WaterflowLayout : UICollectionViewLayout

@property (nonatomic, assign) UIEdgeInsets sectionInset;
/** 每一列之間的間距 */
@property (nonatomic, assign) CGFloat columnMargin;
/** 每一行之間的間距 */
@property (nonatomic, assign) CGFloat rowMargin;
/** 顯示多少列 */
@property (nonatomic, assign) int columnsCount;


@property (nonatomic, weak) id<WaterflowLayoutDelegate> delegate;

@end
           
//
//  WaterflowLayout.m
//  0104-瀑布流

#import "WaterflowLayout.h"

@interface WaterflowLayout()
/** 這個字典用來存儲每一列最大的Y值(每一列的高度) */
@property (nonatomic, strong) NSMutableDictionary *maxYDict;
/** 存放所有的布局屬性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
@end

@implementation WaterflowLayout

- (NSMutableDictionary *)maxYDict
{
    if (!_maxYDict) {
        self.maxYDict = [[NSMutableDictionary alloc] init];
    }
    return _maxYDict;
}

- (NSMutableArray *)attrsArray
{
    if (!_attrsArray) {
        self.attrsArray = [[NSMutableArray alloc] init];
    }
    return _attrsArray;
}

- (instancetype)init
{
    if (self = [super init]) {
        self.columnMargin = 10;
        self.rowMargin = 10;
        self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
        self.columnsCount = 3;
    }
    return self;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    return YES;
}

/**
 *  每次布局之前的準備
 */
- (void)prepareLayout
{
    [super prepareLayout];
    
    // 1.清空最大的Y值
    for (int i = 0; i<self.columnsCount; i++) {
        NSString *column = [NSString stringWithFormat:@"%d", i];
        self.maxYDict[column] = @(self.sectionInset.top);
    }
    
    // 2.計算所有cell的屬性
    [self.attrsArray removeAllObjects];
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (int i = 0; i<count; i++) {
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        [self.attrsArray addObject:attrs];
    }
}

/**
 *  傳回所有的尺寸
 */
- (CGSize)collectionViewContentSize
{
    __block NSString *maxColumn = @"0";
    [self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL *stop) {
        if ([maxY floatValue] > [self.maxYDict[maxColumn] floatValue]) {
            maxColumn = column;
        }
    }];
    return CGSizeMake(0, [self.maxYDict[maxColumn] floatValue] + self.sectionInset.bottom);
}

/**
 *  傳回indexPath這個位置Item的布局屬性
 */
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // 假設最短的那一列的第0列
    __block NSString *minColumn = @"0";
    // 找出最短的那一列
    [self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL *stop) {
        if ([maxY floatValue] < [self.maxYDict[minColumn] floatValue]) {
            minColumn = column;
        }
    }];
    
    // 計算尺寸
    CGFloat width = (self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right - (self.columnsCount - 1) * self.columnMargin)/self.columnsCount;
    CGFloat height = [self.delegate waterflowLayout:self heightForWidth:width atIndexPath:indexPath];
    
    // 計算位置
    CGFloat x = self.sectionInset.left + (width + self.columnMargin) * [minColumn intValue];
    CGFloat y = [self.maxYDict[minColumn] floatValue] + self.rowMargin;
    
    // 更新這一列的最大Y值
    self.maxYDict[minColumn] = @(y + height);
    
    // 建立屬性
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    attrs.frame = CGRectMake(x, y, width, height);
    return attrs;
}


/**
 *  傳回rect範圍内的布局屬性
 */
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}

@end
           

2.控制器如何使用?

//
//  ViewController.m
//  0104-瀑布流


#import "ViewController.h"
#import "WaterflowLayout.h"
#import "ShopCell.h"
#import "MJExtension.h"
#import "Shop.h"
#import "MJRefresh.h"

@interface ViewController ()<UICollectionViewDataSource,UICollectionViewDelegate,WaterflowLayoutDelegate>
@property(nonatomic,weak)UICollectionView *collectionView;
@property(nonatomic,strong)NSMutableArray *shops;
@end

@implementation ViewController

static NSString *const ID = @"shop";

- (NSMutableArray *)shops
{
    if (_shops == nil) {
        self.shops = [NSMutableArray array];
        //2.加載1.plist建立模型
        NSArray *shopArray = [Shop objectArrayWithFilename:@"1.plist"];
        [self.shops addObjectsFromArray:shopArray];
    }
    return _shops;
    
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //1.建立UICollectionView
    [self createCollectionView];
    
    //2.添加一個上來重新整理尾部控件(MJRefresh)
    [self.collectionView addFooterWithTarget:self action:@selector(loadMoreShops)];
}

/**
 *  加載更多商品
 */
- (void)loadMoreShops
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSArray *shopArray = [Shop objectArrayWithFilename:@"1.plist"];
        [self.shops addObjectsFromArray:shopArray];
        [self.collectionView reloadData];
        [self.collectionView footerEndRefreshing];
    });
}

/**
 *  建立UICollectionView
 */
- (void)createCollectionView
{
    WaterflowLayout *layout = [[WaterflowLayout alloc] init];
    layout.delegate = self;
//    layout.sectionInset = UIEdgeInsetsMake(100, 20, 40, 30);
//    layout.columnMargin = 20;
//    layout.rowMargin = 30;
//    layout.columnsCount = 4;
    
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
    collectionView.delegate = self;
    collectionView.dataSource = self;
    [collectionView registerNib:[UINib nibWithNibName:@"ShopCell" bundle:nil] forCellWithReuseIdentifier:ID];
    [self.view addSubview:collectionView];
    self.collectionView = collectionView;
}

#pragma mark - UICollectionViewDataSource
/**
 *  每組有多少個cell
 */
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return self.shops.count;
}

/**
 *  傳回cell
 
 */
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    ShopCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
    cell.shop = self.shops[indexPath.item];
    return cell;
}

#pragma mark - UICollectionViewDelegate

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
   
    NSLog(@"點選了第%ld個cell",(long)indexPath.item);
   
}

#pragma mark - WaterflowLayoutDelegat
- (CGFloat)waterflowLayout:(WaterflowLayout *)waterflowLayout heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath
{
    Shop *shop = self.shops[indexPath.item];
    
    return shop.h / shop.w * width;
}


@end
           

3. 自定義的UICollectionViewCell

//
//  ShopCell.h
//  0104-瀑布流


#import <UIKit/UIKit.h>

@class Shop;
@interface ShopCell : UICollectionViewCell
@property(nonatomic,strong)Shop *shop;
@end
           
//
//  ShopCell.m
//  0104-瀑布流


#import "ShopCell.h"
#import "Shop.h"
#import "UIImageView+WebCache.h"

@interface ShopCell()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UILabel *priceLabel;

@end
@implementation ShopCell

/*
 iOS9 HTTP 不能正常使用的解決辦法:
 1.在Info.plist中添加NSAppTransportSecurity類型Dictionary。
 2.在NSAppTransportSecurity下添加NSAllowsArbitraryLoads類型Boolean,值設為YES
 */

- (void)setShop:(Shop *)shop
{
    _shop = shop;
    

    // 1.圖檔
    [self.imageView sd_setImageWithURL:[NSURL URLWithString:shop.img] placeholderImage:[UIImage imageNamed:@"loading"]];
    
    // 2.價格
    self.priceLabel.text = shop.price;
}

- (void)awakeFromNib {
    // Initialization code
}

@end
           
自定義UICollectionViewLayout—實作瀑布流效果

4.cell上用到的模型

//
//  Shop.h


#import <UIKit/UIKit.h>

@interface Shop : NSObject
@property (nonatomic, assign) CGFloat w;
@property (nonatomic, assign) CGFloat h;
@property (nonatomic, copy) NSString *img;
@property (nonatomic, copy) NSString *price;
@end
           
//
//  shop.m

#import "Shop.h"

@implementation Shop

@end
           

5.1.plist資料結構,實際開發中應該是去伺服器擷取資料

自定義UICollectionViewLayout—實作瀑布流效果