Objective-C的UICollectionView(集合視圖)學習筆記

UICollectionView - 小白級別的應用

開篇即代碼隔躲,這是UICollectionView最簡單的應用效果了,十分符合我的水準:

//
//  TSChannelFourViewController.m

#import "TSChannelFourViewController.h"

/**
 模型
 */
@interface CategoryItemObject : NSObject

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray<NSString *> *categoryArray;

@end

@implementation CategoryItemObject

- (instancetype)init {
    self = [super init];
    if (self) {
        self.title = @"分類名稱";
        self.categoryArray = @[@"分類一",@"分類二",@"分類三",@"分類四",@"分類五",@"分類六",@"分類七",@"分類八",@"分類九",];
    }
    return self;
}

@end

/**
 頁眉補充視圖
 */
UIKIT_EXTERN NSString *const HeaderViewReuseIdentifier;
@interface HeaderView : UICollectionReusableView

@property (nonatomic, strong) UILabel *titleLabel;

@end

NSString *const HeaderViewReuseIdentifier = @"HeaderViewReuseIdentifier";
@implementation HeaderView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.titleLabel = [[UILabel alloc]initWithFrame:self.bounds];
        self.titleLabel.font = [UIFont systemFontOfSize:15];
        self.titleLabel.textColor = [UIColor blackColor];
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        [self addSubview:self.titleLabel];
    }
    return self;
}

@end

/**
 單元格
 */
UIKIT_EXTERN NSString *const CategoryCellReuseIdentifier;
@interface CategoryCell : UICollectionViewCell

@property (nonatomic, strong) UILabel *categoryNameLabel;

@end

NSString *const CategoryCellReuseIdentifier = @"CategoryCellReuseIdentifier";
@implementation CategoryCell

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.layer.cornerRadius = 22.0;
        self.layer.borderWidth = 1.0;
        self.categoryNameLabel = [[UILabel alloc]initWithFrame:self.bounds];
        self.categoryNameLabel.font = [UIFont systemFontOfSize:13];
        self.categoryNameLabel.textAlignment = NSTextAlignmentCenter;
        [self.contentView addSubview:self.categoryNameLabel];
    }
    return self;
}

@end

/**
 控制器
 */
@interface TSChannelFourViewController ()<UICollectionViewDataSource,UICollectionViewDelegate>

@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSMutableArray<CategoryItemObject *> *dataSource;

@end

@implementation TSChannelFourViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //流布局
    UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init];
    flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
    flowLayout.minimumLineSpacing = 15;
    flowLayout.minimumInteritemSpacing = 15;
    flowLayout.sectionInset = UIEdgeInsetsMake(15, 15, 15, 15);
    flowLayout.headerReferenceSize = CGSizeMake(45, 45);
    //集合視圖
    self.collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:flowLayout];
    self.collectionView.dataSource = self;
    self.collectionView.delegate = self;
    self.collectionView.allowsMultipleSelection = YES;
    [self.collectionView registerClass:CategoryCell.class forCellWithReuseIdentifier:CategoryCellReuseIdentifier];
    [self.collectionView registerClass:HeaderView.class forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:HeaderViewReuseIdentifier];
    [self.view addSubview:self.collectionView];
    self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [self.collectionView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
        [self.collectionView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [self.collectionView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [self.collectionView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
    ]];
}

/// 數(shù)據(jù)源
- (NSMutableArray<CategoryItemObject *> *)dataSource {
    if (_dataSource == nil) {
        _dataSource = [[NSMutableArray alloc]init];
        for (int i = 0; i < 3; i++) {
            CategoryItemObject *categoryItem = [[CategoryItemObject alloc]init];
            [_dataSource addObject:categoryItem];
        }
    }
    return _dataSource;
}

#pragma mark - UICollectionViewDataSource
//有幾節(jié)單元格
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return self.dataSource.count;
}

//每節(jié)有多少個單元格
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    CategoryItemObject *categoryItem = self.dataSource[section];
    return categoryItem.categoryArray.count;
}

//配置單元格
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    NSString *categoryName = self.dataSource[indexPath.section].categoryArray[indexPath.row];
    CategoryCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CategoryCellReuseIdentifier forIndexPath:indexPath];
    cell.categoryNameLabel.text = categoryName;
    return cell;
}

//配置頁眉補充視圖
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
        CategoryItemObject *categoryItem = self.dataSource[indexPath.section];
        HeaderView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:HeaderViewReuseIdentifier forIndexPath:indexPath];
        headerView.titleLabel.text = categoryItem.title;
        return headerView;
    }
    return nil;
}

#pragma mark - UICollectionViewDelegate
//單元格已被選擇
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    CategoryCell *cell = (CategoryCell *)[collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:226 / 255.0f green:240 / 255.0f blue:253 / 255.0f alpha:1.0];
    cell.layer.borderColor = [UIColor blueColor].CGColor;
    cell.categoryNameLabel.textColor = [UIColor blueColor];
}

//單元格已被取消選擇
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
    CategoryCell *cell = (CategoryCell *)[collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
    cell.layer.borderColor = [UIColor blackColor].CGColor;
    cell.categoryNameLabel.textColor = [UIColor blackColor];
}

#pragma mark - UICollectionViewDelegateFlowLayout
//每個單元格的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
    CGFloat cellWidth = (collectionView.bounds.size.width - flowLayout.minimumInteritemSpacing * 2 - flowLayout.sectionInset.left - flowLayout.sectionInset.right) / 3;
    return CGSizeMake(floor(cellWidth), 44);
}

@end
截屏2022-08-16 17.22.38.png

UICollectionView - 集合視圖

UICollectionView(以下稱為集合視圖)繼承自UIScrollView,是在IOS6中開放的為了增強網(wǎng)格視圖開發(fā)的視圖API。

集合視圖有兩個重要協(xié)議:UICollectionViewDataSource(數(shù)據(jù)源協(xié)議)與UICollectionViewDelegate(代理協(xié)議)权均,UICollectionViewDataSource(數(shù)據(jù)源協(xié)議)用來管理數(shù)據(jù)和為集合視圖提供單元格,UICollectionViewDelegate(代理協(xié)議)用于管理用戶與集合視圖中的單元格的交互澄惊。

UICollectionViewCell(單元格)是集合視圖要呈現(xiàn)的最小數(shù)據(jù)單位紊馏,UICollectionViewCell(單元格)就是一個視圖,沒有樣式和風格定義姻几,可以在內(nèi)部放置其他視圖或控件宜狐。自定義一個UICollectionViewCell(單元格)類势告,它需要繼承UICollectionViewCell。

除了UICollectionViewCell(單元格)之外抚恒,集合視圖還可以使用其他類型的補充視圖顯示數(shù)據(jù)咱台,例如這些補充視圖可以是與每一節(jié)單元格分開但仍傳達信息的節(jié)頁眉視圖和節(jié)頁腳視圖。對補充視圖的支持是可選的俭驮,由集合視圖的布局對象負責定義這些視圖的位置回溺。

UICollectionViewLayout用于為集合視圖生成布局信息的抽象基類,確定單元格混萝、補充視圖和裝飾視圖在集合視圖邊界內(nèi)的位置遗遵。UICollectionViewFlowLayout類是UICollectionViewLayout類的子類,提供了流式布局逸嘀。

集合視圖的組成

集合視圖有4個重要的組成部分车要,分別為:

  1. 單元格:即視圖中的一個單元格。
  2. 節(jié):即集合視圖中的一個行數(shù)據(jù)崭倘,由多個單元格構(gòu)成翼岁。
  3. 補充視圖:即節(jié)的頁眉和頁腳。
  4. 裝飾視圖:集合視圖中的背景視圖司光。
UICollectionView的一些常用屬性
@property (nonatomic, strong) UICollectionViewLayout *collectionViewLayout;

屬性描述用于組織集合視圖單元格的布局對象登澜,將新的布局對象指定給此屬性會導致將新布局(不帶動畫)應用于集合視圖的單元格。

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

屬性描述充當集合視圖的代理的對象飘庄,代理對象必須采用UICollectionViewDelegate協(xié)議脑蠕,集合視圖維護對委托對象的弱引用,代理對象負責管理選擇行為和與單個單元格的交互跪削。

@property (nonatomic, weak, nullable) id <UICollectionViewDataSource> dataSource;

屬性描述為集合視圖提供數(shù)據(jù)源的對象谴仙,數(shù)據(jù)源對象必須采用UICollectionViewDataSource協(xié)議,集合視圖維護對數(shù)據(jù)源對象的弱引用碾盐。

@property (nonatomic, strong, nullable) UIView *backgroundView;

屬性描述提供背景外觀的視圖晃跺,此屬性中的視圖(如果有)位于所有其他內(nèi)容的下面,并自動調(diào)整大小以填充集合視圖的整個邊界毫玖。背景視圖不與集合視圖的其他內(nèi)容一起滾動掀虎,集合視圖維護對背景視圖對象的強引用,默認情況下此屬性為nil付枫,為此屬性設置的視圖顯示與否也依賴于集合視圖是否設置了數(shù)據(jù)源對象烹玉。

@property (nonatomic) BOOL allowsSelection; 

屬性描述指示用戶是否可以在集合視圖中選擇單元格的布爾值。如果此屬性的值為YES(默認值)阐滩,則用戶可以選擇單元格二打。如果希望對單元格的選擇進行更細粒度的控制,則必須提供代理對象并實現(xiàn)UICollectionViewDelegate協(xié)議的適當方法掂榔。

@property (nonatomic) BOOL allowsMultipleSelection;

屬性描述一個布爾值继效,用于確定用戶是否可以在集合視圖中選擇多個單元格症杏。此屬性的默認值為NO,當此屬性的值為YES時瑞信,單擊單元格將其添加到當前選擇中(假設代理允許選擇該單元格)厉颤,再次點擊單元格將其從選擇中移除。

@property (nonatomic, readonly) NSInteger numberOfSections;

函數(shù)描述 :只讀屬性凡简,返回集合視圖顯示的節(jié)數(shù)走芋。

@property (nonatomic, readonly) NSArray<__kindof UICollectionViewCell *> *visibleCells;

屬性描述 :只讀屬性,返回集合視圖當前顯示的可見單元格對象數(shù)組潘鲫,如果沒有可見的單元格翁逞,則返回空數(shù)組。

@property (nonatomic, readonly) NSArray<NSIndexPath *> *indexPathsForVisibleItems;

