iOS流布局UICollectionView系列一——初識(shí)與簡(jiǎn)單使用UICollectionView

iOS流布局UICollectionView系列一——初識(shí)與簡(jiǎn)單使用UICollectionView

一、簡(jiǎn)介

????????UICollectionView是iOS6之后引入的一個(gè)新的UI控件,它和UITableView有著諸多的相似之處,其中許多代理方法都十分類似。簡(jiǎn)單來說,UICollectionView是比UITbleView更加強(qiáng)大的一個(gè)UI控件,有如下幾個(gè)方面:

1、支持水平和垂直兩種方向的布局

2、通過layout配置方式進(jìn)行布局

3、類似于TableView中的cell特性外,CollectionView中的Item大小和位置可以自由定義

4、通過layout布局回調(diào)的代理方法,可以動(dòng)態(tài)的定制每個(gè)item的大小和collection的大體布局屬性

5、更加強(qiáng)大一點(diǎn),完全自定義一套layout布局方案,可以實(shí)現(xiàn)意想不到的效果

這篇博客,我們主要討論CollectionView使用原生layout的方法和相關(guān)屬性,其他特點(diǎn)和更強(qiáng)的制定化,會(huì)在后面的博客中介紹


二、先來實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的九宮格類布局

????????在了解UICollectionView的更多屬性前,我們先來使用其進(jìn)行一個(gè)最簡(jiǎn)單的流布局試試看,在controller的viewDidLoad中添加如下代碼:

//創(chuàng)建一個(gè)layout布局類

UICollectionViewFlowLayout*?layout?=?[[UICollectionViewFlowLayoutalloc]init];

//設(shè)置布局方向?yàn)榇怪绷鞑季?/p>

layout.scrollDirection?=UICollectionViewScrollDirectionVertical;

//設(shè)置每個(gè)item的大小為100*100

layout.itemSize?=CGSizeMake(100,100);

//創(chuàng)建collectionView?通過一個(gè)布局策略layout來創(chuàng)建

UICollectionView*?collect?=?[[UICollectionViewalloc]initWithFrame:self.view.frame?collectionViewLayout:layout];

//代理設(shè)置

collect.delegate=self;

collect.dataSource=self;

//注冊(cè)item類型?這里使用系統(tǒng)的類型

[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];


[self.view?addSubview:collect];

這里有一點(diǎn)需要注意,collectionView在完成代理回調(diào)前,必須注冊(cè)一個(gè)cell,類似如下:

[collect?registerClass:[UICollectionViewCell?class]forCellWithReuseIdentifier:@"cellid"];

這和tableView有些類似,又有些不同,因?yàn)閠ableView除了注冊(cè)cell的方法外,還可以通過臨時(shí)創(chuàng)建來做:

//tableView在從復(fù)用池中取cell的時(shí)候,有如下兩種方法

//使用這種方式如果復(fù)用池中無,是可以返回nil的,我們?cè)谂R時(shí)創(chuàng)建即可

-?(nullable__kindofUITableViewCell*)dequeueReusableCellWithIdentifier:(NSString*)identifier;

//6.0后使用如下的方法直接從注冊(cè)的cell類獲取創(chuàng)建,如果沒有注冊(cè)?會(huì)崩潰

-?(__kindofUITableViewCell*)dequeueReusableCellWithIdentifier:(NSString*)identifier?forIndexPath:(NSIndexPath*)indexPathNS_AVAILABLE_IOS(6_0);

我們可以分析:因?yàn)閁ICollectionView是iOS6.0之前的新類,因此這里統(tǒng)一了從復(fù)用池中獲取cell的方法,沒有再提供可以返回nil的方式,并且在UICollectionView的回調(diào)代理中,只能使用從復(fù)用池中獲取cell的方式進(jìn)行cell的返回,其他方式會(huì)崩潰,例如:

//這是正確的方法

-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{

UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];

cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];

returncell;

}

//這樣做會(huì)崩潰

-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{

//????UICollectionViewCell?*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"?forIndexPath:indexPath];

//????cell.backgroundColor?=?[UIColor?colorWithRed:arc4random()%255/255.0?green:arc4random()%255/255.0?blue:arc4random()%255/255.0?alpha:1];

UICollectionViewCell*?cell?=?[[UICollectionViewCellalloc]init];

returncell;

}

上面錯(cuò)誤的方式會(huì)崩潰,信息如下,讓我們使用從復(fù)用池中取cell的方式:

上面的設(shè)置完成后,我們來實(shí)現(xiàn)如下幾個(gè)代理方法:

這里與TableView的回調(diào)方式十分類似

//返回分區(qū)個(gè)數(shù)

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView?*)collectionView{

return1;

}

//返回每個(gè)分區(qū)的item個(gè)數(shù)

-(NSInteger)collectionView:(UICollectionView?*)collectionViewnumberOfItemsInSection:(NSInteger)section{

return10;

}

//返回每個(gè)item

-(UICollectionViewCell?*)collectionView:(UICollectionView?*)collectionViewcellForItemAtIndexPath:(NSIndexPath?*)indexPath{

UICollectionViewCell?*?cell??=?[collectionViewdequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];

cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0?green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];

returncell;

}

效果如下:

同樣,如果內(nèi)容的大小超出一屏,和tableView類似是可以進(jìn)行視圖滑動(dòng)的。

還有一點(diǎn)細(xì)節(jié),我們?cè)谏厦嬖O(shè)置布局方式的時(shí)候設(shè)置了垂直布局:

layout.scrollDirection?=UICollectionViewScrollDirectionVertical;

//這個(gè)是水平布局

//layout.scrollDirection?=?UICollectionViewScrollDirectionHorizontal;

這樣系統(tǒng)會(huì)在一行充滿后進(jìn)行第二行的排列,如果設(shè)置為水平布局,則會(huì)在一列充滿后,進(jìn)行第二列的布局,這種方式也被稱為流式布局

三、UICollectionView中的常用方法和屬性

//通過一個(gè)布局策略初識(shí)化CollectionView

-?(instancetype)initWithFrame:(CGRect)frame?collectionViewLayout:(UICollectionViewLayout*)layout;

//獲取和設(shè)置collection的layout

@property(nonatomic,strong)UICollectionViewLayout*collectionViewLayout;

//數(shù)據(jù)源和代理

@property(nonatomic,weak,nullable)id?delegate;

@property(nonatomic,weak,nullable)id?dataSource;

//從一個(gè)class或者xib文件進(jìn)行cell(item)的注冊(cè)

-?(void)registerClass:(nullableClass)cellClass?forCellWithReuseIdentifier:(NSString*)identifier;

-?(void)registerNib:(nullableUINib*)nib?forCellWithReuseIdentifier:(NSString*)identifier;

//下面兩個(gè)方法與上面相似,這里注冊(cè)的是頭視圖或者尾視圖的類

//其中第二個(gè)參數(shù)是設(shè)置?頭視圖或者尾視圖?系統(tǒng)為我們定義好了這兩個(gè)字符串

//UIKIT_EXTERN?NSString?*const?UICollectionElementKindSectionHeader?NS_AVAILABLE_IOS(6_0);

//UIKIT_EXTERN?NSString?*const?UICollectionElementKindSectionFooter?NS_AVAILABLE_IOS(6_0);

