寫在前面
筆者按照Instagram的圖片選取器寫了個小Demo慨仿,
該系列文章是以實現(xiàn)Demo目的來逐個介紹使用到的東西灶体,若有不正確的地方還望指出來其障,共同學(xué)習(xí)。
地址:https://github.com/BigBigPo/RJPhotoPicker
UICollectionView鳄逾,自從推出一來就受到廣大的iOS開發(fā)者對其贊不絕口稻轨,其高度的靈活性使得其自身的可定制化程度極高。開發(fā)者們用其做出了諸多絢麗的效果雕凹。
這要歸功于UICollectionViewLayout殴俱,它CollectionView進行自定義布局的重要基石,只有掌握了它才能說自己掌握了CollectionView枚抵,Layout到底有多強大线欲,筆者會單獨新開一篇來介紹。本節(jié)主要還是以達到我們系列文章所要實現(xiàn)的Demo效果為目的汽摹,對CollectionView有一個簡單的介紹李丰。
好啦,我們先看一下我們要仿照的Instagram的圖片選擇器
上一節(jié)我們實現(xiàn)了上方“展示區(qū)”的效果逼泣,十分簡單趴泌,核心就是UIScrollView的縮放與圖片的布局更新舟舒。這一節(jié)我們要實現(xiàn)下方的列表。
下方的列表可以直接想到UICollectionView,它對于這種類型的列表再適合不過了嗜憔,實現(xiàn)起來非常簡單秃励,好在Instagram的列表部分并不復(fù)雜,我們不需要自己去自定義collectionViewLayout吉捶,使用系統(tǒng)提供的UICollectionViewFlowLayout就可以輕松實現(xiàn)這種流式布局的效果夺鲜。
至于UICollectionView與UITableView的關(guān)系······
UICollectionView完全可以實現(xiàn)UITableView的效果,之前看到有大牛發(fā)現(xiàn)了UIKit框架中更新了UICollectionViewTableLayout這樣的東西(似乎是這個名字呐舔,不知真假币励。)【iOS14中已添加,詳見Lists】滋早,兩者的關(guān)系就不言而喻了榄审。
1. UICollectionView的基本使用
UICollectionView的使用上與UITableVIew極其類似,兩者都是繼承自UIScrollView杆麸,具備ScrollView的所有特性,UICollectionView的初始化是這樣的
_collectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
[_collectionView setDelegate:self];
[_collectionView setDataSource:self];
[_collectionView registerNib:[UINib nibWithNibName:@"RJPhotoCell" bundle:nil] forCellWithReuseIdentifier:RJPhotoPickerCellID];
初始化時附帶了layout浪感,布局昔头,決定了UICollectionView會以何種方式展示,如行間距影兽,item的大小揭斧,間隔大小等等,這些會在談layout的文章中再展開峻堰,我們的相冊選擇器直接用系統(tǒng)提供的流式布局UICollectionViewFlowLayout讹开,不用我們自己操心如何去寫布局的代碼。
UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc] init];
layout.itemSize = cellSize;
layout.minimumInteritemSpacing = 1;
layout.minimumLineSpacing = 1;
我們這里只設(shè)置了間距與大小捐名,這些就足夠了旦万,當然,這些配置也
可以留到layout的代理里面設(shè)置镶蹋,但我們的選擇器并沒有復(fù)雜的布局成艘,專門寫在代理里有點奇怪。
順帶提一下贺归,UICollectionViewFlowLayout最重要的屬性 scrollDirection沒有在這里進行設(shè)置淆两,其包含兩種流式布局的方向:
typedef NS_ENUM(NSInteger, UICollectionViewScrollDirection) {
UICollectionViewScrollDirectionVertical,
UICollectionViewScrollDirectionHorizontal
};
其默認情況下就是垂直方向布局(UICollectionViewScrollDirectionVertical),也就是我們需要的樣子拂酣。
常用代理方法
//section的數(shù)量
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
//對應(yīng)section的item數(shù)量
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
//cell的代理秋冰,只要是繼承自UICollectionViewCell的都可以
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
//header與footer,kind是header與footer的類型區(qū)別
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
//item的點擊事件
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
//當item即將顯示的時候回觸發(fā)該代理
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);
是不是很眼熟婶熬,怎么跟UITableView的代理那么相似剑勾?是的光坝,他們在設(shè)計上是一樣的,只是UICollectionView更加的靈活甥材。這里就不再對代理進行贅述了盯另,用法跟UITableView是一樣的,更多的的代理可以自行查看洲赵,對于我們要實現(xiàn)的demo鸳惯,有這些就足夠了。
掌握了這些基本的知識點就完全可以用UICollectionView實現(xiàn)一個流式布局的列表叠萍。
2. 列表與展示區(qū)域的聯(lián)動
ins的展示區(qū)域與列表是有聯(lián)動效果的芝发,具體如下:
(1) 當向上滑動列表時,上方展示區(qū)域會上移至只留下40px左右的大小苛谷。且該效果需要在上滑列表直至手指觸碰到展示區(qū)域邊界才會開始觸發(fā)辅鲸。
(2)當下滑動列表時,展示區(qū)域會在列表滑動至頂端時開始跟隨列表滑動腹殿,直至占據(jù)一半的屏幕為止独悴。
(3)展示區(qū)域的下方越有40px高度的區(qū)域有手勢效果,可以將處于隱藏狀態(tài)的展示區(qū)域拖動顯示出來锣尉。
前兩點我們會放在列表(UICollectionView上做)刻炒,我們要去獲取什么數(shù)據(jù)來知道列表滾動了?并且能夠拿到對應(yīng)的數(shù)值自沧?
這個時候會首先想到UICollectionView是繼承自UIScrollView的坟奥,想要知道是否滑動,并且要拿到滑動的數(shù)值拇厢,UIScrollView是提供的有代理的:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
該代理只要有滑動的事件發(fā)生都會被觸發(fā)爱谁,我們可以根據(jù)拿到scrollView的contentOffset來獲取滑動的數(shù)據(jù)。然后根據(jù)得到的數(shù)值來推斷出展示區(qū)所應(yīng)該在的位置孝偎。
但是
UIScrollView提供的代理(didScroll)是在滑動發(fā)生后才會觸發(fā)访敌,是已經(jīng)滑動了,這個時候我們?nèi)舾鶕?jù)此數(shù)據(jù)來決定上方展示區(qū)域的位置邪媳,而我們的聯(lián)動是及時的捐顷,若在scrollView后再根據(jù)拿到的數(shù)據(jù)來處理,則會造成顯示異常雨效,例如迅涮,效果(1)中需要展示區(qū)域聯(lián)動時,展示區(qū)域會發(fā)生輕微的抖動(這個肯定不能忍)徽龟。
聯(lián)動效果叮姑,并不僅僅需要滑動數(shù)值,還需要更加詳盡的滑動狀態(tài),如:滑動的開始與結(jié)束传透,滑動距離耘沼,方向等等,以此來觸發(fā)是否需要計算上方展示區(qū)域的位置朱盐。若只是根據(jù)UIScrollView的代理來獲取的確是有點復(fù)雜群嗤,而且也會存在剛提到的【滑動數(shù)據(jù)獲取的時機】問題,我們需要及時的獲取到滑動的數(shù)據(jù)兵琳。
這個時候筆者想到的是這幾個方法:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
在這幾個方法這里來獲取數(shù)據(jù)狂秘,時機肯定是沒問題了,但筆者擔心在此處若操作不當會引發(fā)其他地方的手勢沖突躯肌。但我們并沒有對視圖進行任何操作者春,我們只是獲取到其數(shù)據(jù)即可,不存在手勢沖突的可能性清女。
抱著試試看的心態(tài)钱烟,筆者生成了一個UICollectionView的子類,在子類中實現(xiàn)這些方法嫡丙,以此來獲取所有我們需要的信息拴袭,筆者采用一個Block來進行數(shù)據(jù)的統(tǒng)一回調(diào):
//數(shù)據(jù)的回調(diào),滑動開始的y坐標點(x的坐標對于該demo效果沒有意義),Y軸的移動距離迄沫,以及動作完成的標識稻扬。
typedef void(^ScrollToTopMoreBlock)(CGFloat startY, CGFloat moveY, BOOL isEnd);
而整個子類的功能,都只是在圍繞獲取滑動的數(shù)據(jù)來進行的:
- (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view {
//滑動開始
UITouch * touch = [touches anyObject];
_touchPoint = [touch locationInView:self];
_isTouch = YES;
return YES;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//滑動開始
[super touchesBegan:touches withEvent:event];
UITouch * touch = [touches anyObject];
_isTouch = YES;
_touchPoint = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
//滑動中羊瘩,獲取滑動的距離,并回調(diào)
UITouch * touch = [touches anyObject];
_isTouch = YES;
CGPoint movePoint = [touch locationInView:self];
if (_moveBlock) {
_moveBlock(_touchPoint.y, movePoint.y - _touchPoint.y, NO);
}
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
//滑動結(jié)束
[self endScrollTopEventWithTouch:[touches anyObject]];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
//滑動結(jié)束
[self endScrollTopEventWithTouch:[touches anyObject]];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
gestureRecognizer.cancelsTouchesInView = NO;
}
return YES;
}
- (void)setScrollBlock:(void(^)(CGFloat startY, CGFloat moveY, BOOL isEnd))block {
_moveBlock = block;
}
- (void)endScrollTopEventWithTouch:(UITouch *)touch {
if (_moveBlock) {
_isTouch = YES;
CGPoint movePoint = [touch locationInView:self];
_moveBlock(_touchPoint.y, movePoint.y - _touchPoint.y, YES);
}
}
有了這些實時的數(shù)據(jù)盼砍,我們就可以完成我們的聯(lián)動效果尘吗。聯(lián)動的邏輯稍稍有點復(fù)雜,這里就不列舉出來了···可以先自行實現(xiàn)以下看看浇坐,若沒有什么思路睬捶,可以參考下筆者的方式。筆者的方式稍顯笨拙近刘,并不適宜在此處展開擒贸。感興趣的朋友可以看看Demo。
其他 CollectionView 相關(guān)內(nèi)容:
1. iOS 自定義圖片選擇器 3 - 相冊列表的實現(xiàn)
2. UICollectionView自定義布局基礎(chǔ)
3. UICollectionView自定義拖動重排
4. iOS13 中的 CompositionalLayout 與 DiffableDataSource
5. iOS14 中的UICollectionViewListCell觉渴、UIContentConfiguration 以及 UIConfigurationState