屬性描述 :只讀屬性溉仑,集合視圖中可見單元格的NSIndexPath對象數(shù)組挖函。該數(shù)組未經(jīng)過排序,每個NSIndexPath對象都對應于集合視圖中的一個可見單元格浊竟。如果沒有可見單元格怨喘,則此屬性的值為空數(shù)組。此數(shù)組不包含任何當前可見的補充視圖振定。

UICollectionView的一些常用函數(shù)
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout NS_DESIGNATED_INITIALIZER;

函數(shù)描述使用指定的框架矩形(frame)和集合視圖單元格的布局對象(layout)初始化并返回新分配的集合視圖對象必怜。當以編程方式初始化集合視圖對象時使用此方法,此方法是指定的初始化方法后频。

參數(shù) :

frame : 集合視圖的框架矩形梳庆,以點為單位,框架的原點相對于要在其中添加它的父視圖卑惜,此frame在初始化期間傳遞給父類膏执。

layout : 用于組織單元格的布局對象,集合視圖存儲對指定對象的強引用露久,不能為nil更米。

返回值 : 初始化的集合視圖對象,如果無法創(chuàng)建該對象毫痕,則為nil征峦。

例如 :

UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:layout];
- (void)registerClass:(nullable Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;

函數(shù)描述注冊一個類以用于創(chuàng)建新的集合視圖單元格。在調(diào)用集合視圖的dequeueReusableCellWithReuseIdentifier:forIndexPath:方法之前消请,必須使用此方法或registerNib:forCellWithReuseIdentifier:方法告訴集合視圖如何創(chuàng)建給定類型的新單元格栏笆。如果指定類型的單元格當前不在重用隊列中,則集合視圖將使用提供的信息自動創(chuàng)建新的單元格對象梯啤。

如果兩個相同的重用標識符注冊了類或nib文件竖伯,則后注冊的類將替換之前使用該重用標識符注冊的類存哲,如果要從指定的重用標識符中注銷類因宇,可以調(diào)用該函數(shù)傳入指定的重用標識符并為cellClass參數(shù)指定nil七婴。

參數(shù) :

cellClass : 要在集合視圖中使用的單元格的類。

identifier : 要與指定類關(guān)聯(lián)的重用標識符察滑。此參數(shù)不能為nil打厘,也不能為空字符串。

例如 :

[self.collectionView registerClass:[GoodsSalesDiscountItem class] forCellWithReuseIdentifier:@"item"];
- (void)registerClass:(nullable Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;  

函數(shù)描述注冊一個類贺辰,用于為集合視圖創(chuàng)建補充視圖户盯。在調(diào)用集合視圖的dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:方法之前,必須使用此方法或registerNib:forSupplementaryViewOfKind:withReuseIdentifier:method來告訴集合視圖如何創(chuàng)建給定類型的補充視圖饲化。如果指定類型的視圖當前不在重用隊列中莽鸭,則集合視圖將使用提供的信息自動創(chuàng)建視圖對象。

如果兩個相同的重用標識符注冊了類或nib文件吃靠,則后注冊的類將替換之前使用該重用標識符注冊的類硫眨,如果要從指定的重用標識符中注銷類,可以調(diào)用該函數(shù)傳入指定的重用標識符并為viewClass參數(shù)指定nil巢块。

參數(shù) :

viewClass : 用于補充視圖的類礁阁。

elementKind : 要創(chuàng)建的補充視圖的類型。此值由布局對象定義族奢,此參數(shù)不能為nil姥闭。

identifier : 要與指定類關(guān)聯(lián)的重用標識符。此參數(shù)不能為nil越走,也不能為空字符串棚品。

例如 :

[self.collectionView registerClass:[HeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headerView"];

注:定義于UICollectionViewLayout中的補充視圖類型:

// 標識給定節(jié)的頁眉補充視圖
UIKIT_EXTERN NSString *const UICollectionElementKindSectionHeader API_AVAILABLE(ios(6.0));
// 標識給定節(jié)的頁腳補充視圖
UIKIT_EXTERN NSString *const UICollectionElementKindSectionFooter API_AVAILABLE(ios(6.0));
- (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;

函數(shù)描述返回由其標識符定位的可重用單元格對象,當要求為集合視圖提供新單元格時廊敌,從數(shù)據(jù)源對象代理函數(shù)中調(diào)用此方法南片,如果有一個可復用的單元格可用,此方法會使現(xiàn)有單元格出列庭敦,或者根據(jù)先前注冊的類或NIB文件創(chuàng)建一個新的單元格疼进。

在調(diào)用此方法之前,必須使用registerClass:forCellWithReuseIdentifier:或register nib:forCellWithReuseIdentifier:方法注冊類或nib文件秧廉。

如果您為指定的標識符注冊了一個類伞广,并且必須創(chuàng)建一個新的單元格,則此方法通過調(diào)用其initWithFrame:方法初始化該單元格疼电。對于基于nib的單元嚼锄,此方法從提供的nib文件加載單元對象。如果現(xiàn)有的單元可用于重用蔽豺,則該方法調(diào)用單元格的PraseReFuleRead方法区丑。

參數(shù) :

identifier : 指定單元格的重用標識符。此參數(shù)不能為nil。

indexPath : 指定單元格位置的索引路徑沧侥。數(shù)據(jù)源在請求單元格時接收此信息可霎,并應將其傳遞出去。此方法使用索引路徑根據(jù)單元格在集合視圖中的位置執(zhí)行其他配置宴杀。

返回值 :有效的UICollectionViewCell對象癣朗。

例如 :

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    GoodsSalesDiscountItem *item = [collectionView dequeueReusableCellWithReuseIdentifier:@"item" forIndexPath:indexPath];
    return item;
}
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;

函數(shù)描述返回可重用的補充視圖,該視圖按其標識符(identifier)和類型(elementKind)定位旺罢。當要求為集合視圖提供新的補充視圖時旷余,數(shù)據(jù)源對象代理函數(shù)中調(diào)用此方法,如果現(xiàn)有補充視圖可復用扁达,則此方法使現(xiàn)有補充視圖出列正卧,或者根據(jù)之前注冊的類或 nib 文件創(chuàng)建新補充視圖。

在調(diào)用此方法之前跪解,必須使用registerClass:forSupplementaryViewOfKind:withReuseIdentifier:或registerNib:forSupplementaryViewOfKind:withReuseIdentifier:方法注冊類或nib文件穗酥。還可以使用registerClass:forDecorationViewOfKind:或registerNib:forDecorationViewOfKind:方法向布局對象注冊一組默認補充視圖。

參數(shù) :

elementKind : 要檢索的補充視圖的類型惠遏。此值由布局對象定義砾跃。此參數(shù)不能為nil。

identifier : 指定視圖的重用標識符节吮。此參數(shù)不能為nil抽高。

indexPath : 指定補充視圖在集合視圖中的位置的索引路徑。數(shù)據(jù)源在請求視圖時接收此信息透绩,并應將其傳遞出去翘骂。此方法使用信息根據(jù)視圖在集合視圖中的位置執(zhí)行附加配置。

返回值 : 有效的UICollectionReusableView對象帚豪。

例如 :

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
        HeaderView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headerView" forIndexPath:indexPath];     
        return headerView;
    }
    return nil;
}
- (void)reloadData;

函數(shù)描述重新加載集合視圖的所有數(shù)據(jù)碳竟。當需要重新加載集合視圖中的所有單元格時,請調(diào)用此方法狸臣。調(diào)用此方法需要謹慎莹桅,為了提高效率,集合視圖只顯示可見的單元格和補充視圖烛亦,而調(diào)用該方法將導致集合視圖放棄任何當前可見的單元格(包括占位符)诈泼,并基于數(shù)據(jù)源對象的當前狀態(tài)重新創(chuàng)建單元格。如果重新加載導致集合數(shù)據(jù)收縮煤禽,則集合視圖會相應地調(diào)整其滾動偏移铐达。不應在插入或刪除項目的動畫塊中間調(diào)用此方法,插入和刪除會自動導致集合的數(shù)據(jù)得到適當更新檬果。

- (NSInteger)numberOfItemsInSection:(NSInteger)section;

函數(shù)描述 :返回指定節(jié)中的單元格數(shù)量瓮孙。

參數(shù) :

section : 要對其進行單元格計數(shù)的節(jié)的索引唐断。

返回值 : 指定節(jié)中的單元格數(shù)量。

- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;

函數(shù)描述返回位于指定索引路徑的單元格的布局信息杭抠。要檢索特定單元格的布局信息脸甘,應該始終使用此方法,而不是直接查詢布局對象祈争。

參數(shù) :

indexPath : 項的索引路徑斤程。

返回值 : 項的布局屬性角寸,如果指定路徑上不存在項菩混,則為nil。

- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;

函數(shù)描述返回位于指定索引路徑的補充視圖的布局信息扁藕。要檢索特定補充視圖的布局信息沮峡,應該始終使用此方法,而不是直接查詢布局對象亿柑。

參數(shù) :

kind : 指定所需布局屬性定義的補充視圖類型的字符串邢疙,布局類負責定義它們支持的補充視圖的類型。

indexPath : 補充視圖的索引路徑望薄,此值的解釋取決于布局如何實現(xiàn)視圖疟游。例如,與節(jié)關(guān)聯(lián)的視圖可能只包含節(jié)的值痕支。

返回值 : 返回補充視圖的布局屬性颁虐,如果指定的補充視圖不存在,則返回nil卧须。

- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point;

函數(shù)描述返回集合視圖中指定點所在的單元格的索引路徑另绩,此方法依賴關(guān)聯(lián)的布局對象提供的布局信息來確定包含該點的單元格。

參數(shù) :

point : 集合視圖坐標系中的點花嘶。

返回值 : 在指定點上的單元格的索引路徑笋籽,如果在指定點上找不到單元格,則為nil椭员。

- (nullable NSIndexPath *)indexPathForCell:(UICollectionViewCell *)cell;

函數(shù)描述 : 返回指定單元格的索引路徑车海。

參數(shù) :

cell : 需要其索引路徑的單元格對象。

返回值 : 單元格的索引路徑隘击,如果指定的單元格不在集合視圖中容劳,則為nil。

- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath;

函數(shù)描述 : 返回指定索引路徑處的可見單元格對象闸度。

參數(shù) :

indexPath : 指定單元格的節(jié)(indexPath.section)和項(indexPath.item竭贩、indexPath.row)的索引路徑。

返回值 : 對應索引路徑處的單元格對象莺禁,如果單元格不可見或indexPath超出范圍留量,則為nil。

- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated;

函數(shù)描述滾動集合視圖內(nèi)容,直到指定的單元格可見楼熄。

參數(shù) :

indexPath : 要滾動到視圖中的單元格的索引路徑忆绰。

scrollPosition : 一個選項,指定滾動完成時單元格應放置在何處可岂。有關(guān)可能值的列表错敢,參閱UICollectionViewScrollPosition。

animated : 指定“YES”設置滾動行為的動畫缕粹,或指定“NO”立即調(diào)整滾動視圖的可見內(nèi)容稚茅。

  • UICollectionViewScrollPosition的枚舉值:
typedef NS_OPTIONS(NSUInteger, UICollectionViewScrollPosition) {
    //不要將項目滾動到視圖中。
    UICollectionViewScrollPositionNone                 = 0,
    //滾動以使項目位于集合視圖邊界的頂部平斩。
    //此選項與UICollectionViewScrollPositionCenteredVertical
    //和UICollectionViewScrollPositionBottom選項互斥亚享。
    UICollectionViewScrollPositionTop                  = 1 << 0, 
    //滾動以使項目在集合視圖中垂直居中。
    //此選項與UICollectionViewScrollPositionTop
    //和UICollectionViewScrollPositionBottom選項互斥
    UICollectionViewScrollPositionCenteredVertically   = 1 << 1, 
    //滾動以使項目位于集合視圖邊界的底部绘面。
    //此選項與UICollectionViewScrollPositionTop
    //和UICollectionViewScrollPositionCenteredVertical選項互斥欺税。
    UICollectionViewScrollPositionBottom               = 1 << 2,
    //滾動以使項目位于集合視圖邊界的左邊緣。
    //此選項與uiCollectionViewScrollPositionCenteredHorizontal
    //和UICollectionViewScrollPositionRight選項互斥。
    UICollectionViewScrollPositionLeft                 = 1 << 3, 
    //滾動以使項目在集合視圖中水平居中。
    //此選項與UICollectionViewScrollPositionLeft
    //和UICollectionViewScrollPositionRight選項互斥补疑。
    UICollectionViewScrollPositionCenteredHorizontally = 1 << 4, 
    //滾動以使項目位于集合視圖邊界的右邊緣。
    //此選項與UICollectionViewScrollPositionLeft
    //和uiCollectionViewScrollPositionCenteredHorizontal選項互斥歼秽。
    UICollectionViewScrollPositionRight                = 1 << 5 
};

UICollectionViewDataSource - 集合視圖數(shù)據(jù)源

采用UICollectionViewDataSource協(xié)議的對象負責提供集合視圖所需的數(shù)據(jù)和視圖。數(shù)據(jù)源對象表示應用程序的數(shù)據(jù)模型扣墩,并根據(jù)需要向集合視圖提供信息哲银。它還處理集合視圖用于顯示數(shù)據(jù)的單元格和補充視圖的創(chuàng)建和配置。所有數(shù)據(jù)源對象都必須實現(xiàn)collectionView:numberOfItemsInSection:和collectionView:cellForItemAtIndexPath:方法呻惕。這些方法負責返回集合視圖中的單元格數(shù)量以及單元格本身荆责。協(xié)議的其余方法是可選的,只有在集合視圖將單元格組織為多個節(jié)或為給定節(jié)提供頁眉補充試圖和頁腳補充視圖時才需要亚脆。

UICollectionViewDataSource中提供的常用函數(shù)
//提供視圖中節(jié)的個數(shù)做院,這個方法需要注意數(shù)據(jù)的行是否能與每一行有幾個單元格整除,不能整除時要多加一行
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
}

函數(shù)描述詢問數(shù)據(jù)源對象集合視圖中的節(jié)數(shù)濒持。如果不實現(xiàn)此方法键耕,則集合視圖使用默認值1。

參數(shù) :

collectionView : 請求此信息的集合視圖柑营。

返回值 : collectionView中的節(jié)數(shù)屈雄。

//每一節(jié)有幾個單元格
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
}