-?(void)registerClass:(nullableClass)viewClass?forSupplementaryViewOfKind:(NSString*)elementKind?withReuseIdentifier:(NSString*)identifier;

-?(void)registerNib:(nullableUINib*)nib?forSupplementaryViewOfKind:(NSString*)kind?withReuseIdentifier:(NSString*)identifier;

//這兩個(gè)方法是從復(fù)用池中取出cell或者頭尾視圖

-?(__kindofUICollectionViewCell*)dequeueReusableCellWithReuseIdentifier:(NSString*)identifier?forIndexPath:(NSIndexPath*)indexPath;

-?(__kindofUICollectionReusableView*)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind?withReuseIdentifier:(NSString*)identifier?forIndexPath:(NSIndexPath*)indexPath;

//設(shè)置是否允許選中?默認(rèn)yes

@property(nonatomic)BOOLallowsSelection;

//設(shè)置是否允許多選?默認(rèn)no

@property(nonatomic)BOOLallowsMultipleSelection;

//獲取所有選中的item的位置信息

-?(nullableNSArray?*)indexPathsForSelectedItems;

//設(shè)置選中某一item,并使視圖滑動(dòng)到相應(yīng)位置,scrollPosition是滑動(dòng)位置的相關(guān)參數(shù),如下:

/*

typedef?NS_OPTIONS(NSUInteger,?UICollectionViewScrollPosition)?{

????//無

????UICollectionViewScrollPositionNone?????????????????=?0,

????//垂直布局時(shí)使用的?對(duì)應(yīng)上中下

????UICollectionViewScrollPositionTop??????????????????=?1?<<?0,

????UICollectionViewScrollPositionCenteredVertically???=?1?<<?1,

????UICollectionViewScrollPositionBottom???????????????=?1?<<?2,

????//水平布局時(shí)使用的??對(duì)應(yīng)左中右

????UICollectionViewScrollPositionLeft?????????????????=?1?<<?3,

????UICollectionViewScrollPositionCenteredHorizontally?=?1?<<?4,

????UICollectionViewScrollPositionRight????????????????=?1?<<?5

};

*/

-?(void)selectItemAtIndexPath:(nullableNSIndexPath*)indexPath?animated:(BOOL)animated?scrollPosition:(UICollectionViewScrollPosition)scrollPosition;

//將某一item取消選中

-?(void)deselectItemAtIndexPath:(NSIndexPath*)indexPath?animated:(BOOL)animated;

//重新加載數(shù)據(jù)

-?(void)reloadData;

//下面這兩個(gè)方法,可以重新設(shè)置collection的布局,后面的方法多了一個(gè)布局完成后的回調(diào),iOS7后可以用

//使用這兩個(gè)方法可以產(chǎn)生非常炫酷的動(dòng)畫效果

-?(void)setCollectionViewLayout:(UICollectionViewLayout*)layout?animated:(BOOL)animated;

-?(void)setCollectionViewLayout:(UICollectionViewLayout*)layout?animated:(BOOL)animated?completion:(void(^?__nullable)(BOOLfinished))completionNS_AVAILABLE_IOS(7_0);

//下面這些方法更加強(qiáng)大,我們可以對(duì)布局更改后的動(dòng)畫進(jìn)行設(shè)置

//這個(gè)方法傳入一個(gè)布局策略layout,系統(tǒng)會(huì)開始進(jìn)行布局渲染,返回一個(gè)UICollectionViewTransitionLayout對(duì)象

//這個(gè)UICollectionViewTransitionLayout對(duì)象管理動(dòng)畫的相關(guān)屬性,我們可以進(jìn)行設(shè)置

-?(UICollectionViewTransitionLayout*)startInteractiveTransitionToCollectionViewLayout:(UICollectionViewLayout*)layout?completion:(nullableUICollectionViewLayoutInteractiveTransitionCompletion)completionNS_AVAILABLE_IOS(7_0);

//準(zhǔn)備好動(dòng)畫設(shè)置后,我們需要調(diào)用下面的方法進(jìn)行布局動(dòng)畫的展示,之后會(huì)調(diào)用上面方法的block回調(diào)

-?(void)finishInteractiveTransitionNS_AVAILABLE_IOS(7_0);

//調(diào)用這個(gè)方法取消上面的布局動(dòng)畫設(shè)置,之后也會(huì)進(jìn)行上面方法的block回調(diào)

-?(void)cancelInteractiveTransitionNS_AVAILABLE_IOS(7_0);

//獲取分區(qū)數(shù)

-?(NSInteger)numberOfSections;

//獲取某一分區(qū)的item數(shù)

-?(NSInteger)numberOfItemsInSection:(NSInteger)section;

//下面兩個(gè)方法獲取item或者頭尾視圖的layout屬性,這個(gè)UICollectionViewLayoutAttributes對(duì)象

//存放著布局的相關(guān)數(shù)據(jù),可以用來做完全自定義布局,后面博客會(huì)介紹

-?(nullableUICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath;

-?(nullableUICollectionViewLayoutAttributes*)layoutAttributesForSupplementaryElementOfKind:(NSString*)kind?atIndexPath:(NSIndexPath*)indexPath;

//獲取某一點(diǎn)所在的indexpath位置

-?(nullableNSIndexPath*)indexPathForItemAtPoint:(CGPoint)point;

//獲取某個(gè)cell所在的indexPath

-?(nullableNSIndexPath*)indexPathForCell:(UICollectionViewCell*)cell;

//根據(jù)indexPath獲取cell

-?(nullableUICollectionViewCell*)cellForItemAtIndexPath:(NSIndexPath*)indexPath;

//獲取所有可見cell的數(shù)組

-?(NSArray<__kindofUICollectionViewCell*>?*)visibleCells;

//獲取所有可見cell的位置數(shù)組

-?(NSArray?*)indexPathsForVisibleItems;

//下面三個(gè)方法是iOS9中新添加的方法,用于獲取頭尾視圖

-?(UICollectionReusableView*)supplementaryViewForElementKind:(NSString*)elementKind?atIndexPath:(NSIndexPath*)indexPathNS_AVAILABLE_IOS(9_0);

-?(NSArray?*)visibleSupplementaryViewsOfKind:(NSString*)elementKindNS_AVAILABLE_IOS(9_0);

-?(NSArray?*)indexPathsForVisibleSupplementaryElementsOfKind:(NSString*)elementKindNS_AVAILABLE_IOS(9_0);

//使視圖滑動(dòng)到某一位置,可以帶動(dòng)畫效果

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

//下面這些方法用于動(dòng)態(tài)添加,刪除,移動(dòng)某些分區(qū)獲取items

-?(void)insertSections:(NSIndexSet*)sections;

-?(void)deleteSections:(NSIndexSet*)sections;

-?(void)reloadSections:(NSIndexSet*)sections;

-?(void)moveSection:(NSInteger)section?toSection:(NSInteger)newSection;

-?(void)insertItemsAtIndexPaths:(NSArray?*)indexPaths;

-?(void)deleteItemsAtIndexPaths:(NSArray?*)indexPaths;

-?(void)reloadItemsAtIndexPaths:(NSArray?*)indexPaths;

-?(void)moveItemAtIndexPath:(NSIndexPath*)indexPath?toIndexPath:(NSIndexPath*)newIndexPath;


iOS流布局UICollectionView系列二——UICollectionView的代理方法

一、引言

????????在上一篇博客中,介紹了最基本的UICollectionView的使用和其中我們常用的屬性和方法,也介紹了瀑布流布局的過程與思路,這篇博客來討論關(guān)于UICollectionView的代理方法的使用。

二、UICollectionViewDataSource協(xié)議

????????這個(gè)協(xié)議主要用于collectionView相關(guān)數(shù)據(jù)的處理,包含方法如下:

首先,有兩個(gè)方法是我們必須實(shí)現(xiàn)的:

設(shè)置每個(gè)分區(qū)的Item個(gè)數(shù)

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;

設(shè)置返回每個(gè)item的屬性

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;

下面的方法是可選實(shí)現(xiàn)的:

雖然這個(gè)方法是可選的,一般我們都會(huì)去實(shí)現(xiàn),設(shè)置分區(qū)數(shù)

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;

對(duì)頭視圖或者尾視圖進(jìn)行設(shè)置

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;


設(shè)置某個(gè)item是否可以被移動(dòng),返回NO則不能移動(dòng)

- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);

