在有社交分享平臺(tái)屬性的app中渣叛,我們經(jīng)嘲嗝校看見類似有tableview中多圖展示希停。不管是發(fā)布的表單界面中,還是社交動(dòng)態(tài)的時(shí)間線的界面中署隘,都需要根據(jù)圖片數(shù)量動(dòng)態(tài)變化界面宠能。最近剛好寫了一個(gè)這樣的界面,花了點(diǎn)時(shí)間寫了個(gè)Demo總結(jié)一下磁餐,希望可以幫助有需要的人违崇。實(shí)現(xiàn)Demo效果如下圖。
實(shí)現(xiàn)原理分析
初步思路是在tableview的cell中內(nèi)嵌一個(gè)collecitonview,collectionview高度動(dòng)態(tài)變化并是collectionview所在的tableview的cell的高度動(dòng)態(tài)變化亦歉⌒衾耍總結(jié)起來我們需要這幾件事:
1.實(shí)現(xiàn)一個(gè)tableview并自定義一種tableviewcell并實(shí)現(xiàn)高度自適應(yīng)
2.在tableviewcell中實(shí)現(xiàn)collectionview并實(shí)現(xiàn)高度動(dòng)態(tài)變化
3.自定義collectionviewcell中實(shí)現(xiàn)按鈕點(diǎn)擊事件(如刪除,跳轉(zhuǎn))肴楷,數(shù)據(jù)展示操作
1.實(shí)現(xiàn)tableview和自定義tableviewcell
tableview就很簡單水由,storyboard或者代碼寫一下都可以。實(shí)現(xiàn)數(shù)據(jù)源協(xié)議啥的赛蔫,很普通砂客。要實(shí)現(xiàn)tableviewcell的高度自適應(yīng),一般來說有兩種方式呵恢,一種是用iOS7后支持的cell的estimatedRowHeight和iOS8后支持的self-sizing cells(兩者差不多鞠值,iOS8更完善一些),另一種是用孫源大神的第三方開源庫渗钉,可以看這篇文章彤恶,兩者共同之處都是需要設(shè)置cell里contenview的元素對(duì)cell的contenview的四個(gè)邊的布局約束,換言之要讓cell里的元素把cell四邊“撐”起來鳄橘。Demo里使用了原生的self-sizing cells來高度自適應(yīng)声离,需要下面兩句代碼。
self.tableview.estimatedRowHeight = 100.f;//數(shù)字為大致估算高度瘫怜,比如有些50有些100可以估算75左右
self.tableview.rowHeight = UITableViewAutomaticDimension;
如果tableview是用代碼創(chuàng)建的术徊,那么rowheight屬性的默認(rèn)參數(shù)就是UITableViewAutomaticDimension
,不需要第二行代碼鲸湃。而在storyboard或xib里拖的默認(rèn)rowheight是拖的storyboard屬性菜單里的赠涮,需要更改為UITableViewAutomaticDimension
。
接著自定義一個(gè)CDZTableViewCell
暗挑,并用xib拖上一個(gè)collectionview并設(shè)置對(duì)contenview的autolayout約束笋除。用Masonry之類的第三方布局庫或原生進(jìn)行代碼約束也可以。然后在tableview里用registerNib
方法注冊(cè)一下自定義的cell炸裆。
因?yàn)門ableView執(zhí)行reloadData方法時(shí)株憾,會(huì)把所有cell從VisableCells池中移除,并從同一復(fù)用標(biāo)識(shí)的復(fù)用池中取出Cell加入視圖中晒衩,這個(gè)時(shí)候cell的高度已經(jīng)確定好嗤瞎,但indexPath的row順序有可能不是原來的,所以不能復(fù)用听系。也就是雖然每一個(gè)Cell功能一致贝奇,但是由于高度和里面圖片數(shù)量不一樣,并不能互相復(fù)用靠胜。在Storyboard中可以用靜態(tài)cell分別放入collectionView實(shí)現(xiàn)掉瞳。這里為了不重復(fù)放collectionView用了代碼書寫TableView毕源。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *identifer = [NSString stringWithFormat:@"CDZTableViewCell%ld",indexPath.row];//唯一復(fù)用標(biāo)識(shí),相當(dāng)于不復(fù)用
CDZTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];
if (!cell) {
cell = [CDZTableViewCell.alloc initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];
}
cell.delegate = self;
return cell;
}
當(dāng)然要實(shí)現(xiàn)動(dòng)態(tài)高度變化陕习,我們還需要讓cell在合適的時(shí)候通知tableview應(yīng)該更新數(shù)據(jù)和布局了霎褐,即調(diào)用tableview自身的reloadData
方法。這里我用delegate實(shí)現(xiàn)该镣,通知的話也可以冻璃,但是通知的要明確接受者和發(fā)送者的對(duì)應(yīng)關(guān)系,不然有些情況會(huì)接受對(duì)象不明確(比如實(shí)現(xiàn)了兩個(gè)類似的tableview在視圖里)损合。在cell的頭文件里創(chuàng)建delegate省艳。
@protocol CDZTableViewCellDelegate<NSObject>
- (void)didChangeCell:(UITableViewCell *)cell;
@end
@interface CDZTableViewCell : UITableViewCell
@property (nonatomic,assign) id<CDZTableViewCellDelegate> delegate;
@end
然后讓tableview遵守CDZTableViewCellDelegate
并在tableview的datasource或delegate方法里設(shè)置cell的delegate,比如
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//......
cell.delegate = self;
//......
}
也可以在delegate里的-(void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
里設(shè)置(感覺這個(gè)方法更好些嫁审,datasource應(yīng)該處理數(shù)據(jù)相關(guān)的跋炕,而且有時(shí)候會(huì)復(fù)用抽離出來)。
2.實(shí)現(xiàn)collectionview和自定義collectionviewcell
collectionview在約束上律适,除了實(shí)現(xiàn)collecitionview對(duì)tableviewcell的上下左右約束外辐烂,還要實(shí)現(xiàn)其高度約束,因?yàn)楸举|(zhì)collectionview是scrollview的子類捂贿,實(shí)現(xiàn)scollview的autolayout其實(shí)就是需要實(shí)現(xiàn)對(duì)其撐起來contentview的約束纠修,而動(dòng)態(tài)實(shí)現(xiàn)contentview的高度,則需要我們更新contenview的高度布局約束的值眷蜓。collectionview也是類似的,我們先設(shè)定一個(gè)collectionview的高度約束胎围,再在數(shù)據(jù)更新時(shí)更新高度約束吁系。
這里再提一句,collectionview本身和tableview類似白魂,系統(tǒng)本身也有self-sizing cells的api汽纤,如下:
self.collectionViewFlowLayout.estimatedItemSize = CGSizeMake(125, 100);
self.collectionViewFlowLayout.itemSize = UICollectionViewFlowLayoutAutomaticSize;
同樣也要實(shí)現(xiàn)collectionviewcell里元素對(duì)cell四邊的約束,“撐”起來自動(dòng)計(jì)算高度福荸,但是不知道為何用了之后在一個(gè)cell的時(shí)候會(huì)莫名其妙居中蕴坪,且cell的indexPath有些錯(cuò)亂,根據(jù)cell的indexPath找出來的cell是錯(cuò)誤的敬锐,不知道是啥問題背传,若有人知道可以告訴一聲囧。
這里就不用collectionview的大小自適應(yīng)了台夺,常規(guī)的用設(shè)置itemsize的大小就好了径玖。在tableviewcellawakeFromNib
方法中,我們讓collectionviewreloadData
更新一下高度布局約束為collectionview的真實(shí)高度并調(diào)用剛才自定義的delegate去讓tableviewreloadData
從而讓tableview的cell高度自適應(yīng)颤介。
- (void)reloadCell{
[self.collectionView reloadData];
[self.collectionView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(@(self.collectionView.collectionViewLayout.collectionViewContentSize.height));
}];
[self.delegate didChangeCell:self];
}
collectionview的真實(shí)大小其實(shí)是他的layout對(duì)象的collectionviewcontentsize的值梳星。所以我們需要在每次數(shù)據(jù)發(fā)生改變時(shí)赞赖,更新一下高度約束的值。
還有就是實(shí)現(xiàn)collectionview的delegate冤灾,點(diǎn)擊后執(zhí)行操作前域,在賦值在cell對(duì)應(yīng)的model里等操作(比如調(diào)用相冊(cè)等)。注意當(dāng)點(diǎn)擊最后一個(gè)collectioncell時(shí)韵吨,要在它的后面插入一個(gè)數(shù)據(jù)匿垄,也就是說,最后一個(gè)總是會(huì)保持在最后学赛。
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
//載入數(shù)據(jù)年堆,如圖片等
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;
}
[self reloadCell];
}
3.自定義collectionviewcell并實(shí)現(xiàn)刪除按鈕
先自定義一個(gè)CDZCollectionViewCell
,并用xib拖上一個(gè)imageview用于展示圖片盏浇,一個(gè)button用于點(diǎn)擊關(guān)閉cell变丧。并定義好解析item的方法。item中設(shè)定一個(gè)delBtnHidden屬性用于設(shè)定cell里的button是否隱藏绢掰。
- (void)setItem:(CDZCollectionViewItem *)item{
// 解析需要的數(shù)據(jù)
self.imageView.image = item.image;
self.delButton.hidden = item.delBtnHidden;
}
并定義一個(gè)delegate用于將按鈕點(diǎn)擊事件回傳給collectionview并刪除數(shù)據(jù)痒蓬。
@protocol CDZCollectionCellDelegate<NSObject>
- (void)didDelete:(UICollectionViewCell *)cell;
@end
@interface CDZCollectionViewCell : UICollectionViewCell
@property (strong, nonatomic) CDZCollectionViewItem *item;
@property (assign, nonatomic) id<CDZCollectionCellDelegate> delegate;
@end
并在點(diǎn)擊按鈕的事件中調(diào)用delegate,把cell本身傳回tableview滴劲,從而找到cell對(duì)應(yīng)的item攻晒。
- (IBAction)delCell:(UIButton *)sender{
if ([self.delegate respondsToSelector:@selector(didDelete:)]){
[self.delegate didDelete:self];
}
}
然后使collectionview所在的tableviewcell遵守CDZCollectionCellDelegate
。并和之前一樣班挖,在collectionview的datasource或delegate中設(shè)置delegate鲁捏。
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
//......
cell.delegate = self;
//......
}
并調(diào)用cell的delegate的方法,通過cell去找到對(duì)應(yīng)的indexPath
- (void)didDelete:(UICollectionViewCell *)cell{
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
[self.itemsArray removeObjectAtIndex:indexPath.row];
[self reloadCell];
}
第一個(gè)cell是沒有關(guān)閉按鈕的萧芙,那么只要把第一個(gè)item的delBthHidden屬性設(shè)為YES就可以了给梅。
最后
所有源碼和Demo
如果您覺得有幫助,不妨給個(gè)star鼓勵(lì)一下,歡迎關(guān)注&交流
有任何問題歡迎評(píng)論私信或者提issue
QQ:757765420
Email:nemocdz@gmail.com
Github:Nemocdz
微博:@Nemocdz