函數(shù)描述 :詢問數(shù)據(jù)源對象指定節(jié)中的單元格數(shù)量

參數(shù) :

collectionView : 請求此信息的集合視圖官套。

section : 標識collectionView中節(jié)的索引號酒奶,此索引值基于0蚁孔。

返回值 : 節(jié)中的單元格數(shù)量。

//為某個單元格提供顯示數(shù)據(jù)
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
}

函數(shù)描述向數(shù)據(jù)源對象詢問與集合視圖中指定項(indexPath.item惋嚎、indexPath.row)對應的單元格杠氢,此方法的實現(xiàn)負責為給定項(indexPath.item、indexPath.row)創(chuàng)建另伍、配置和返回適當?shù)膯卧?/strong>鼻百。為此,可以調(diào)用集合視圖的dequeueReusableCellWithReuseIdentifier:forIndexPath:方法并傳遞與所需單元格類型對應的重用標識符摆尝。該方法始終返回有效的單元格對象温艇。接收單元格后,應設置與相應項(indexPath.item结榄、indexPath.row)的數(shù)據(jù)相對應的任何屬性中贝,執(zhí)行任何其他所需配置囤捻,并返回單元格臼朗。

不需要在集合視圖的邊界內(nèi)設置單元格的位置。集合視圖使用每個單元格的布局對象提供的布局屬性自動設置每個單元格的位置蝎土。

如果集合視圖上的prefetchingEnabled設置為YES视哑,則在單元格出現(xiàn)之前調(diào)用此方法。使用collectionView:willDisplayCell:forItemAtIndexPath:delegate方法對單元格的外觀進行任何更改誊涯,以反映其視覺狀態(tài)(如選擇)挡毅。

此方法必須始終返回有效的視圖對象

參數(shù) :

collectionView : 請求此信息的集合視圖暴构。

indexPath : 指定項位置的索引路徑跪呈。

返回值 : 已配置的單元格對象。此方法不能返回nil取逾,nil會引發(fā)異常耗绿。

//為補充視圖提供顯示數(shù)據(jù)
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
}

函數(shù)描述詢問數(shù)據(jù)源對象提供要在集合視圖中顯示的補充視圖,此方法的實現(xiàn)負責創(chuàng)建砾隅、配置和返回所請求的適當補充視圖。為此晴埂,可以調(diào)用集合視圖的dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:方法并傳遞與所需視圖對應的信息精耐。該方法始終返回有效的視圖對象。接收到視圖后攀痊,應設置與要顯示的數(shù)據(jù)相對應的任何屬性棘街,執(zhí)行任何其他所需配置,并返回視圖飒箭。

不需要在集合視圖的邊界內(nèi)設置補充視圖的位置髓抑,集合視圖使用其布局對象提供的布局屬性設置每個視圖的位置碳却。

此方法必須始終返回有效的視圖對象冠胯。如果在特定情況下不需要補充視圖,則布局對象不應為該視圖創(chuàng)建屬性灼卢【蠢保或者,可以通過將相應屬性的hidden屬性設置為YES或?qū)傩缘腶lpha屬性設置為0來隱藏視圖。若要隱藏流布局中的頁眉補充視圖和頁腳補充視圖,還可以將這些視圖的寬度和高度設置為0拣宰。

參數(shù) :

collectionView : 請求此信息的集合視圖。

kind : 要提供的那種補充視圖類型西土。此字符串的值由支持補充視圖的布局對象定義。

indexPath : 指定新補充視圖位置的索引路徑拱燃。

返回值 : 已配置的補充視圖對象。此方法不能返回nil力惯,nil會引發(fā)異常碗誉。

UICollectionViewDelegate - 集合視圖代理

UICollectionViewDelegate協(xié)議定義了一些方法,管理集合視圖中單元格的選擇和突出顯示父晶,并對這些單元格執(zhí)行操作哮缺。本協(xié)議的方法都是可選的,該協(xié)議的許多方法都將NSIndexPath對象作為參數(shù)甲喝。為了支持集合視圖尝苇,UIKit在NSIndexPath上聲明了一個類別,使您能夠獲取表示的項索引(indexPath.item埠胖、indexPath.row)和節(jié)索引(indexPath.section)糠溜,并從項和索引值構(gòu)造新的索引路徑對象。由于項位于其節(jié)中直撤,因此通常必須先計算節(jié)索引號非竿,然后才能通過其索引號標識項。配置集合視圖對象時谋竖,請將代理對象分配給其代理屬性红柱。

UICollectionViewDelegate中提供的常用函數(shù):
//詢問委托選擇指定的項是否可以被選擇
-(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    return YES;
}

函數(shù)描述詢問代理指定的單元格是否可以被選擇。當用戶試圖在集合視圖中選擇單元格時蓖乘,集合視圖調(diào)用此方法锤悄;當以編程方式設置選擇時,則不會調(diào)用此方法嘉抒。如果不實現(xiàn)此方法零聚,則默認返回值為YES。

參數(shù) :

collectionView : 詢問選擇是否應更改的集合視圖對象众眨。

indexPath : 要選擇的單元格的索引路徑握牧。

返回值 : 如果可以選擇單元格,則為“YES”娩梨;如果不可以選擇單元格,則為“NO”览徒。

//選擇單元格之后觸發(fā)
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
}

函數(shù)描述通知代理已選擇指定索引路徑處的單元格狈定。當用戶在集合視圖中成功選擇單元格時,集合視圖調(diào)用此方法;當以編程方式設置選擇時纽什,則不會調(diào)用此方法措嵌。

collectionView : 通知選擇更改的集合視圖對象。

indexPath : 選定單元格的索引路徑芦缰。

//取消選擇單元格之后觸發(fā)
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
}

函數(shù)描述通知代理指定路徑處的單元格已被取消選擇企巢。當用戶在集合視圖中成功取消選擇的單元格時,集合視圖調(diào)用此方法让蕾;當以編程方式取消選擇單元格時浪规,則不會調(diào)用此方法。

參數(shù) :

collectionView : 通知選擇更改的集合視圖對象探孝。

indexPath : 取消選擇的單元格的索引路徑笋婿。

\color{red}{例如:創(chuàng)建一個可以多選的集合視圖示例:}

//多選要設置屬性allowsMultipleSelection為YES
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    //獲取當前要操作的Cell
    self.cell = (YSLSeeEvaluateCell *)[collectionView cellForItemAtIndexPath:indexPath];
    //可以對cell 的屬性做一些設置
    self.cell.title.textColor = [YSLUiUtils colorPrimary];
    CALayer *layer = [self.cell.title layer];
    [layer setBorderWidth:0.5f];
    [layer setBorderColor:[YSLUiUtils colorPrimary].CGColor];
    layer.cornerRadius = 3.0f;
}

- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
    //獲取當前要操作的Cell
    self.cell = (YSLSeeEvaluateCell *)[collectionView cellForItemAtIndexPath:indexPath];
    //可以對cell 的屬性做一些設置
    self.cell.title.textColor = [YSLUiUtils colorThree];
    CALayer *layer = [self.cell.title layer];
    [layer setBorderWidth:0.5f];
    [layer setBorderColor:[YSLUiUtils colorSeven].CGColor];
    layer.cornerRadius = 3.0f;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(8.0));

函數(shù)描述通知代理指定的單元格即將顯示在集合視圖中。集合視圖在將單元格添加到其內(nèi)容之前調(diào)用此方法顿颅。此方法檢測的是單元格何時添加缸濒,而不是檢測單元格何時出現(xiàn)。

參數(shù) :

collectionView : 正在添加單元格的集合視圖對象粱腻。

cell : 正在添加的單元格對象庇配。

indexPath : 單元格表示的數(shù)據(jù)項的索引路徑。

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;

函數(shù)描述通知代理指定的單元格已從集合視圖中移除绍些。此方法可以檢測單元格何時從集合視圖中移除捞慌,而不是檢測單元格何時消失。

參數(shù) :

collectionView : 移除單元格的集合視圖對象遇革。

cell : 已移除的單元格對象卿闹。

indexPath : 單元格表示的數(shù)據(jù)項的索引路徑。

- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(8.0));

函數(shù)描述通知代理指定的補充視圖即將顯示在集合視圖中萝快。集合視圖在向其內(nèi)容添加補充視圖之前調(diào)用此方法锻霎。此方法檢測補充視圖何時添加,而不是檢測補充視圖何時出現(xiàn)揪漩。

參數(shù) :

collectionView : 正在添加補充視圖的集合視圖對象旋恼。

view : 正在添加的補充視圖。

elementKind : 補充視圖的類型奄容。此字符串由顯示視圖的布局定義冰更。

indexPath : 補充視圖表示的數(shù)據(jù)項的索引路徑。

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;

函數(shù)描述通知代理指定的補充視圖已從集合視圖中移除昂勒。使用此方法可以檢測何時從集合視圖中移除補充視圖蜀细,而不是檢測補充視圖何時消失。

參數(shù) :

collectionView : 移除輔助視圖的集合視圖對象戈盈。

view : 已移除的補充視圖奠衔。

elementKind : 補充視圖的類型谆刨。此字符串由顯示視圖的布局定義。

indexPath : 補充視圖表示的數(shù)據(jù)項的索引路徑归斤。

UICollectionViewFlowLayout - 流布局管理器

UICollectionViewFlowLayout是一種流布局管理器痊夭,即從左到右從上到下布局,每行包含盡可能多的單元格脏里,單元格可以是相同大小或不同大小她我。以水平布局為例,單元格從左到右排列迫横,但如果集合視圖的高度允許顯示更多的單元格番舆,單元格也會從上到下盡可能多的排列,排列不下的才會滾動顯示员淫。

如圖:這是一個1節(jié)中顯示10個單元格的水平布局合蔽,但第一感覺似乎是垂直布局了,這和UICollectionView的高度有關(guān)系介返,因為水平布局時拴事,還考慮到了上下布局的關(guān)系,所以想要UICollectionView一行水平方向展示單元格圣蝎,最好鉗制UICollectionView的高度刃宵。

截屏2022-08-12 09.52.20.png

流布局使用集合視圖的代理對象(UICollectionViewDelegate)來確定每個節(jié)中單元格、頁眉補充視圖和頁腳補充視圖的大小徘公,代理對象必須符合UICollectionViewDelegateFlowLayout協(xié)議(即通過集合視圖delegate屬性設置代理對象牲证,但代理對象需要遵循UICollectionViewDelegateFlowLayout協(xié)議)。使用代理可以動態(tài)調(diào)整布局信息关面。例如需要使用代理對象為網(wǎng)格中的單元格指定不同的大小坦袍。如果不提供代理對象,則流布局使用使用此類屬性設置的默認值等太。

流布局使用一個方向上的固定距離和另一個方向上的可滾動距離來布局其內(nèi)容捂齐。例如在垂直滾動的網(wǎng)格狀布局中,網(wǎng)格布局內(nèi)容的寬度被限制為相應集合視圖的寬度缩抡,而內(nèi)容的高度則動態(tài)調(diào)整以匹配網(wǎng)格布局中要顯示的節(jié)數(shù)和單元格數(shù)奠宜。默認情況下,布局配置為垂直滾動瞻想,但可以使用scrollDirection屬性配置滾動方向压真。

流布局中的每個節(jié)都可以有自己的自定義頁眉補充視圖和頁腳補充視圖。若要為視圖配置頁眉補充視圖或頁腳補充視圖蘑险,必須將頁眉補充視圖或頁腳補充視圖的大小配置為非零(zero)值滴肿。可以通過實現(xiàn)適當?shù)拇矸椒ɑ驗閔eaderReferenceSize和footerReferenceSize屬性分配適當?shù)闹祦硗瓿纱瞬僮鞯杵H绻撁蓟蝽撃_大小為零(zero)嘴高,則相應的視圖不會添加到集合視圖中竿音。

UICollectionViewFlowLayout流布局管理器的一些常見屬性
@property (nonatomic) CGFloat minimumLineSpacing;

屬性描述 :網(wǎng)格狀布局中和屎,一節(jié)單元格之內(nèi)拴驮,每行(或每列)單元格之間要使用的最小間距。如果代理對象未實現(xiàn)collectionView:layout:minimumLineSpacingForSectionAtIndex:方法柴信,則流布局使用此屬性中的值設置節(jié)中單元格的行(或列)間距套啤。

對于垂直滾動的網(wǎng)格狀布局,此值表示連續(xù)行之間的最小間距随常;對于水平滾動的網(wǎng)格狀布局潜沦,此值表示連續(xù)列之間的最小間距。此間距不應用于頁眉與第一行之間或最后一行與頁腳之間的間距绪氛。此屬性的默認值為10.0唆鸡。

垂直布局時,minimumLineSpacing決定的行間距:

截屏2022-08-12 11.58.57.png

水平布局時枣察,minimumLineSpacing決定的列間距:

截屏2022-08-12 11.47.43.png
@property (nonatomic) CGFloat minimumInteritemSpacing;

屬性描述 :網(wǎng)格狀布局中争占,一節(jié)單元格之內(nèi),要在同一行(或同一列)中的單元格與單元格之間使用的最小間距序目。如果委托對象未實現(xiàn)collectionView:layout:minimumitemSpacingForSectionAtIndex:方法臂痕,則流布局使用此屬性中的值設置同一行(或同一列)中單元格與單元格之間的間距。

對于垂直滾動的網(wǎng)格狀布局猿涨,此值表示同一行中單元格與單元格之間的最小間距握童;對于水平滾動網(wǎng)格狀布局,此值表示同一列中單元格與單元格之間的最小間距叛赚。此間距用于計算一行可以容納多少個項目澡绩,但在確定項目數(shù)之后,實際間距可能會向上調(diào)整俺附。此屬性的默認值為10.0肥卡。

垂直布局時,minimumInteritemSpacing決定的單元格與單元格間距:

截屏2022-08-12 13.59.52.png

水平布局時昙读,minimumInteritemSpacing決定的單元格與單元格間距:

截屏2022-08-12 14.19.52.png

注意minimumLineSpacing屬性與minimumInteritemSpacing屬性間的配合與其默認值召调,例如數(shù)據(jù)需要水平一行展示時,設置單元格間的間距需要使用minimumLineSpacing而非minimumInteritemSpacing蛮浑。

@property (nonatomic) CGSize itemSize;

屬性描述用于單元格的默認大小唠叛。如果代理未實現(xiàn)collectionView:layout:sizeForItemAtIndexPath:方法,則流布局使用此屬性中的值設置每個單元格的大小沮稚,這將導致所有單元格都具有相同的大小艺沼,默認大小值為CGSizeMake(50, 50)。

@property (nonatomic) CGSize estimatedItemSize API_AVAILABLE(ios(8.0));

屬性描述集合視圖中單元格的預估大小蕴掏。當單元格動態(tài)調(diào)整其大小時障般,提供估計的單元格大小可以提高集合視圖的性能调鲸,通過指定估計值,集合視圖可以推遲確定其內(nèi)容實際大小所需的某些計算挽荡。沒有顯示在屏幕上的單元格被假定為估計高度藐石。

此屬性的默認值為CGSizeZero。如果將其設置為任何其他值定拟,則集合視圖將使用單元格的preferredLayoutAttributesFittingAttributes:方法查詢每個單元格的實際大小于微。如果所有單元格高度相同,請使用itemSize屬性而不是此屬性指定單元格大小青自。

@property (nonatomic) UICollectionViewScrollDirection scrollDirection;

屬性描述網(wǎng)格狀布局的滾動方向株依。網(wǎng)格布局只沿著一個軸滾動,可以是水平方向延窜,也可以是垂直方向恋腕。對于沿著軸水平方向滾動的網(wǎng)格布局,集合視圖的高度用作內(nèi)容的起始高度逆瑞;對于沿著軸垂直方向滾動的網(wǎng)格布局荠藤,集合視圖的寬度用作內(nèi)容的起始寬度。這個屬性的默認值是UICollectionViewScrollDirectionVertical呆万。

  • UICollectionViewScrollDirection的枚舉值 :
typedef NS_ENUM(NSInteger, UICollectionViewScrollDirection) {
    UICollectionViewScrollDirectionVertical, //垂直滾動
    UICollectionViewScrollDirectionHorizontal //水平滾動商源。
};
@property (nonatomic) CGSize headerReferenceSize;

屬性描述用于節(jié)頁眉補充視圖的默認大小。如果代理未實現(xiàn)collectionView:layout:referenceSizeForHeaderInSection:方法谋减,則流布局對象使用此屬性中的值設置的默認的頁眉補充視圖大小牡彻。

在布局過程中,只使用與相應滾動方向相對應的大小出爹。例如對于垂直滾動方向庄吼,布局對象使用屬性返回的高度值(在這種情況下,頁眉的寬度將設置為集合視圖的寬度)严就。如果相應滾動維度中的大小為0总寻,則不添加頁眉補充視圖,默認大小值為CGSizeMake(0, 0)梢为。

@property (nonatomic) CGSize footerReferenceSize;

屬性描述用于節(jié)頁腳補充視圖的默認大小渐行。如果代理未實現(xiàn)collectionView:layout:referenceSizeForFooternSection:method,則流布局對象將使用為此屬性中的值設置的默認頁腳補充視圖大小铸董。