移動(dòng)item的時(shí)候,會(huì)調(diào)用這個(gè)方法

- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath;

三、UICollectionViewDelegate協(xié)議

????????這個(gè)協(xié)議用來設(shè)置和處理collectionView的功能和一些邏輯,所有方法都是可選實(shí)現(xiàn):

是否允許某個(gè)Item的高亮,返回NO,則不能進(jìn)入高亮狀態(tài)

- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;

當(dāng)item高亮?xí)r觸發(fā)的方法

- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;


結(jié)束高亮狀態(tài)時(shí)觸發(fā)的方法

- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath;


是否可以選中某個(gè)Item,返回NO,則不能選中

- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;


是否可以取消選中某個(gè)Item

- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;


已經(jīng)選中某個(gè)item時(shí)觸發(fā)的方法

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;


取消選中某個(gè)Item時(shí)觸發(fā)的方法

- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;


將要加載某個(gè)Item時(shí)調(diào)用的方法

- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);


將要加載頭尾視圖時(shí)調(diào)用的方法

- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);


已經(jīng)展示某個(gè)Item時(shí)觸發(fā)的方法

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


已經(jīng)展示某個(gè)頭尾視圖時(shí)觸發(fā)的方法

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


這個(gè)方法設(shè)置是否展示長(zhǎng)按菜單

- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;

長(zhǎng)按菜單中可以觸發(fā)一下類復(fù)制粘貼的方法,效果如下:

這個(gè)方法用于設(shè)置要展示的菜單選項(xiàng)

- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;


這個(gè)方法用于實(shí)現(xiàn)點(diǎn)擊菜單按鈕后的觸發(fā)方法,通過測(cè)試,只有copy,cut和paste三個(gè)方法可以使用

- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;

通過下面的方式可以將點(diǎn)擊按鈕的方法名打印出來:

-(void)collectionView:(UICollectionView*)collectionView?performAction:(SEL)action?forItemAtIndexPath:(NSIndexPath*)indexPath?withSender:(id)sender{

NSLog(@"%@",NSStringFromSelector(action));

}


collectionView進(jìn)行重新布局時(shí)調(diào)用的方法

- (nonnull UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout;

iOS流布局UICollectionView系列三——使用FlowLayout進(jìn)行更靈活布局

一、引言

????????前面的博客介紹了UICollectionView的相關(guān)方法和其協(xié)議中的方法,但對(duì)布局的管理類UICollectionViewFlowLayout沒有著重探討,這篇博客介紹關(guān)于布局的相關(guān)設(shè)置和屬性方法。通過layout的設(shè)置,我們可以編寫更加靈活的布局效果。

二、將九宮格式的布局進(jìn)行升級(jí)

????????在第一篇博客中,通過UICollectionView,我們很輕松的完成了一個(gè)九宮格的布局,但是如此中規(guī)中矩的布局方式,有時(shí)候并不能滿足我們的需求,有時(shí)我們需要每一個(gè)Item展示不同的大小,代碼如下:

-?(void)viewDidLoad?{

[superviewDidLoad];

//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.

UICollectionViewFlowLayout*layout?=?[[UICollectionViewFlowLayoutalloc]init];

layout.scrollDirection?=UICollectionViewScrollDirectionVertical;

UICollectionView*collect?=?[[UICollectionViewalloc]initWithFrame:CGRectMake(0,0,320,400)?collectionViewLayout:layout];

collect.delegate=self;

collect.dataSource=self;


[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];

??;

[self.view?addSubview:collect];



}

//設(shè)置每個(gè)item的大小,雙數(shù)的為50*50?單數(shù)的為100*100

-(CGSize)collectionView:(UICollectionView*)collectionView?layout:(UICollectionViewLayout*)collectionViewLayout?sizeForItemAtIndexPath:(NSIndexPath*)indexPath{

if(indexPath.row%2==0)?{

returnCGSizeMake(50,50);

}else{

returnCGSizeMake(100,100);

????}

}

//代理相應(yīng)方法

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView{

return1;

}

-(NSInteger)collectionView:(UICollectionView*)collectionView?numberOfItemsInSection:(NSInteger)section{

return100;

}

-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{

UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];

cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];

returncell;

}

效果如下:

現(xiàn)在的布局效果是不是炫酷了許多。

三、UICollectionViewFlowLayout相關(guān)屬性方法

????????UICollectionViewFlowLayout是系統(tǒng)提供給我們一個(gè)封裝好的流布局設(shè)置類,其中有一些布局屬性我們可以進(jìn)行設(shè)置:?

設(shè)置行與行之間的間距最小距離

@property(nonatomic) CGFloat minimumLineSpacing;


設(shè)置列與列之間的間距最小距離

@property(nonatomic) CGFloat minimumInteritemSpacing;


設(shè)置每個(gè)item的大小

@property(nonatomic) CGSize itemSize;


設(shè)置每個(gè)Item的估計(jì)大小,一般不需要設(shè)置

@property(nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0);


設(shè)置布局方向

@property(nonatomic) UICollectionViewScrollDirection scrollDirection;

這個(gè)UICollectionViewScrollDirection的枚舉如下:

typedefNS_ENUM(NSInteger,UICollectionViewScrollDirection)?{

UICollectionViewScrollDirectionVertical,//水平布局

UICollectionViewScrollDirectionHorizontal//垂直布局

};


設(shè)置頭視圖尺寸大小

@property (nonatomic) CGSize headerReferenceSize;


設(shè)置尾視圖尺寸大小

@property (nonatomic) CGSize footerReferenceSize;


設(shè)置分區(qū)的EdgeInset

@property (nonatomic) UIEdgeInsets sectionInset;

這個(gè)屬性可以設(shè)置分區(qū)的偏移量,例如我們?cè)趧偛诺睦又刑砑尤缦略O(shè)置:

layout.sectionInset?=UIEdgeInsetsMake(20,20,20,20);

效果如下,會(huì)看到分區(qū)的邊界閃出了20像素

