乾貨之UICollectionViewFlowLayout自定義排序和拖拽手勢

使用UICollectionView,須要使用UICollectionViewLayout控制UICollectionViewCell佈局,雖然UICollectionViewLayout提供了高度自定義空間,可是對於平常使用顯得太繁瑣,因而常見使用UICollectionViewFlowLayout。除了提供UITableView相似的協議方法,後者還提供了協議UICollectionViewDelegateFlowLayout <UICollectionViewDelegate>,定義了返回cell尺寸、間距,section的insets,header、footer尺寸等方法。從iOS9開始,UICollectionViewDelegate增長了cell的move相關的協議方法。可是不一樣尺寸的cell排序效果仍須要自行實現。git

爲了支持iOS7以上,實現常見不一樣尺寸cell自定義排序,故實現了一個UICollectionViewFlowLayout的子類,並提供了一個UICollectionViewLayout分類支持自定義拖拽手勢。github

查閱了一些資料,實現不一樣尺寸cell自定義排序的文章已經很多,常見都是描述了實現不具備header和footer的獨個section的items排序,可是基本都未完整支持UICollectionViewFlowLayout的功能。數組

我封裝了一個子類,徹底支持UICollectionViewFlowLayout的功能,並額外提供了設置section背景顏色和全部section的items規則排序的擴展功能,鑑於代碼數量,在此記錄一下關鍵的實現過程。app

 

ALWCollectionViewFlowLayout佈局

1.設置section背景顏色動畫

若是設置了UICollectionView的背景色,可是須要不一樣section顯示不一樣顏色,就只須要自行在子類實現了。spa

UICollectionView的內容view能夠分爲三類:SupplementaryView(header和footer),Cell(item),DecorationView(多用在cell下層)code

設置section的背景色,能夠控制decorationView的背景色來實現。orm

UICollectionViewLayout提供了一個以下方法對象

- (void)registerClass:(nullable Class)viewClass forDecorationViewOfKind:(NSString *)elementKind;

能夠在子類init方法中,註冊一個UICollectionReusableView的子類ALWCollectionReusableView,做爲decorationView。在ALWCollectionReusableView中,重載方法- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;這裏的layoutAttributes對象,具備衆多屬性,惟獨沒有一個UIColor。理所固然,再實現一個UICollectionViewLayoutAttributes的子類,增長一個UIColor屬性名爲backgroundColor,能夠設置默認值。而後在applyLayoutAttributes方法中,以下實現:

- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
    [super applyLayoutAttributes:layoutAttributes];
    
    if ([layoutAttributes isMemberOfClass:[ALWCollectionViewLayoutAttributes class]]) {
        self.backgroundColor = ((ALWCollectionViewLayoutAttributes *)layoutAttributes).backgroundColor;
    }
}

 

以上,只是註冊和設置了decorationView的背景色,可是還未設置裝飾view的顯示frame和在合適的時機使其生效。

這個時機,就是UICollectionViewFlowLayout的以下兩個方法

- (void)prepareLayout;

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

前者是設置屬性的時機,後者是使其生效的時機。

 

A.爲了便於動態設置每一個section的背景色,提供了一個協議方法

- (UIColor *)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout backgroundColorForSectionAtIndex:(NSInteger)section;

B.計算decorationView尺寸時候,能夠根據每一個section的首尾item的frame和sectionInset來肯定;當同時有Header和footer時候,也能夠根據兩者來肯定。可是須要注意,若是部分header或者footer未實現,在獲取佈局屬性對象時候會爲nil

C.在UICollectionViewFlowLayout子類中可以使用以下實例方法獲取佈局屬性對象

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

- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;

- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;

可是在UICollectionViewLayout中將會返回nil,可使用UICollectionViewLayoutAttributes的類方法獲得實例

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

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

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

 

2.自定義不一樣尺寸的item的排序

UICollectionViewFlowLayout已經很好的展現了等尺寸的item排列,可是不一樣尺寸的item排列則顯得不規則,若是但願在使用父類的完整功能基礎上,每排item按照固定間距排列,能夠按照以下記錄實現。

爲了間距規則,因此個人實現前提是固定橫向豎向其中一個方向每排的item數量、邊長都相等。以下圖顯示了填充類型的排序演示效果:

 

 

大體記錄一下實現過程:

A.重載方法- (void)prepareLayout,在方法中獲得item、header、footer的佈局屬性數組(NSMutableArray<UICollectionViewLayoutAttributes*>

a.以縱向滑動爲例,循環累加每一個section的header、item、footer高度

b.根據每行item數量決定高度佔用數組元素數量,記錄每列item垂直方向當前佔用的內容高度

c.以填充排序爲例,每一個item的y方向偏移量由高度佔用數組最小元素決定;x方向由sectionInset、item固定的寬度、橫向間距、列的索引共同決定;尺寸方面只須要獲取itemSize協議方法返回尺寸中的高度

 

B.重載方法- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;返回items、header、footer、decorationView的屬性數組

C.重載方法- (CGSize)collectionViewContentSize,返回A過程當中記錄的佔用的最大內容尺寸

D.特別注意,不能在super方法返回的數組基礎上,再添加Header、footer的佈局屬性。這樣可能會出現以下錯誤

layout attributes for supplementary item at index path (<NSIndexPath>) 
changed from <UICollectionViewLayoutAttributes> 
to <UICollectionViewLayoutAttributes>
without invalidating the layout

就是因爲佈局屬性數組中,存在相同indexPath的佈局屬性對象。

 

UICollectionViewLayout (DragGesture)

提供支持iOS7以上的拖拽手勢,適用於全部子類。iOS9之後,UICollectionView提供了move相關方法,只須要添加手勢觸發調用相關方向便可。我實現的分類提供了相似的方法,爲了避免混淆使用,還提供了啓用屬性,默認關閉。

實現過程:

1.交換init方法,在其中增長collectionView屬性的KVO,由於UICollectionView的實例化通常在UICollectionViewLayout以後

2.在UICollectionView實例化後,根據啓用屬性,添加長按手勢和拖動手勢到UICollectionView上

3.若是選中了某個item,開始持續關注拖動位置,進入另外一個item後,交換兩者,實現動畫和數據交換。主要涉及UICollectionView的方法

- (void)performBatchUpdates:(void (^ __nullable)(void))updates completion:(void (^ __nullable)(BOOL finished))completion;

4.增長CADisplayLink對象,根據拖動方向和item所處位置,以屏幕刷新頻率和預約移動速度,自動移動UICollectionView內容偏移量

 

效果圖:

 

備註:目前在自定義排序後,拖拽手勢效果仍存在短暫閃爍的問題,會持續修復。

猜想問題在重載返回屬性數組的方法中,由於使用UICollectionViewFlowLayout默認排序時候,拖拽效果沒有問題。

若是有朋友找到問題所在,請留言,很是感謝。

 

該類庫已經在Base項目中更新:https://github.com/ALongWay/base.git

相關文章
相關標籤/搜索