在布局過程中祟印,只使用與相應滾動方向相對應的大小。例如對于垂直滾動方向粟害,布局對象使用此屬性指定的高度值(在這種情況下蕴忆,頁腳的寬度將設置為集合視圖的寬度)。如果相應滾動維度中的大小為0悲幅,則不添加頁腳補充視圖套鹅,默認大小值為CGSizeMake(0, 0)站蝠。

@property (nonatomic) UIEdgeInsets sectionInset;

屬性描述用于在每個節(jié)中布局單元格的內(nèi)邊距。sectionInset屬性的類型是UIEdgeInsets結(jié)構(gòu)體卓鹿,UIEdgeInsets包括:top(上邊界)菱魔,left(左邊界),bottom(下邊界)减牺,right(右邊界)4個成員豌习。UIEdgeInsetsMake函數(shù)可以創(chuàng)建UIEdgeInsets結(jié)構(gòu)體實例。如果代理對象未實現(xiàn)collectionView:layout:insetForSectionAtIndex:方法拔疚,則流布局使用此屬性中的值設置每個節(jié)布局單元格的內(nèi)邊距。默認的邊邊距插入都設置為0既荚。

截屏2022-08-12 16.53.08.png
@property (nonatomic) BOOL sectionHeadersPinToVisibleBounds API_AVAILABLE(ios(9.0));

屬性描述一個布爾值碾篡,當此屬性為“YES”時畔况,設置懸停效果。節(jié)中的頁眉補充視圖將與內(nèi)容一起滾動,滾動方向為垂直時催束,直到它們到達屏幕頂部,此時它們將固定到集合視圖的上沿(即頁眉補充視圖懸停)涂召;滾動方向為水平時麻捻,直到它們到達屏幕左側(cè),此時它們將固定到集合視圖的左沿(即頁眉補充視圖懸停)兼蕊。每個滾動到屏幕頂部的新頁眉補充視圖都會將先前固定的頁眉補充視圖推離屏幕初厚。此屬性的默認值為“NO”。

@property (nonatomic) BOOL sectionFootersPinToVisibleBounds API_AVAILABLE(ios(9.0));

屬性描述一個布爾值孙技,當此屬性為“YES”時产禾,設置懸停效果。節(jié)中的頁腳視圖補充視圖將隨內(nèi)容一起滾動牵啦,滾動方向為垂直時亚情,直到它們到達屏幕底部,此時它們將固定到集合視圖的下沿(即頁腳補充視圖懸停)哈雏;滾動方向為水平時楞件,直到它們到達屏幕右側(cè),此時它們將固定到集合視圖的右沿(即頁眉補充視圖懸停)裳瘪。滾動到屏幕底部的每個新頁腳補充視圖都會將先前固定的頁腳補充視圖推離屏幕土浸。此屬性的默認值為“NO”。

UICollectionViewDelegateFlowLayout提供的一些方法
//動態(tài)設置每個Item的尺寸大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
}

函數(shù)描述詢問代理指定單元格的大小盹愚。如果不實現(xiàn)此方法栅迄,則流布局將使用其itemSize屬性中的值來設置單元格的大小。此方法的實現(xiàn)可以返回一組固定的大小或根據(jù)單元格的內(nèi)容動態(tài)調(diào)整大小皆怕。

流布局不會裁剪單元格邊界以使其適合網(wǎng)格布局邊界毅舆,因此返回的值必須允許單元格在集合視圖中完全顯示西篓。例如在垂直滾動的網(wǎng)格狀布局中,單個單元格的寬度不能超過集合視圖本身的寬度(減去任何節(jié)插入)憋活。但是在滾動方向上岂津,單元格可以大于集合視圖,因為剩余的內(nèi)容始終可以滾動到視圖中悦即。

參數(shù) :

collectionView : 顯示流布局的集合視圖對象吮成。

collectionViewLayout : 請求信息的布局對象。

indexPath :單元格的索引路徑辜梳。

返回值 : 指定單元格的寬度和高度粱甫。兩個值都必須大于0。

//動態(tài)設置每個分區(qū)的EdgeInsets
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
}

函數(shù)描述要求代理將內(nèi)邊距應用于指定節(jié)中的內(nèi)容作瞄。如果不實現(xiàn)此方法茶宵,則流布局將使用其sectionInset屬性中的值來設置內(nèi)邊距。此方法的實現(xiàn)可以返回一組固定的邊距大小宗挥,或者為每個部分返回不同的邊距大小乌庶。

節(jié)插入是僅應用于節(jié)中內(nèi)容的邊距,它們表示頁眉補充視圖和第一行單元格之間以及最后一行單元格和頁腳補充視圖之間的距離契耿,它們還指示一行單元格兩邊的間距瞒大,它們不會影響頁眉補充視圖或頁腳補充視圖本身的大小。

參數(shù) :

collectionView : 顯示流布局的集合視圖對象搪桂。

collectionViewLayout : 請求信息的布局對象透敌。

section : 需要插入的節(jié)的索引號。

返回值 : 應用于節(jié)中內(nèi)容的內(nèi)邊距锅棕。

//動態(tài)設置每行的間距大小
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section{
}

函數(shù)描述詢問代理指定節(jié)中連續(xù)行或列之間的間距拙泽。如果未實現(xiàn)此方法,則流布局將使用其minimumLineSpacing屬性中的值來設置行或列之間的間距裸燎。此方法的實現(xiàn)可以為每個節(jié)中連續(xù)行或列之間返回固定間距值或不同的間距值顾瞻。

對于垂直滾動網(wǎng)格,此值表示連續(xù)行之間的最小間距德绿。對于水平滾動網(wǎng)格荷荤,此值表示連續(xù)列之間的最小間距。此間距不應用于頁眉與第一行之間或最后一行與頁腳之間的間距移稳。

參數(shù) :

collectionView : 顯示流布局的集合視圖對象蕴纳。

collectionViewLayout : 請求信息的布局對象。

section : 需要行或列間距的節(jié)的索引號个粱。

返回值 : 分段中連續(xù)線之間的最小空間(以點為單位)古毛。

//動態(tài)設置每個單元格的間距大小
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section{
}

函數(shù)描述要求代理指定節(jié)的行或列中連續(xù)單元格之間的間距。如果未實現(xiàn)此方法,則流布局將使用其minimumitemSpacing屬性中的值來設置單元格與單元格之間的間距稻薇。此方法的實現(xiàn)可以為每個節(jié)的單元格與單元格之間返回固定間距值或不同的間距值嫂冻。

對于垂直滾動網(wǎng)格,此值表示同一行中單元格與單元格之間的最小間距塞椎。對于水平滾動網(wǎng)格桨仿,此值表示同一列中單元格與單元格之間的最小間距。此間距用于計算一行可以容納多少個單元格案狠,但在確定單元格數(shù)之后服傍,實際間距可能會向上調(diào)整。

參數(shù) :

collectionView : 顯示流布局的集合視圖對象骂铁。

collectionViewLayout : 請求信息的布局對象吹零。

section : 需要單元格與單元格間距的節(jié)的索引號。

返回值 : 分段線中連續(xù)單元格之間的最小間距(以點為單位)从铲。

//動態(tài)設置某個分區(qū)頭視圖大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section{
}

函數(shù)描述詢問代理指定節(jié)中頁眉補充視圖的大小瘪校。如果不實現(xiàn)此方法,則流布局將使用其headerReferenceSize屬性中的值設置頁眉充視視圖的大小名段。在布局過程中,只使用與相應滾動方向相對應的大小泣懊,例如對于垂直滾動方向伸辟,布局對象使用方法返回的高度值(在這種情況下,頁眉的寬度將設置為集合視圖的寬度)馍刮。如果相應滾動維度中的大小為0信夫,則不添加頁眉補充視圖。

參數(shù):

collectionView : 顯示流布局的集合視圖對象卡啰。

collectionViewLayout : 請求信息的布局對象静稻。

section : 正在請求其頁眉補充視圖大小的節(jié)的索引。

返回值 : 頁眉補充視圖的大小匈辱。如果返回的值為CGSizeMake(0, 0)振湾,則不添加頁眉補充視圖。

//動態(tài)設置某個分區(qū)尾視圖大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section{
}

函數(shù)描述詢問代理指定節(jié)中頁腳補充視圖的大小亡脸。如果不實現(xiàn)此方法押搪,則流布局將使用其footerReferenceSize屬性中的值來設置頁腳補充視圖的大小。在布局過程中浅碾,只使用與相應滾動方向相對應的大小大州,例如對于垂直滾動方向,布局對象使用此屬性指定的高度值(在這種情況下垂谢,頁腳的寬度將設置為集合視圖的寬度)厦画。如果相應滾動維度中的大小為0,則不添加頁腳滥朱。

參數(shù) :

collectionView : 顯示流布局的集合視圖對象根暑。

collectionViewLayout : 請求信息的布局對象力试。

section : 正在請求其頁腳補充視圖大小的節(jié)的索引。

返回值 : 頁腳補充視圖的大小购裙。如果返回的值為CGSizeMake(0, 0)懂版,則不添加頁腳補充視圖。

UICollectionViewLayout - 布局信息的抽象基類

用于為集合視圖生成布局信息的抽象基類躏率。布局對象的工作是確定單元格躯畴、補充視圖和裝飾視圖在集合視圖范圍內(nèi)的位置,并在需要時向集合視圖報告該信息薇芝。然后集合視圖將提供的布局信息應用于相應的視圖蓬抄,以便在屏幕上顯示它們。

必須子類化UICollectionViewLayout才能使用它夯到,不過在考慮子類化之前嚷缭,應該查看UICollectionViewFlowLayout類,看看它是否能夠適應布局的需求耍贾。

UICollectionViewLayout常用屬性
@property (nullable, nonatomic, readonly) UICollectionView *collectionView;

屬性描述當前使用此布局對象的集合視圖對象阅爽。

UICollectionViewLayout常用函數(shù)
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;

函數(shù)描述 :初始化相關(guān)函數(shù)。

- (void)invalidateLayout;

函數(shù)描述使當前布局失效并觸發(fā)布局更新荐开「段蹋可以隨時調(diào)用此方法來更新布局信息。此方法使集合視圖本身的布局無效晃听,并立即返回百侧。因此,可以從同一代碼塊多次調(diào)用此方法能扒,而無需觸發(fā)多個布局更新佣渴。實際布局更新發(fā)生在下一個視圖布局更新周期中。如果重寫此方法初斑,則必須在實現(xiàn)中的某個點調(diào)用super辛润。

\color{red}{例如更新集合視圖布局的代碼片段 } invalidateLayout使用示例