下面這兩個(gè)方法設(shè)置分區(qū)的頭視圖和尾視圖是否始終固定在屏幕上邊和下邊

?@property (nonatomic) BOOL sectionHeadersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);

@property (nonatomic) BOOL sectionFootersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);

四、動(dòng)態(tài)的配置layout的相關(guān)屬性UICollectionViewDelegateFlowLayout

????????上面的方法在創(chuàng)建FlowLayout時(shí)靜態(tài)的進(jìn)行設(shè)置,如果我們需要?jiǎng)討B(tài)的設(shè)置這些屬性,就像我們例子中的,每個(gè)item的大小會(huì)有差異,我們可以通過代理來實(shí)現(xiàn)。

? ? ? ? UICollectionViewDelegateFlowLayout是UICollectionViewDelegate的子協(xié)議,其中常用方法如下,我們只需要實(shí)現(xiàn)我們需要的即可:


動(dòng)態(tài)設(shè)置每個(gè)Item的尺寸大小

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;


動(dòng)態(tài)設(shè)置每個(gè)分區(qū)的EdgeInsets

?- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;


動(dòng)態(tài)設(shè)置每行的間距大小

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;


動(dòng)態(tài)設(shè)置每列的間距大小

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;


動(dòng)態(tài)設(shè)置某個(gè)分區(qū)頭視圖大小

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;


動(dòng)態(tài)設(shè)置某個(gè)分區(qū)尾視圖大小

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;

iOS流布局UICollectionView系列四——自定義FlowLayout進(jìn)行瀑布流布局

一、引言

????????前幾篇博客從UICollectionView的基礎(chǔ)應(yīng)用到設(shè)置UICollectionViewFlowLayout更加靈活的進(jìn)行布局,但都限制在系統(tǒng)為我們準(zhǔn)備好的布局框架中,還是有一些局限性,例如,如果我要進(jìn)行瀑布流似的不定高布局,前面的方法就很難滿足我們的需求了,如下:

這種布局無疑在app的應(yīng)用中更加廣泛,商品的展示,書架書目的展示,都會(huì)傾向于采用這樣的布局方式,當(dāng)然,通過自定義FlowLayout,我們也很容易實(shí)現(xiàn)。

二、進(jìn)行自定義瀑布流布局

????????首先,我們新建一個(gè)文件繼承于UICollectionViewFlowLayout:

@interfaceMyLayout:UICollectionViewFlowLayout

為了演示的方便,這里我不做更多的封裝,只添加一個(gè)屬性,直接讓外界將item個(gè)數(shù)傳遞進(jìn)來,我們把重心方法重寫布局的方法上:

@interfaceMyLayout:UICollectionViewFlowLayout

@property(nonatomic,assign)intitemCount;

@end

前面說過,UICollectionViewFlowLayout是一個(gè)專門用來管理collectionView布局的類,因此,collectionView在進(jìn)行UI布局前,會(huì)通過這個(gè)類的對(duì)象獲取相關(guān)的布局信息,F(xiàn)lowLayout類將這些布局信息全部存放在了一個(gè)數(shù)組中,數(shù)組中是UICollectionViewLayoutAttributes類,這個(gè)類是對(duì)item布局的具體設(shè)置,以后咱們?cè)谟懻撨@個(gè)類。總之,F(xiàn)lowLayout類將每個(gè)item的位置等布局信息放在一個(gè)數(shù)組中,在collectionView布局時(shí),會(huì)調(diào)用FlowLayout類layoutAttributesForElementsInRect:方法來獲取這個(gè)布局配置數(shù)組。因此,我們需要重寫這個(gè)方法,返回我們自定義的配置數(shù)組,另外,F(xiàn)lowLayout類在進(jìn)行布局之前,會(huì)調(diào)用prepareLayout方法,所以我們可以重寫這個(gè)方法,在里面對(duì)我們的自定義配置數(shù)據(jù)進(jìn)行一些設(shè)置。

簡(jiǎn)單來說,自定義一個(gè)FlowLayout布局類就是兩個(gè)步驟:

1、設(shè)計(jì)好我們的布局配置數(shù)據(jù) prepareLayout方法中

2、返回我們的配置數(shù)組?layoutAttributesForElementsInRect方法中

示例代碼如下:

@implementationMyLayout

{

//這個(gè)數(shù)組就是我們自定義的布局配置數(shù)組

NSMutableArray*?_attributeAttay;

}

//數(shù)組的相關(guān)設(shè)置在這個(gè)方法中

//布局前的準(zhǔn)備會(huì)調(diào)用這個(gè)方法

-(void)prepareLayout{

_attributeAttay?=?[[NSMutableArrayalloc]init];

[superprepareLayout];

//演示方便?我們?cè)O(shè)置為靜態(tài)的2列

//計(jì)算每一個(gè)item的寬度

floatWIDTH?=?([UIScreenmainScreen].bounds.size.width-self.sectionInset.left-self.sectionInset.right-self.minimumInteritemSpacing)/2;

//定義數(shù)組保存每一列的高度

//這個(gè)數(shù)組的主要作用是保存每一列的總高度,這樣在布局時(shí),我們可以始終將下一個(gè)Item放在最短的列下面

CGFloatcolHight[2]={self.sectionInset.top,self.sectionInset.bottom};

//itemCount是外界傳進(jìn)來的item的個(gè)數(shù)?遍歷來設(shè)置每一個(gè)item的布局

for(inti=0;?i<_itemCount;?i++)?{

//設(shè)置每個(gè)item的位置等相關(guān)屬性

NSIndexPath*index?=?[NSIndexPathindexPathForItem:i?inSection:0];

//創(chuàng)建一個(gè)布局屬性類,通過indexPath來創(chuàng)建

UICollectionViewLayoutAttributes*?attris?=?[UICollectionViewLayoutAttributeslayoutAttributesForCellWithIndexPath:index];

//隨機(jī)一個(gè)高度?在40——190之間

CGFloathight?=?arc4random()%150+40;

//哪一列高度小?則放到那一列下面

//標(biāo)記最短的列

intwidth=0;

if(colHight[0]

//將新的item高度加入到短的一列

colHight[0]?=?colHight[0]+hight+self.minimumLineSpacing;

width=0;

}else{

colHight[1]?=?colHight[1]+hight+self.minimumLineSpacing;

width=1;

????????}


//設(shè)置item的位置

attris.frame?=CGRectMake(self.sectionInset.left+(self.minimumInteritemSpacing+WIDTH)*width,?colHight[width]-hight-self.minimumLineSpacing,?WIDTH,?hight);

????????[_attributeAttay?addObject:attris];

????}


//設(shè)置itemSize來確保滑動(dòng)范圍的正確?這里是通過將所有的item高度平均化,計(jì)算出來的(以最高的列位標(biāo)準(zhǔn))

if(colHight[0]>colHight[1])?{

self.itemSize?=CGSizeMake(WIDTH,?(colHight[0]-self.sectionInset.top)*2/_itemCount-self.minimumLineSpacing);

}else{

self.itemSize?=CGSizeMake(WIDTH,?(colHight[1]-self.sectionInset.top)*2/_itemCount-self.minimumLineSpacing);

????}


}

//這個(gè)方法中返回我們的布局?jǐn)?shù)組

