1. 程式人生 > >iOS 中 TableView 內巢狀 CollectionView 動態高度的實現

iOS 中 TableView 內巢狀 CollectionView 動態高度的實現

在有社交分享平臺屬性的app中,我們經常看見類似有tableview中多圖展示。不管是釋出的表單介面中,還是社交動態的時間線的介面中,都需要根據圖片數量動態變化介面。最近剛好寫了一個這樣的介面,花了點時間寫了個Demo總結一下,希望可以幫助有需要的人。實現Demo效果如下圖。

11967869-a08de49bc52e66d8 collectionintable.gif

實現原理分析

初步思路是在tableview的cell中內嵌一個collecitonview,collectionview高度動態變化並是collectionview所在的tableview的cell的高度動態變化。總結起來我們需要這幾件事:

1.實現一個tableview並自定義一種tableviewcell並實現高度自適應

2.在tableviewcell中實現collectionview並實現高度動態變化

3.自定義collectionviewcell中實現按鈕點選事件(如刪除,跳轉),資料展示操作

1.實現tableview和自定義tableviewcell

tableview就很簡單,storyboard或者程式碼寫一下都可以。實現資料來源協議啥的,很普通。要實現tableviewcell的高度自適應,一般來說有兩種方式,一種是用iOS7後支援的cell的estimatedRowHeight和iOS8後支援的self-sizing cells(兩者差不多,iOS8更完善一些),另一種是用孫源大神的第三方開源庫,可以看這篇

文章,兩者共同之處都是需要設定cell裡contenview的元素對cell的contenview的四個邊的佈局約束,換言之要讓cell裡的元素把cell四邊“撐”起來。Demo裡使用了原生的self-sizing cells來高度自適應,需要下面兩句程式碼。

12 self.tableview.estimatedRowHeight=100.f;//數字為大致估算高度,比如有些50有些100可以估算75左右self.tableview.rowHeight=UITableViewAutomaticDimension;

如果tableview是用程式碼建立的,那麼rowheight屬性的預設引數就是UITableViewAutomaticDimension,不需要第二行程式碼。而在storyboard或xib裡拖的預設rowheight是拖的storyboard屬性選單裡的,需要更改為UITableViewAutomaticDimension

接著自定義一個CDZTableViewCell,並用xib拖上一個collectionview並設定對contenview的autolayout約束。用Masonry之類的第三方佈局庫或原生進行程式碼約束也可以。然後在tableview裡用registerNib方法註冊一下自定義的cell。當然要實現動態高度變化,我們還需要讓cell在合適的時候通知tableview應該更新資料和佈局了,即呼叫tableview自身的reloadData方法。這裡我用delegate實現,通知的話也可以,但是通知的要明確接受者和傳送者的對應關係,不然有些情況會接受物件不明確(比如實現了兩個類似的tableview在視圖裡)。在cell的標頭檔案裡建立delegate。

123456 @protocol CDZTableViewCellDelegate-(void)shouldReload;@end@interfaceCDZTableViewCell:UITableViewCell@property(nonatomic,assign)id delegate;@end

然後讓tableview遵守CDZTableViewCellDelegate並在tableview的datasource或delegate方法裡設定cell的delegate,比如