//獲取集合視圖布局
NHAlignmentFlowLayout *layout = (NHAlignmentFlowLayout *)self.collectionView.collectionViewLayout;
//判斷是否顯示頁面邊距與商品邊距
BOOL hasBorder = (dataModel.style_border == 0) ? YES : NO;
if (!hasBorder) {
    //不頁面邊距與商品邊距
   self.style_goods_item_margins = 0.0;
   self.style_goods_page_margins = 0.0;
}
//行間距
layout.minimumLineSpacing = self.style_goods_item_margins;
//列間距
layout.minimumInteritemSpacing = self.style_goods_item_margins;
//cell大小
layout.itemSize = [(NSValue *)dataModel.extraInfo CGSizeValue];
//禁用動畫轉(zhuǎn)換視圖
[UIView performWithoutAnimation:^{
    //刷新集合視圖
    [self.collectionView reloadData];
    //立即觸發(fā)集合視圖更新布局
    [self.collectionView.collectionViewLayout invalidateLayout];
    //設置集合視圖內(nèi)容內(nèi)間距
    self.collectionView.contentInset = UIEdgeInsetsMake(self.style_goods_page_margins
                                                                , self.style_goods_page_margins
                                                                , self.style_goods_page_margins
                                                                , self.style_goods_page_margins);
}];

UICollectionViewLayout - (UISubclassingHooks)分類

UICollectionViewLayout (UISubclassingHooks)常用屬性
@property(nonatomic, readonly) CGSize collectionViewContentSize;

屬性描述返回集合視圖內(nèi)容的寬度和高度。子類必須覆蓋此屬性并使用它來返回集合視圖內(nèi)容的寬度和高度越平,這些值表示所有內(nèi)容的寬度和高度频蛔,而不僅僅是當前可見的內(nèi)容。 集合視圖使用此信息來配置其自己的內(nèi)容大小以用于滾動目的秦叛,此屬性默認值CGSizeZero晦溪。

\color{red}{例如獲取集合視圖內(nèi)容的高度代碼片段 } collectionViewContentSize使用示例

- (void)setShopActivity:(NSArray<YSCResponseModelShopStreetShopActivityModel> *)shopActivity{
    _shopActivity = shopActivity;
    [self setNeedsLayout];
    [self layoutIfNeeded];
    [self.activityCollectionView reloadData];
    CGFloat activityCollectionViewHeoght = self.activityCollectionView.collectionViewLayout.collectionViewContentSize.height;
    [self.activityCollectionView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.height.mas_equalTo(activityCollectionViewHeoght);
    }];
}
UICollectionViewLayout (UISubclassingHooks)常用函數(shù)
- (void)prepareLayout;

函數(shù)描述集合視圖第一次顯示其內(nèi)容時,以及布局因視圖更改而顯式或隱式無效時挣跋,集合視圖首先調(diào)用這個方法三圆,讓布局對象有機會為即將到來的布局操作做準備。此方法的默認實現(xiàn)什么也不做。 子類可以覆蓋它并使用它來設置數(shù)據(jù)結(jié)構(gòu)或執(zhí)行稍后執(zhí)行布局之前所需的任何初始計算舟肉。

- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; 

函數(shù)描述返回指定矩形中所有單元格和視圖的布局屬性修噪。子類必須覆蓋此方法并使用它來返指定矩形中的所有可視元素的布局信息,這些可視元素包括單元格路媚、補充視圖和裝飾視圖黄琼。

在創(chuàng)建布局屬性時,始終要創(chuàng)建一個UICollectionViewLayoutAttributes對象整慎,該對象表示正確的元素類型(單元格脏款、補充元素或裝飾元素)。集合視圖區(qū)分每種類型的屬性裤园,并使用這些信息來決定創(chuàng)建哪些視圖以及如何管理它們撤师。

參數(shù) :

rect : 包含可視元素的矩形(在集合視圖的坐標系統(tǒng)中指定)。

返回值 :UICollectionViewLayoutAttributes對象的數(shù)組拧揽,表示單元格剃盾、補充視圖和裝飾視圖的布局信息。默認實現(xiàn)返回nil淤袜。

- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;

函數(shù)描述返回在指定索引路徑上的單元格布局屬性痒谴。子類必須重寫此方法,并使用它返回集合視圖中單元格的布局信息铡羡。使用此方法只能為相應的單元格提供布局信息闰歪,不要將其用于補充視圖或裝飾視圖。

參數(shù) :

indexPath : 單元格的索引路徑蓖墅。

返回值 : 包含要應用于單元格的布局屬性對象。

UICollectionViewLayoutAttributes - 布局屬性對象

管理集合視圖中給定可視元素的布局相關(guān)屬性的布局對象临扮。當集合視圖要求布局對象創(chuàng)建該類的實例時會創(chuàng)建該類的實例论矾。然后集合視圖使用布局信息在其邊界內(nèi)定位單元格和補充視圖。

UICollectionViewLayoutAttributes常用屬性
@property (nonatomic) CGRect frame;

屬性描述可視元素的框架矩形杆勇√翱牵框架矩形以點為單位測量,并在集合視圖的坐標系中指定蚜退,設置此屬性的值還會設置center和size屬性的值闰靴。

@property (nonatomic) CGPoint center;

屬性描述可視元素的中心點。中心點在集合視圖的坐標系中指定钻注,設置此屬性的值也會更新frame屬性中矩形的原點蚂且。

@property (nonatomic) CGSize size;

屬性描述 : 可視元素的大小。設置此屬性的值還會更改frame和bounds屬性返回的矩形的大小幅恋。

@property (nonatomic) CGRect bounds API_AVAILABLE(ios(7.0));

屬性描述可視元素的的邊界杏死。設置邊界時,邊界矩形的原點必須始終位于 (0, 0)。 更改邊界矩形也會更改size屬性中的值以匹配新的邊界大小淑翼。

@property (nonatomic) CGFloat alpha;

屬性描述可視元素的透明度腐巢。可能的值介于0.0??(透明)和1.0(不透明)之間玄括, 默認值為 1.0冯丙。

@property (nonatomic) NSInteger zIndex;

屬性描述指定可視元素在z軸上的位置。此屬性用于確定布局期間可視元素的從前到后的順序遭京, 具有較高索引值的可視元素出現(xiàn)在具有較低值的可視元素之上胃惜,具有相同值的項目具有未確定的順序,此屬性的默認值為 0洁墙。

@property (nonatomic, getter=isHidden) BOOL hidden;

屬性描述 :確定可視元素當前是否顯示蛹疯。此屬性的默認值為 NO, 作為優(yōu)化热监,如果此屬性設置為YES捺弦,則集合視圖可能不會創(chuàng)建相應的可視元素。

@property (nonatomic, strong) NSIndexPath *indexPath;

屬性描述集合視圖中可視元素(單元格孝扛、頁眉補充視圖列吼、頁腳補充視圖)的索引路徑。索引路徑包含節(jié)(section)的索引和該節(jié)內(nèi)的項(item或row)的索引苦始,這兩個唯一值標識了集合視圖中相應可視元素的位置寞钥。

@property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;

屬性描述可視元素的類型∧把。可以使用此屬性中的值來區(qū)分布局屬性是用于單元格理郑、補充視圖還是裝飾視圖。

  • UICollectionElementCategory枚舉
typedef NS_ENUM(NSUInteger, UICollectionElementCategory) {
    //單元格咨油。
    UICollectionElementCategoryCell,
    //補充視圖
    UICollectionElementCategorySupplementaryView,
    //裝飾視圖
    UICollectionElementCategoryDecorationView
};
@property (nonatomic, readonly, nullable) NSString *representedElementKind; 

屬性描述目標視圖的特定于布局的標識符您炉。可以使用此屬性中的值來標識與布局屬性關(guān)聯(lián)的補充或裝飾視圖的特定用途役电,如果表示的ElementCategory屬性包含值UICollectionElementCategoryCell赚爵,則此屬性為nil。

瀑布流布局的簡單實現(xiàn)

本來作為一個小白級別的UICollectionView使用者法瑟,我是很開心的冀膝,直到有一天項目經(jīng)理找了一張圖甩在我的臉上,并溫和的說霎挟,當單元格內(nèi)容多的時候窝剖,他想要的布局是這樣的:

winfred_zen的博客.png

我覺得差不多就得了,但是產(chǎn)品經(jīng)理想要他覺得氓扛,不想要我覺得枯芬,所以我只能再次發(fā)揮面向百度編程的本領(lǐng)论笔,發(fā)現(xiàn)這是流布局的一種 - 瀑布流布局,網(wǎng)上有很多的實現(xiàn)方法千所,而我只想說狂魔,搞這么多樣式的布局干嘛,都是做程序員的淫痰,你們別再卷了最楷,我已經(jīng)學不動了,放棄那些奇奇怪怪的布局吧待错。但是代碼該寫還是要寫籽孙,誰讓人家是經(jīng)理呢!火俄!

//
//  TSChannelFourViewController.m

#import "TSChannelFourViewController.h"

/**
 模型
 */
@interface CategoryItemObject : NSObject

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray<NSString *> *categoryArray;

@end

@implementation CategoryItemObject

- (instancetype)init {
    self = [super init];
    if (self) {
        self.title = @"分類名稱";
        self.categoryArray = @[@"《清明上河圖》描繪的是清明時節(jié)北宋都城汴京(今河南開封)東角子門內(nèi)外和汴河兩岸的繁華熱鬧景象犯建,再現(xiàn)了12世紀北宋全盛時期都城汴京的生活面貌。",
            @"《千里江山圖》卷是北宋畫家王希孟傳世的唯一作品瓜客。此圖描繪了祖園的錦繡河山适瓦,一向被視為宋代青綠山水中的巨制杰構(gòu)。",
            @"《步輦圖》畫幅描繪的是唐太宗李世民在宮內(nèi)接見松贊干布派來的吐蕃使臣祿東贊的情景谱仪,全畫線條純熟玻熙,設色濃重、鮮艷疯攒,是一幅出色的工筆重彩人物畫作品嗦随。",
            @"《平復帖》的書寫年代距今己有1700余年,是現(xiàn)存年代最早并真實可信的西晉名家法帖敬尺,在中國書法史上占有重要地位枚尼。",
            @"長信宮燈,西漢的一件銅鎏金青銅器砂吞,大約制成于公元前151年姑原。這盞燈于1968年在中山蜻王劉勝之妻竇綰墓出土,燈身上刻有“長信”的銘文呜舒,被譽為“中華第一燈“。",
            @"越王勾踐劍笨奠,1965年12月出土于湖北江陵望山一號楚墓袭蝗,出土時插在漆木劍鞘里,出鞘時仍然寒光閃閃般婆,耀人眼目到腥,被譽為“天下第一劍”。",
            @"錯金博山爐蔚袍,這是西漢村作為香薰乡范、薰爐用的青銅器配名,因為造型象征的是傳說中的海上仙山一博山:所以叫做博山爐。整體精致華美晋辆,被稱為“史上最豪華的香薰”渠脉。",
            @"曾侯乙編鐘,1978年出土于湖北隨州曾候乙墓瓶佳,架長7.48米芋膘、高2.65米,全套編鐘共六十五件霸饲,能演奏五聲为朋、六聲或七聲音階的樂曲。",
            @"曾侯乙尊盤厚脉,1978年出土于湖北隨州市曾侯乙墓习寸,由尊和盤兩件器物組成,商周青銅器的巔峰之作傻工。",
            ];
    }
    return self;
}