-(NSArray?*)layoutAttributesForElementsInRect:(CGRect)rect{

return_attributeAttay;

}

@end

自定義完成FlowLayout后,我們?cè)赩iewController中進(jìn)行使用:

-?(void)viewDidLoad?{

[superviewDidLoad];

//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.

????MyLayout?*?layout?=?[[MyLayout?alloc]init];

layout.scrollDirection?=UICollectionViewScrollDirectionVertical;

layout.itemCount=100;

UICollectionView*?collect??=?[[UICollectionViewalloc]initWithFrame:CGRectMake(0,0,320,400)?collectionViewLayout:layout];

collect.delegate=self;

collect.dataSource=self;


[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];


[self.view?addSubview:collect];



}

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView{

return1;

}

-(NSInteger)collectionView:(UICollectionView*)collectionView?numberOfItemsInSection:(NSInteger)section{

return100;

}

-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{

UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];

cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];

returncell;

}

運(yùn)行效果就是我們引言中的截圖。

三、UICollectionViewLayoutAttributes類中我們可以配置的屬性

????????通過上面的例子,我們可以了解,collectionView的item布局其實(shí)是LayoutAttributes類具體配置的,這個(gè)類可以配置的布局屬性不止是frame這么簡(jiǎn)單,其中還有許多屬性:

//配置item的布局位置

@property(nonatomic)CGRectframe;

//配置item的中心

@property(nonatomic)CGPointcenter;

//配置item的尺寸

@property(nonatomic)CGSizesize;

//配置item的3D效果

@property(nonatomic)CATransform3Dtransform3D;

//配置item的bounds

@property(nonatomic)CGRectboundsNS_AVAILABLE_IOS(7_0);

//配置item的旋轉(zhuǎn)

@property(nonatomic)CGAffineTransformtransformNS_AVAILABLE_IOS(7_0);

//配置item的alpha

@property(nonatomic)CGFloatalpha;

//配置item的z坐標(biāo)

@property(nonatomic)NSIntegerzIndex;//?default?is?0

//配置item的隱藏

@property(nonatomic,getter=isHidden)BOOLhidden;

//item的indexpath

@property(nonatomic,strong)NSIndexPath*indexPath;

//獲取item的類型

@property(nonatomic,readonly)UICollectionElementCategoryrepresentedElementCategory;

@property(nonatomic,readonly,nullable)NSString*representedElementKind;

//一些創(chuàng)建方法

+?(instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath*)indexPath;

+?(instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString*)elementKind?withIndexPath:(NSIndexPath*)indexPath;

+?(instancetype)layoutAttributesForDecorationViewOfKind:(NSString*)decorationViewKind?withIndexPath:(NSIndexPath*)indexPath;

通過上面的屬性,可以布局出各式各樣的炫酷效果,正如一句話:沒有做不到,只有想不到。

iOS流布局UICollectionView系列五——圓環(huán)布局的實(shí)現(xiàn)

一、引言

????????前邊的幾篇博客,我們了解了UICollectionView的基本用法以及一些擴(kuò)展,在不定高的瀑布流布局中,我們發(fā)現(xiàn),可以通過設(shè)置具體的布局屬性類UICollectionViewLayoutAttributes來設(shè)置設(shè)置每個(gè)item的具體位置,我們可以再擴(kuò)展一下,如果位置我們可以自由控制,那個(gè)布局我們也可以更加靈活,就比如創(chuàng)建一個(gè)如下的circleLayout:

這種布局方式在apple的官方文檔中也有介紹,是UICollectionView的一個(gè)應(yīng)用示例。

二、設(shè)計(jì)一個(gè)圓環(huán)布局

? ? ? ? 先自定義一個(gè)layout類,這個(gè)類繼承于UICollectionViewLayout,UICollectionLayout是一個(gè)布局抽象基類,我們要使用自定義的布局方式,必須將其子類化,可能你還記得,我們?cè)谶M(jìn)行瀑布流布局的時(shí)候使用過UICollectionViewFlowLayout類,這個(gè)類就是繼承于UICollectionViewLayout類,系統(tǒng)為我們實(shí)現(xiàn)好的一個(gè)布局方案。

@interfaceMyLayout:UICollectionViewLayout

//這個(gè)int值存儲(chǔ)有多少個(gè)item

@property(nonatomic,assign)intitemCount;

@end


我們需要重寫這個(gè)類的三個(gè)方法,來進(jìn)行圓環(huán)布局的設(shè)置,首先是prepareLayout,為布局做一些準(zhǔn)備工作,使用collectionViewContentSize來設(shè)置內(nèi)容的區(qū)域大小,最后使用layoutAttributesForElementsInRect方法來返回我們的布局信息字典,這個(gè)和前面瀑布流布局的思路是一樣的:

@implementationMyLayout

{

NSMutableArray*?_attributeAttay;

}

-(void)prepareLayout{

[superprepareLayout];

//獲取item的個(gè)數(shù)

_itemCount?=?(int)[self.collectionView?numberOfItemsInSection:0];

_attributeAttay?=?[[NSMutableArrayalloc]init];

//先設(shè)定大圓的半徑?取長(zhǎng)和寬最短的

CGFloatradius?=?MIN(self.collectionView.frame.size.width,self.collectionView.frame.size.height)/2;

//計(jì)算圓心位置

CGPointcenter?=CGPointMake(self.collectionView.frame.size.width/2,self.collectionView.frame.size.height/2);

//設(shè)置每個(gè)item的大小為50*50?則半徑為25

for(inti=0;?i<_itemCount;?i++)?{

UICollectionViewLayoutAttributes*?attris?=?[UICollectionViewLayoutAttributeslayoutAttributesForCellWithIndexPath:[NSIndexPathindexPathForItem:i?inSection:0]];

//設(shè)置item大小

attris.size?=CGSizeMake(50,50);

//計(jì)算每個(gè)item的圓心位置

/*

?????????.

?????????.?.

?????????.???.?r

?????????.?????.

?????????.........

?????????*/

//計(jì)算每個(gè)item中心的坐標(biāo)

//算出的x?y值還要減去item自身的半徑大小

floatx?=?center.x+cosf(2*M_PI/_itemCount*i)*(radius-25);

floaty?=?center.y+sinf(2*M_PI/_itemCount*i)*(radius-25);


attris.center?=CGPointMake(x,?y);

????????[_attributeAttay?addObject:attris];

????}




}

//設(shè)置內(nèi)容區(qū)域的大小

-(CGSize)collectionViewContentSize{

returnself.collectionView.frame.size;

}

//返回設(shè)置數(shù)組

-(NSArray?*)layoutAttributesForElementsInRect:(CGRect)rect{

return_attributeAttay;

}


在viewController中代碼如下:

-?(void)viewDidLoad?{

[superviewDidLoad];

//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.

????MyLayout?*?layout?=?[[MyLayout?alloc]init];

UICollectionView*?collect??=?[[UICollectionViewalloc]initWithFrame:CGRectMake(0,0,320,400)?collectionViewLayout:layout];

collect.delegate=self;

collect.dataSource=self;


[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];

[self.view?addSubview:collect];

}

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView{

return1;

}

-(NSInteger)collectionView:(UICollectionView*)collectionView?numberOfItemsInSection:(NSInteger)section{

return10;

}