12345 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{//......cell.delegate=self;//......}

也可以在delegate裡的-(void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath裡設定(感覺這個方法更好些,datasource應該處理資料相關的,而且有時候會複用抽離出來)。

2.實現collectionview和自定義collectionviewcell

collectionview在約束上,除了實現collecitionview對tableviewcell的上下左右約束外,還要實現其高度約束,因為本質collectionview是scrollview的子類,實現scollview的autolayout其實就是需要實現對其撐起來contentview的約束,而動態實現contentview的高度,則需要我們更新contenview的高度佈局約束的值。collectionview也是類似的,我們先設定一個collectionview的高度約束,再在資料更新時更新高度約束。

這裡再提一句,collectionview本身和tableview類似,系統本身也有self-sizing cells的api,如下:

12 self.collectionViewFlowLayout.estimatedItemSize=CGSizeMake(125,100);self.collectionViewFlowLayout.itemSize=UICollectionViewFlowLayoutAutomaticSize;

同樣也要實現collectionviewcell裡元素對cell四邊的約束,“撐”起來自動計算高度,但是不知道為何用了之後在一個cell的時候會莫名其妙居中,且cell的indexPath有些錯亂,根據cell的indexPath找出來的cell是錯誤的,不知道是啥問題,若有人知道可以告訴一聲囧。

這裡就不用collectionview的大小自適應了,常規的用設定itemsize的大小就好了。在tableviewcellawakeFromNib方法中,我們讓collectionviewreloadData更新一下高度佈局約束為collectionview的真實高度並呼叫剛才自定義的delegate去讓tableviewreloadData從而讓tableview的cell高度自適應。

123456 -(void)reloadCell{[self.collectionView reloadData];self.collectionViewHeightLayout.constant=self.collectionViewFlowLayout.collectionViewContentSize.height;[selfupdateConstraintsIfNeeded];[self.delegate shouldReload];}

collectionview的真實大小其實是他的layout物件的collectionviewcontentsize的值。所以我們需要在每次資料發生改變時,更新一下高度約束的值。

還有就是實現collectionview的delegate,點選後執行操作,在賦值在cell對應的model裡等操作(比如呼叫相簿等)。注意當點選最後一個collectioncell時,要在它的後面插入一個數據,也就是說,最後一個總是會保持在最後。

123456789101112 -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{//載入資料,如圖片等CDZCollectionViewItem *item=[CDZCollectionViewItem new];item.image=[UIImage imageNamed:@"example"];if((indexPath.row==self.itemsArray.count-1)){[self.itemsArray insertObject:item atIndex:self.itemsArray.count-1];}else{self.itemsArray[indexPath.row]=item;}[selfreloadCell];}

3.自定義collectionviewcell並實現刪除按鈕

先自定義一個CDZCollectionViewCell,並用xib拖上一個imageview用於展示圖片,一個button用於點選關閉cell。並定義好解析item的方法。item中設定一個delBtnHidden屬性用於設定cell裡的button是否隱藏。

12345 -(void)setItem:(CDZCollectionViewItem *)item{//  解析需要的資料self.imageView.image=item.image;self.delButton.hidden=item.delBtnHidden;}

並定義一個delegate用於將按鈕點選事件回傳給collectionview並刪除資料。

12345678 @protocol CDZCollectionCellDelegate-(void)didDelete:(UICollectionViewCell *)cell;@end@interfaceCDZCollectionViewCell:UICollectionViewCell@property(strong,nonatomic)CDZCollectionViewItem *item;@property(assign,nonatomic)id delegate;@end

並在點選按鈕的事件中呼叫delegate,把cell本身傳回tableview,從而找到cell對應的item。

12345 -(IBAction)delCell:(UIButton *)sender{if([self.delegate respondsToSelector:@selector(didDelete:)]){[self.delegate didDelete:self];}}

然後使collectionview所在的tableviewcell遵守CDZCollectionCellDelegate。並和之前一樣,在collectionview的datasource或delegate中設定delegate。

12345 -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{//......cell.delegate=self;//......}

並呼叫cell的delegate的方法,通過cell去找到對應的indexPath

12345 -(void)didDelete:(UICollectionViewCell *)cell{NSIndexPath *indexPath=[self.collectionView indexPathForCell:cell];[self.itemsArray removeObjectAtIndex:indexPath.row];[selfreloadCell];}

第一個cell是沒有關閉按鈕的,那麼只要把第一個item的delBthHidden屬性設為YES就可以了。

最後

所有原始碼和Demo

如果您覺得有幫助,不妨給個star鼓勵一下,歡迎關注&交流

有任何問題歡迎評論私信或者提issue

QQ:757765420
Email:[email protected]
Github:Nemocdz
微博:@Nemocdz

謝謝觀看