@end

/**
 布局
 */
@interface UICollectionViewWaterFallLayout : UICollectionViewFlowLayout

@end

@interface UICollectionViewWaterFallLayout()

//一行單元格計數(shù)
@property (nonatomic, assign) NSInteger oneRowCountCell;
//存放節(jié)中第一行Y軸最小的單元格布局對象字典
@property (nonatomic, strong) NSMutableDictionary<NSString *,UICollectionViewLayoutAttributes *> *oneRowminYCellAttributesDic;
//存放已經(jīng)更改布局的布局對象字典
@property (nonatomic, strong) NSMutableDictionary<NSString *,UICollectionViewLayoutAttributes *> *layoutedAttributesDic;

@end


@implementation UICollectionViewWaterFallLayout

/// 準備布局
- (void)prepareLayout {
    if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
        //集合視圖可以顯示內(nèi)容的寬度
        CGFloat collectionContentWidth = self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right;
        //單元格加單元格間距的寬度
        CGFloat cellAndItemSpacingwidth = 0;
        //構(gòu)造用于檢索的框架矩形
        CGSize size = super.collectionViewContentSize;
        CGRect rect = CGRectMake(self.collectionView.frame.origin.x, self.collectionView.frame.origin.y, size.width, size.height);
        //檢索構(gòu)造的框架矩形中所有可視元素的布局屬性
        NSArray *originalAttributes = [super layoutAttributesForElementsInRect:rect];
        //遍歷構(gòu)造的框架矩形中所有可視元素的布局屬性
        for (UICollectionViewLayoutAttributes *attributes in originalAttributes) {
            //如果可視元素為單元格
            if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
                //累加一個單元格的寬度
                cellAndItemSpacingwidth += attributes.size.width;
                //如果單元格加單元格間距的寬度小于等于集合視圖顯示內(nèi)容的寬度
                if (cellAndItemSpacingwidth <= collectionContentWidth) {
                    //一行單元格計數(shù)增加1
                    self.oneRowCountCell ++;
                    //累加一個單元格間距
                    cellAndItemSpacingwidth += self.minimumInteritemSpacing;
                } else {
                    break;
                }
            }
        }
        //強制一行單元格計數(shù)最小為1
        if (self.oneRowCountCell <= 0) {
            self.oneRowCountCell = 1;
        }
    }
}

/// 返回指定矩形中所有單元格和視圖的布局屬性
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
        //返回指定矩形中所有單元格和視圖的布局屬性
        NSArray *originalAttributes = [super layoutAttributesForElementsInRect:rect];
        NSMutableArray *updatedAttributes = [NSMutableArray arrayWithArray:originalAttributes];
        //遍歷構(gòu)造的框架矩形中所有可視元素的布局屬性
        for (UICollectionViewLayoutAttributes *attributes in originalAttributes) {
            //如果可視元素為單元格
            if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
                //如果單元格的項索引小于一行單元格計數(shù)(說明是第一行單元格)
                if (attributes.indexPath.item < self.oneRowCountCell) {
                    //獲取節(jié)中Y軸最小的單元格布局對象
                    UICollectionViewLayoutAttributes *minYCellAttributes = [self.oneRowminYCellAttributesDic objectForKey:[NSString stringWithFormat:@"%ld",(long)attributes.indexPath.section]];
                    //如果獲取的單元格布局對象為nil
                    if (minYCellAttributes == nil) {
                        //將這個單元格布局對象作為節(jié)中Y軸最小的單元格存入字典
                        [self.oneRowminYCellAttributesDic setValue:attributes forKey:[NSString stringWithFormat:@"%ld",(long)attributes.indexPath.section]];
                    } else {
                        //如果在字典中獲取了單元格布局對象霞溪,將當前這個單元格布局對象與字典中獲取的進行最小Y軸比較
                        //利用字典Key相同的值會進行覆蓋,保證存儲的是第一行單元格中Y軸以最小的單元格布局對象
                        if (CGRectGetMinY(attributes.frame) < CGRectGetMinY(minYCellAttributes.frame)) {
                            [self.oneRowminYCellAttributesDic setValue:attributes forKey:[NSString stringWithFormat:@"%ld",(long)attributes.indexPath.section]];
                        }
                    }
                }
            }
        }
        //再次遍歷構(gòu)造的框架矩形中所有可視元素的布局屬性
        for (UICollectionViewLayoutAttributes *attributes in originalAttributes) {
            //如果可視元素為單元格
            if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
                //獲取單元格布局屬性對象在數(shù)組中的索引
                NSUInteger index = [updatedAttributes indexOfObject:attributes];
                //返回位于指定索引路徑的單元格的布局信息
                UICollectionViewLayoutAttributes *cellLayoutAttributes = [self layoutAttributesForItemAtIndexPath:attributes.indexPath];
                //如果單元格布局對象的項索引小于一行單元格計數(shù)(說明是第一行單元格)
                if (cellLayoutAttributes.indexPath.item < self.oneRowCountCell) {
                    //獲取一節(jié)中第一行Y軸最小的單元格布局對象
                    UICollectionViewLayoutAttributes *minYCellAttributes = [self.oneRowminYCellAttributesDic objectForKey:[NSString stringWithFormat:@"%ld",(long)cellLayoutAttributes.indexPath.section]];
                    //將位于節(jié)中第一行的單元格布局對象的Y軸都設置為最小的那個
                    cellLayoutAttributes.frame = CGRectMake(cellLayoutAttributes.frame.origin.x, minYCellAttributes.frame.origin.y, cellLayoutAttributes.frame.size.width, cellLayoutAttributes.frame.size.height);
                    //將更改過布局屬性之的布局屬性對象存放到已經(jīng)更改布局的布局對象字典中精钮,以section-item為Key
                    [self.layoutedAttributesDic setValue:cellLayoutAttributes forKey:[NSString stringWithFormat:@"%ld-%ld",(long)cellLayoutAttributes.indexPath.section,(long)cellLayoutAttributes.indexPath.item]];
                } else {
                    //單元格布局對象的項索引大于或等于一行單元格計數(shù)(說明不是第一行單元格)
                    //使用當前遍歷出的單元格布局屬性對象的項索引減去一行單元格計數(shù)威鹿,獲取當前遍歷出的單元格布局屬性在垂直布局中對應的它上面顯示的那個單元格布局屬性索引
                    //例如項索引為3,一行單元格計數(shù)為3轨香,結(jié)果為0忽你;項索引為4,一行單元格計數(shù)為3臂容,結(jié)果為1科雳;項索引為5,一行單元格計數(shù)為3脓杉,結(jié)果為2糟秘;這就可以計算出當前遍歷出的單元格布局屬性在垂直布局中對應的它上面的那個單元格布局屬性索引
                    NSInteger item = cellLayoutAttributes.indexPath.item - self.oneRowCountCell;
                    //在已經(jīng)更改布局的布局對象字典獲取當前遍歷出的單元格布局屬性在垂直布局中對應的它上面顯示的那個單元格布局屬性
                    UICollectionViewLayoutAttributes *layoutedAttributes = [self.layoutedAttributesDic objectForKey:[NSString stringWithFormat:@"%ld-%ld",(long)cellLayoutAttributes.indexPath.section,(long)item]];
                    //設置當前遍歷出的單元格布局屬性的Y軸值為它上面顯示的那個單元格布局屬性最大的Y軸值加上列間距
                    cellLayoutAttributes.frame = CGRectMake(cellLayoutAttributes.frame.origin.x, CGRectGetMaxY(layoutedAttributes.frame) + self.minimumLineSpacing, cellLayoutAttributes.frame.size.width, cellLayoutAttributes.frame.size.height);
                    //將當前遍歷出的單元格布局屬性也存入存放已經(jīng)更改布局的布局對象字典中
                    [self.layoutedAttributesDic setValue:cellLayoutAttributes forKey:[NSString stringWithFormat:@"%ld-%ld",(long)cellLayoutAttributes.indexPath.section,(long)cellLayoutAttributes.indexPath.item]];
                }
                //設置修改后的當前遍歷出的單元格布局屬性
                updatedAttributes[index] = cellLayoutAttributes;
            }
        }
        return updatedAttributes;
    } else {
        return [super layoutAttributesForElementsInRect:rect];
    }
}

- (NSMutableDictionary<NSString *,UICollectionViewLayoutAttributes *> *)oneRowminYCellAttributesDic {
    if (_oneRowminYCellAttributesDic == nil) {
        _oneRowminYCellAttributesDic = [[NSMutableDictionary alloc]init];
    }
    return _oneRowminYCellAttributesDic;
}

- (NSMutableDictionary<NSString *,UICollectionViewLayoutAttributes *> *)layoutedAttributesDic {
    if (_layoutedAttributesDic == nil) {
        _layoutedAttributesDic = [[NSMutableDictionary alloc]init];
    }
    return _layoutedAttributesDic;
}

@end

/**
 頁眉補充視圖
 */
UIKIT_EXTERN NSString *const HeaderViewReuseIdentifier;
@interface HeaderView : UICollectionReusableView

@property (nonatomic, strong) UILabel *titleLabel;

@end

NSString *const HeaderViewReuseIdentifier = @"HeaderViewReuseIdentifier";
@implementation HeaderView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.titleLabel = [[UILabel alloc]initWithFrame:self.bounds];
        self.titleLabel.font = [UIFont systemFontOfSize:18];
        self.titleLabel.textColor = [UIColor blackColor];
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        self.titleLabel.numberOfLines = 0;
        [self addSubview:self.titleLabel];
    }
    return self;
}

@end

/**
 單元格
 */
UIKIT_EXTERN NSString *const CategoryCellReuseIdentifier;
@interface CategoryCell : UICollectionViewCell

@property (nonatomic, strong) UILabel *categoryNameLabel;

@end