-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{

UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];

cell.layer.masksToBounds?=YES;

cell.layer.cornerRadius?=25;

cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];

returncell;

}


如上非常簡(jiǎn)單的一些邏輯控制,我們就實(shí)現(xiàn)哦圓環(huán)布局,隨著item的多少,布局會(huì)自動(dòng)調(diào)整,如果不是UICollectionView的功勞,實(shí)現(xiàn)這樣的功能,我們可能要寫上一陣子了^_^。

iOS流布局UICollectionView系列六——將布局從平面應(yīng)用到空間

一、引言

????????前面,我們將布局由線性的瀑布流布局?jǐn)U展到了圓環(huán)布局,這使我們使用UICollectionView的布局思路大大邁進(jìn)了一步,這次,我們玩的更加炫一些,想辦法將布局應(yīng)用到空間。之前在管理布局的item的具體屬性的類UICollectionViewLayoutAttributrs類中,有transform3D這個(gè)屬性,通過這個(gè)屬性的設(shè)置,我們真的可以在空間的坐標(biāo)系中進(jìn)行布局設(shè)計(jì)。iOS系統(tǒng)的控件中,也并非沒有這樣的先例,UIPickerView就是很好的一個(gè)實(shí)例,這篇博客,我們就通過使用UICollectionView實(shí)現(xiàn)一個(gè)類似系統(tǒng)的UIPickerView的布局視圖,來體會(huì)UICollectionView在3D控件布局的魅力。系統(tǒng)的pickerView效果如下:

二、先來實(shí)現(xiàn)一個(gè)炫酷的滾輪空間布局

????????萬丈的高樓也是由一磚一瓦堆砌而成,在我們完全模擬系統(tǒng)pickerView前,我們應(yīng)該先將視圖的布局?jǐn)[放這一問題解決。我們依然來創(chuàng)建一個(gè)類,繼承于UICollectionViewLayout:

@interfaceMyLayout:UICollectionViewLayout

@end

對(duì)于.m文件的內(nèi)容,前幾篇博客中我們都是在prepareLayout中進(jìn)行布局的靜態(tài)設(shè)置,那是因?yàn)槲覀兦皫灼┛椭械牟季侄际庆o態(tài)的,布局并不會(huì)隨著我們的手勢(shì)操作而發(fā)生太大的變化,因此我們?nèi)吭趐repareLayout中一次配置完了。而我們這次要討論的布局則不同,pickerView會(huì)隨著我們手指的拖動(dòng)而進(jìn)行滾動(dòng),因此UICollectionView中的每一個(gè)item的布局是在不斷變化的,所以這次,我們采用動(dòng)態(tài)配置的方式,在layoutAttributesForItemAtIndexPath方法中進(jìn)行每個(gè)item的布局屬性設(shè)置。

????????至于layoutAttributesForItemAtIndexPath方法,它也是UICollectionViewLayout類中的方法,用于我們自定義時(shí)進(jìn)行重寫,至于為什么動(dòng)態(tài)布局要在這里面配置item的布局屬性,后面我們會(huì)了解到。

? ? ? ? 在編寫我們的布局類之前,先做好準(zhǔn)備工作,在viewController中,實(shí)現(xiàn)如下代碼:

-?(void)viewDidLoad?{

[superviewDidLoad];

//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.

????MyLayout?*?layout?=?[[MyLayout?alloc]init];

UICollectionView*?collect??=?[[UICollectionViewalloc]initWithFrame:CGRectMake(0,0,320,400)?collectionViewLayout:layout];

collect.delegate=self;

collect.dataSource=self;

[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];

[self.view?addSubview:collect];

}

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView{

return1;

}

-(NSInteger)collectionView:(UICollectionView*)collectionView?numberOfItemsInSection:(NSInteger)section{

return10;

}

-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{

UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];

cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];

UILabel*?label?=?[[UILabelalloc]initWithFrame:CGRectMake(0,0,250,80)];

label.text?=?[NSStringstringWithFormat:@"我是第%ld行",(long)indexPath.row];

????[cell.contentView?addSubview:label];

returncell;

}

上面我創(chuàng)建了10個(gè)Item,并且在每個(gè)Item上添加了一個(gè)標(biāo)簽,標(biāo)寫是第幾行。

在我們自定義的布局類中重寫layoutAttributesForElementsInRect,在其中返回我們的布局?jǐn)?shù)組:

-(NSArray?*)layoutAttributesForElementsInRect:(CGRect)rect{

NSMutableArray*?attributes?=?[[NSMutableArrayalloc]init];

//遍歷設(shè)置每個(gè)item的布局屬性

for(inti=0;?i<[self.collectionView?numberOfItemsInSection:0];?i++)?{

[attributes?addObject:[selflayoutAttributesForItemAtIndexPath:[NSIndexPathindexPathForItem:i?inSection:0]]];

????}

returnattributes;

}

之后,在我們布局類中重寫layoutAttributesForItemAtIndexPath方法:

-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath{

//創(chuàng)建一個(gè)item布局屬性類

UICollectionViewLayoutAttributes*?atti?=?[UICollectionViewLayoutAttributeslayoutAttributesForCellWithIndexPath:indexPath];

//獲取item的個(gè)數(shù)

intitemCounts?=?(int)[self.collectionView?numberOfItemsInSection:0];

//設(shè)置每個(gè)item的大小為260*100

atti.size?=CGSizeMake(260,100);

/*

???后邊介紹的代碼添加在這里


???*/

returnatti;

}

上面的代碼中,我們什么都沒有做,下面我們一步步來實(shí)現(xiàn)3D的滾輪效果。

首先,我們先將所有的item的位置都設(shè)置為collectionView的中心:

atti.center?=CGPointMake(self.collectionView.frame.size.width/2,self.collectionView.frame.size.height/2);

這時(shí),如果我們運(yùn)行程序的話,所有item都將一層層貼在屏幕的中央,如下:

很丑對(duì)吧,之后我們來設(shè)置每個(gè)item的3D效果,在上面的布局方法中添加如下代碼:

//創(chuàng)建一個(gè)transform3D類

//CATransform3D是一個(gè)類似矩陣的結(jié)構(gòu)體

//CATransform3DIdentity創(chuàng)建空得矩陣

CATransform3Dtrans3D?=CATransform3DIdentity;

//這個(gè)值設(shè)置的是透視度,影響視覺離投影平面的距離

trans3D.m34?=-1/900.0;

//下面這些屬性?后面會(huì)具體介紹

//這個(gè)是3D滾輪的半徑

CGFloatradius?=50/tanf(M_PI*2/itemCounts/2);

//計(jì)算每個(gè)item應(yīng)該旋轉(zhuǎn)的角度

CGFloatangle?=?(float)(indexPath.row)/itemCounts*M_PI*2;

//這個(gè)方法返回一個(gè)新的CATransform3D對(duì)象,在原來的基礎(chǔ)上進(jìn)行旋轉(zhuǎn)效果的追加

//第一個(gè)參數(shù)為旋轉(zhuǎn)的弧度,后三個(gè)分別對(duì)應(yīng)x,y,z軸,我們需要以x軸進(jìn)行旋轉(zhuǎn)

trans3D?=CATransform3DRotate(trans3D,?angle,1.0,0,0);

//進(jìn)行設(shè)置

????atti.transform3D?=?trans3D;

????????對(duì)于上面的radius屬性,運(yùn)用了一些簡(jiǎn)單的幾何和三角函數(shù)的知識(shí)。如果我們將系統(tǒng)的pickerView沿著y軸旋轉(zhuǎn)90°,你會(huì)發(fā)現(xiàn)側(cè)面的它是一個(gè)規(guī)則的正多邊形,這里的radius就是這個(gè)多邊形中心到其邊的垂直距離,也是內(nèi)切圓的半徑,所有的item拼成了一個(gè)正多邊形,示例如下:

通過簡(jiǎn)單的數(shù)學(xué)知識(shí),h/2弦對(duì)應(yīng)的角的弧度為2*pi/(邊數(shù))/2,在根據(jù)三角函數(shù)相關(guān)知識(shí)可知,這個(gè)角的正切值為h/2/radius,這就是我們r(jià)adius的由來。?

????????對(duì)于angle屬性,它是每一個(gè)item的x軸旋轉(zhuǎn)度數(shù),如果我們將所有item的中心都放在一點(diǎn),通過旋轉(zhuǎn)讓它們散開如下圖所示:

每個(gè)item旋轉(zhuǎn)的弧度就是其索引/(2*pi)。

通過上面的設(shè)置,我們?cè)龠\(yùn)行代碼,效果如下:

仔細(xì)觀察我們可以發(fā)現(xiàn),item以x中軸線進(jìn)行了旋轉(zhuǎn)平均布局,側(cè)面的效果就是我們上面的簡(jiǎn)筆畫那樣,下面要進(jìn)行我們的第三步了,將這個(gè)item,全部沿著其Z軸向前拉,就可以成為我們滾輪的效果,示例圖如下:

我們繼續(xù)在剛才的代碼后面添加這行代碼:

//這個(gè)方法也返回一個(gè)transform3D對(duì)象,追加平移效果,后面三個(gè)參數(shù),對(duì)應(yīng)平移的x,y,z軸,我們沿z軸平移

trans3D?=CATransform3DTranslate(trans3D,0,0,?radius);

再次運(yùn)行,效果如下:

布局的效果我們已經(jīng)完成了,離成功很近了對(duì)吧,只是現(xiàn)在的布局是靜態(tài)的,我們不能滑動(dòng)這個(gè)滾輪,我們還需要用動(dòng)態(tài)滑動(dòng)做一些處理。

三、讓滾輪滑動(dòng)起來

????????????通過上面的努力,我們已經(jīng)靜態(tài)布局出了一個(gè)類似pickerView的滾輪,現(xiàn)在我們?cè)賮硖砑踊瑒?dòng)滾動(dòng)的效果

????????首先,我們需要給collectionView一個(gè)滑動(dòng)的范圍,我們以一屏collectionView的滑動(dòng)距離來當(dāng)做滾輪滾動(dòng)一下的參照,我們?cè)诓季诸愔械娜缦路椒ㄖ蟹祷鼗瑒?dòng)區(qū)域:

-(CGSize)collectionViewContentSize{

returnCGSizeMake(self.collectionView.frame.size.width,self.collectionView.frame.size.height*[self.collectionView?numberOfItemsInSection:0]);

}

這時(shí)我們的collectionView已經(jīng)可以進(jìn)行滑動(dòng),但是并不是我們想要的效果,滾輪并沒有滾動(dòng),而是隨著滑動(dòng)出了屏幕,因此,我們需要在滑動(dòng)的時(shí)候不停的動(dòng)態(tài)布局,將滾輪始終固定在collectionView的中心,先需要在布局類中實(shí)現(xiàn)如下方法:

//返回yes,則一有變化就會(huì)刷新布局

-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{

returnYES;


}

將上面的布局的中心點(diǎn)設(shè)置加上一個(gè)動(dòng)態(tài)的偏移量:

atti.center?=CGPointMake(self.collectionView.frame.size.width/2,self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y);

現(xiàn)在在運(yùn)行,會(huì)發(fā)現(xiàn)滾輪會(huì)隨著滑動(dòng)始終固定在中間,但是還是不如人意,滾輪并沒有轉(zhuǎn)動(dòng)起來,我們還需要?jiǎng)討B(tài)的設(shè)置每個(gè)item的旋轉(zhuǎn)角度,這樣連續(xù)看起來,滾輪就轉(zhuǎn)了起來,在上面設(shè)置布局的方法中,我們?cè)谔砑右恍┨幚恚?/p>

//獲取當(dāng)前的偏移量

floatoffset?=self.collectionView.contentOffset.y;

//在角度設(shè)置上,添加一個(gè)偏移角度

floatangleOffset?=?offset/self.collectionView.frame.size.height;

CGFloatangle?=?(float)(indexPath.row+angleOffset)/itemCounts*M_PI*2;

再看看效果,沒錯(cuò),就是這么簡(jiǎn)單,滾輪已經(jīng)轉(zhuǎn)了起來。

四、讓其循環(huán)滾動(dòng)的邏輯

????????我們?cè)龠M(jìn)一步,如果滾動(dòng)可以循環(huán),這個(gè)控件將更加炫酷,添加這樣的邏輯也很簡(jiǎn)單,通過監(jiān)測(cè)scrollView的偏移量,我們可以對(duì)齊進(jìn)行處理,因?yàn)閏ollectionView繼承于scrollView,我們可以直接在ViewController中實(shí)現(xiàn)其代理方法,如下:

-(void)scrollViewDidScroll:(UIScrollView*)scrollView{

//小于半屏?則放到最后一屏多半屏

if(scrollView.contentOffset.y<200)?{

scrollView.contentOffset?=CGPointMake(0,?scrollView.contentOffset.y+10*400);

//大于最后一屏多一屏?放回第一屏

}elseif(scrollView.contentOffset.y>11*400){

scrollView.contentOffset?=CGPointMake(0,?scrollView.contentOffset.y-10*400);

????}

}

因?yàn)樵蹅兊沫h(huán)狀布局,上面的邏輯剛好可以無縫對(duì)接,但是會(huì)有新的問題,一開始運(yùn)行,滾輪就是出現(xiàn)在最后一個(gè)item的位置,而不是第一個(gè),并且有些相關(guān)的地方,我們也需要一些適配:

在viewController中:

//一開始將collectionView的偏移量設(shè)置為1屏的偏移量

collect.contentOffset?=CGPointMake(0,400);

在layout類中:

//將滾動(dòng)范圍設(shè)置為(item總數(shù)+2)*每屏高度?

-(CGSize)collectionViewContentSize{

returnCGSizeMake(self.collectionView.frame.size.width,self.collectionView.frame.size.height*([self.collectionView?numberOfItemsInSection:0]+2));

}

//將計(jì)算的具體item角度向前遞推一個(gè)

CGFloatangle?=?(float)(indexPath.row+angleOffset-1)/itemCounts*M_PI*2;