NSString *const CategoryCellReuseIdentifier = @"CategoryCellReuseIdentifier";
@implementation CategoryCell

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.layer.cornerRadius = 8.0;
        self.layer.borderWidth = 1.0;
        self.categoryNameLabel = [[UILabel alloc]initWithFrame:CGRectZero];
        self.categoryNameLabel.font = [UIFont systemFontOfSize:15];
        self.categoryNameLabel.textAlignment = NSTextAlignmentCenter;
        [self.contentView addSubview:self.categoryNameLabel];
        self.categoryNameLabel.numberOfLines = 0;
        self.categoryNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
        [NSLayoutConstraint activateConstraints:@[
            [self.categoryNameLabel.topAnchor constraintEqualToAnchor:self.topAnchor constant:5],
            [self.categoryNameLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:5],
            [self.categoryNameLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-5],
            [self.categoryNameLabel.bottomAnchor  constraintEqualToAnchor:self.bottomAnchor constant:-5],
        ]];
    }
    return self;
}

@end

/**
 控制器
 */
@interface TSChannelFourViewController ()<UICollectionViewDataSource,UICollectionViewDelegate>

@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSMutableArray<CategoryItemObject *> *dataSource;

@end

@implementation TSChannelFourViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.extendedLayoutIncludesOpaqueBars = YES;
    //流布局
    UICollectionViewWaterFallLayout *flowLayout = [[UICollectionViewWaterFallLayout alloc]init];
    flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
    flowLayout.minimumLineSpacing = 15;
    flowLayout.minimumInteritemSpacing = 15;
    flowLayout.sectionInset = UIEdgeInsetsMake(15, 15, 15, 15);
    flowLayout.headerReferenceSize = CGSizeMake(45, 45);
    //集合視圖
    self.collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:flowLayout];
    self.collectionView.dataSource = self;
    self.collectionView.delegate = self;
    self.collectionView.allowsMultipleSelection = YES;
    self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    [self.collectionView registerClass:CategoryCell.class forCellWithReuseIdentifier:CategoryCellReuseIdentifier];
    [self.collectionView registerClass:HeaderView.class forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:HeaderViewReuseIdentifier];
    [self.view addSubview:self.collectionView];
    self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [self.collectionView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
        [self.collectionView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [self.collectionView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [self.collectionView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
    ]];
}

/// 數(shù)據(jù)源
- (NSMutableArray<CategoryItemObject *> *)dataSource {
    if (_dataSource == nil) {
        _dataSource = [[NSMutableArray alloc]init];
        for (int i = 0; i < 3; i++) {
            CategoryItemObject *categoryItem = [[CategoryItemObject alloc]init];
            [_dataSource addObject:categoryItem];
        }
    }
    return _dataSource;
}

#pragma mark - UICollectionViewDataSource
//有幾節(jié)單元格
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return self.dataSource.count;
}

//每節(jié)有多少個單元格
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    CategoryItemObject *categoryItem = self.dataSource[section];
    return categoryItem.categoryArray.count;
}

//配置單元格
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    NSString *categoryName = self.dataSource[indexPath.section].categoryArray[indexPath.row];
    CategoryCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CategoryCellReuseIdentifier forIndexPath:indexPath];
    cell.categoryNameLabel.text = categoryName;
    //設置選中與未選中時單元格的樣式
    if (cell.isSelected) {
        cell.backgroundColor = [UIColor colorWithRed:226 / 255.0f green:240 / 255.0f blue:253 / 255.0f alpha:1.0];
        cell.layer.borderColor = [UIColor blueColor].CGColor;
        cell.categoryNameLabel.textColor = [UIColor blueColor];
    } else {
        cell.backgroundColor = [UIColor whiteColor];
        cell.layer.borderColor = [UIColor blackColor].CGColor;
        cell.categoryNameLabel.textColor = [UIColor blackColor];
    }
    return cell;
}

//配置頁眉補充視圖
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
        CategoryItemObject *categoryItem = self.dataSource[indexPath.section];
        HeaderView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:HeaderViewReuseIdentifier forIndexPath:indexPath];
        headerView.titleLabel.text = categoryItem.title;
        return headerView;
    }
    return nil;
}

#pragma mark - UICollectionViewDelegate
//單元格已被選擇
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    CategoryCell *cell = (CategoryCell *)[collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:226 / 255.0f green:240 / 255.0f blue:253 / 255.0f alpha:1.0];
    cell.layer.borderColor = [UIColor blueColor].CGColor;
    cell.categoryNameLabel.textColor = [UIColor blueColor];
}

//單元格已被取消選擇
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
    CategoryCell *cell = (CategoryCell *)[collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
    cell.layer.borderColor = [UIColor blackColor].CGColor;
    cell.categoryNameLabel.textColor = [UIColor blackColor];
}

#pragma mark - UICollectionViewDelegateFlowLayout
//每個單元格的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    //計算cell寬度
    UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
    CGFloat cellWidth = (collectionView.bounds.size.width - flowLayout.minimumInteritemSpacing * 2 - flowLayout.sectionInset.left - flowLayout.sectionInset.right) / 3;
    //計算cell高度
    NSString *categoryName = self.dataSource[indexPath.section].categoryArray[indexPath.row];
    CGFloat cellHeight = [categoryName boundingRectWithSize:CGSizeMake(floor(cellWidth) - 10, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]} context:nil].size.height;
    return CGSizeMake(floor(cellWidth), ceil(cellHeight) + 10 );
}

@end

注:示例中控制器是沒有導航欄的,如果是擁有導航欄的視圖控制器球散,需要注意控制器extendedLayoutIncludesOpaqueBars屬性與集合視圖contentInsetAdjustmentBehavior屬性的設置尿赚,否則布局容易錯亂。

效果如圖 :

截屏2022-08-19 17.46.25.png

UICollectionView高度自適應

estimatedItemSize的推出蕉堰,可以讓CollectionView中也能讓 cell 自適應內(nèi)容大小凌净,達到自動適應高度的預期效果

UICollectionView的高度自適應的原理:

    1. CollectionView根據(jù) layout 的 estimatedItemSize 算出估計的 contentSize屋讶,有了 contentSize CollectionView就開始顯示
    1. CollectionView 在顯示的過程中冰寻,即將被顯示的 cell 根據(jù) autolayout 的約束算出自適應內(nèi)容的 size
    1. layout 從 CollectionView 里獲取更新過的 size attribute
    1. layout 返回最終的 size attribute 給 CollectionView。CollectionView 使用這個最終的 size attribute 展示 cell

問題記錄

1.關(guān)于集合視圖使用Masonry設置約束皿渗,設置內(nèi)容水平布局斩芭,第一次reloadData時cell不顯示的問題:

Jietu20210405-222413.gif

設置UICollectionView的代碼如下:

    ///合并的分類集合視圖
    //初始化合并的分類集合視圖布局
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
    //設置水平滾動
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    //初始化合并的分類集合視圖
    _mergeStyleCollectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:layout];
    //設置合并的分類集合視圖背景色
    _mergeStyleCollectionView.backgroundColor = [UIColor whiteColor];
    //設置合并的分類集合視圖內(nèi)容內(nèi)邊距
    _mergeStyleCollectionView.contentInset = UIEdgeInsetsMake(0, 10, 0, 0);
    //設置合并的分類集合視圖數(shù)據(jù)源
    _mergeStyleCollectionView.dataSource = self;
    //設置合并的分類集合視圖代理
    _mergeStyleCollectionView.delegate = self;
    //設置合并的分類集合視圖不顯示垂直滾動條
    _mergeStyleCollectionView.showsVerticalScrollIndicator = NO;
    //設置合并的分類集合視圖不顯示水平滾動條
    _mergeStyleCollectionView.showsHorizontalScrollIndicator = NO;
    //添加合并的分類集合視圖
    [self addSubview:_mergeStyleCollectionView];
    //設置合并的分類集合視圖約束
    [_mergeStyleCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self);
        make.top.bottom.equalTo(self);
        make.right.equalTo(self.mas_right).offset(- 28);
    }];
    //注冊商品子分類cell
    [_mergeStyleCollectionView registerClass:[YSCGoodsSubCategoryItem class] forCellWithReuseIdentifier:YSC_GOODSSUBCATEGORY_ITEM];

問題出在UICollectionView設置約束的方式上轻腺,猜測可能是因為Masonry設置約束的方式上,UICollectionView在水平方向布局時,可能需要確切的高度,但使用Masonry時咐吼,第一次reloadData時,沒有辦法拿到UICollectionView的確切高度煤蚌,導致cell無法顯示,將約束改為如下代碼:

//設置合并的分類集合視圖約束
 [_mergeStyleCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
     make.left.equalTo(self);
     make.top.equalTo(self);
     make.right.equalTo(self.mas_right).offset(- 28);
     make.height.mas_equalTo(45);
  }];

UICollectionView的cell在第一次reloadData時能夠正常顯示:

Jietu20210405-223335.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末细卧,一起剝皮案震驚了整個濱河市尉桩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贪庙,老刑警劉巖蜘犁,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異止邮,居然都是意外死亡这橙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門导披,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屈扎,“玉大人,你說我怎么就攤上這事撩匕∮コ浚” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵止毕,是天一觀的道長模蜡。 經(jīng)常有香客問我,道長扁凛,這世上最難降的妖魔是什么忍疾? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮谨朝,結(jié)果婚禮上卤妒,老公的妹妹穿的比我還像新娘。我一直安慰自己字币,他們只是感情好荚孵,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纬朝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪骄呼。 梳的紋絲不亂的頭發(fā)上共苛,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天判没,我揣著相機與錄音,去河邊找鬼隅茎。 笑死澄峰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的辟犀。 我是一名探鬼主播俏竞,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼堂竟!你這毒婦竟也來了魂毁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤出嘹,失蹤者是張志新(化名)和其女友劉穎席楚,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體税稼,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡烦秩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了郎仆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片只祠。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扰肌,靈堂內(nèi)的尸體忽然破棺而出抛寝,到底是詐尸還是另有隱情,我是刑警寧澤狡耻,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布墩剖,位于F島的核電站,受9級特大地震影響夷狰,放射性物質(zhì)發(fā)生泄漏岭皂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一沼头、第九天 我趴在偏房一處隱蔽的房頂上張望爷绘。 院中可真熱鬧,春花似錦进倍、人聲如沸土至。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陶因。三九已至,卻和暖如春垂蜗,著一層夾襖步出監(jiān)牢的瞬間楷扬,已是汗流浹背解幽。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烘苹,地道東北人躲株。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像镣衡,于是被迫代替她去往敵國和親霜定。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

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