OK,我們終于大功告成了,可以發(fā)現(xiàn),實(shí)現(xiàn)這樣一個(gè)布局效果炫酷的控件,代碼其實(shí)并沒有多少,相比,數(shù)學(xué)邏輯要比編寫代碼本身困難,這十分類似數(shù)學(xué)中的幾何問題,如果你弄清了邏輯,解決是分分鐘的事,我們可以通過這樣的一個(gè)思路,設(shè)計(jì)更多3D或者平面特效的布局方案,抽獎(jiǎng)的轉(zhuǎn)動(dòng)圓盤,書本的翻頁,甚至立體的標(biāo)簽云,UICollectionView都可以實(shí)現(xiàn),這篇博客中的代碼在下面的連接中,疏漏之處,歡迎指正!

http://pan.baidu.com/s/1jGCmbKM

iOS流布局UICollectionView系列七——三維中的球型布局

一、引言

????????通過6篇的博客,從平面上最簡(jiǎn)單的規(guī)則擺放的布局,到不規(guī)則的瀑布流布局,再到平面中的圓環(huán)布局,我們突破了線性布局的局限,在后面,我們將布局?jǐn)U展到了空間,在Z軸上進(jìn)行了平移,我們實(shí)現(xiàn)了一個(gè)類似UIPickerView的布局模型,其實(shí)我們還可以再進(jìn)一步,類比于平面布局,picKerView只是線性排列布局在空間上的旋轉(zhuǎn)與平移,這次,我們更加充分了利用一下空間的尺寸,來設(shè)計(jì)一個(gè)圓球的布局模型。

二、將布局?jǐn)U展為空間球型

????????在viewController中先實(shí)現(xiàn)一些準(zhǔn)備代碼:

-?(void)viewDidLoad?{

[superviewDidLoad];

//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.

????MyLayout?*?layout?=?[[MyLayout?alloc]init];

UICollectionView*?collect??=?[[UICollectionViewalloc]initWithFrame:CGRectMake(0,0,320,400)?collectionViewLayout:layout];

collect.delegate=self;

collect.dataSource=self;

//這里設(shè)置的偏移量是為了無縫進(jìn)行循環(huán)的滾動(dòng),具體在上一篇博客中有解釋

collect.contentOffset?=CGPointMake(320,400);

[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];

[self.view?addSubview:collect];

}

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView{

return1;

}

//我們返回30的標(biāo)簽

-(NSInteger)collectionView:(UICollectionView*)collectionView?numberOfItemsInSection:(NSInteger)section{

return30;

}

-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{

UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];

cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];

UILabel*?label?=?[[UILabelalloc]initWithFrame:CGRectMake(0,0,30,30)];

label.text?=?[NSStringstringWithFormat:@"%ld",(long)indexPath.row];

????[cell.contentView?addSubview:label];

returncell;

}

-?(void)didReceiveMemoryWarning?{

[superdidReceiveMemoryWarning];

//?Dispose?of?any?resources?that?can?be?recreated.

}

//這里對(duì)滑動(dòng)的contentOffset進(jìn)行監(jiān)控,實(shí)現(xiàn)循環(huán)滾動(dòng)

-(void)scrollViewDidScroll:(UIScrollView*)scrollView{

if(scrollView.contentOffset.y<200)?{

scrollView.contentOffset?=CGPointMake(scrollView.contentOffset.x,?scrollView.contentOffset.y+10*400);

}elseif(scrollView.contentOffset.y>11*400){

scrollView.contentOffset?=CGPointMake(scrollView.contentOffset.x,?scrollView.contentOffset.y-10*400);

????}

if(scrollView.contentOffset.x<160)?{

scrollView.contentOffset?=CGPointMake(scrollView.contentOffset.x+10*320,scrollView.contentOffset.y);

}elseif(scrollView.contentOffset.x>11*320){

scrollView.contentOffset?=CGPointMake(scrollView.contentOffset.x-10*320,scrollView.contentOffset.y);

????}

}


這里面的代碼比較上一篇博客中的并沒有什么大的改動(dòng),只是做了橫坐標(biāo)的兼容。

在我們的layout類中,將代碼修改成如下:

-(void)prepareLayout{

[superprepareLayout];


}

//返回的滾動(dòng)范圍增加了對(duì)x軸的兼容

-(CGSize)collectionViewContentSize{

returnCGSizeMake(self.collectionView.frame.size.width*([self.collectionView?numberOfItemsInSection:0]+2),self.collectionView.frame.size.height*([self.collectionView?numberOfItemsInSection:0]+2));

}

-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{

returnYES;

}

-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath{

UICollectionViewLayoutAttributes*?atti?=?[UICollectionViewLayoutAttributeslayoutAttributesForCellWithIndexPath:indexPath];

//獲取item的個(gè)數(shù)

intitemCounts?=?(int)[self.collectionView?numberOfItemsInSection:0];

atti.center?=CGPointMake(self.collectionView.frame.size.width/2+self.collectionView.contentOffset.x,self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y);

atti.size?=CGSizeMake(30,30);


CATransform3Dtrans3D?=CATransform3DIdentity;

trans3D.m34?=-1/900.0;


CGFloatradius?=15/tanf(M_PI*2/itemCounts/2);

//根據(jù)偏移量?改變角度

//添加了一個(gè)x的偏移量

floatoffsety?=self.collectionView.contentOffset.y;

floatoffsetx?=self.collectionView.contentOffset.x;

//分別計(jì)算偏移的角度

floatangleOffsety?=?offsety/self.collectionView.frame.size.height;

floatangleOffsetx?=?offsetx/self.collectionView.frame.size.width;

CGFloatangle1?=?(float)(indexPath.row+angleOffsety-1)/itemCounts*M_PI*2;

//x,y的默認(rèn)方向相反

CGFloatangle2?=?(float)(indexPath.row-angleOffsetx-1)/itemCounts*M_PI*2;

//這里我們進(jìn)行四個(gè)方向的排列

if(indexPath.row%4==1)?{

trans3D?=CATransform3DRotate(trans3D,?angle1,1.0,0,0);

}elseif(indexPath.row%4==2){

trans3D?=CATransform3DRotate(trans3D,?angle2,0,1,0);

}elseif(indexPath.row%4==3){

trans3D?=CATransform3DRotate(trans3D,?angle1,0.5,0.5,0);

}else{

trans3D?=CATransform3DRotate(trans3D,?angle1,0.5,-0.5,0);

????}


trans3D?=CATransform3DTranslate(trans3D,0,0,?radius);


????atti.transform3D?=?trans3D;

returnatti;

}

-(NSArray?*)layoutAttributesForElementsInRect:(CGRect)rect{

NSMutableArray*?attributes?=?[[NSMutableArrayalloc]init];

//遍歷設(shè)置每個(gè)item的布局屬性

for(inti=0;?i<[self.collectionView?numberOfItemsInSection:0];?i++)?{

[attributes?addObject:[selflayoutAttributesForItemAtIndexPath:[NSIndexPathindexPathForItem:i?inSection:0]]];

????}

returnattributes;

}


布局效果如下:

滑動(dòng)屏幕,這個(gè)圓球是可以進(jìn)行滾動(dòng)的。

TIP:這里我們只平均分配了四個(gè)方向上的布局,如果item更加小也更加多,我們可以分配到更多的方向上,使球體更加充實(shí)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評(píng)論 1 283
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,608評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評(píng)論 2 372

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