UICollectionView:支持橫向滑動(dòng)分頁

最近做直播APP,坑爹的產(chǎn)品想要實(shí)現(xiàn)一個(gè)禮物列表,列表中有多種類型的禮物,每個(gè)類型下面又有n個(gè)禮物,禮物分頁展示,可以左右滑動(dòng),當(dāng)滑到分類最后一頁自動(dòng)切換到下個(gè)分類,且禮物列表支持橫豎屏切換。

備注:下文中“每一類”、“組”和“類別”概念相同,都指一類禮物

橫豎屏切換:

豎屏?xí)r,每個(gè)分類要顯示當(dāng)前多少頁,以及定位當(dāng)前第幾頁,當(dāng)pageCount<=1時(shí),不顯示頁碼。
橫屏?xí)r,不顯示頁碼,左右滑動(dòng)可以切換類別,同一類別可以上下滑動(dòng)。

工程地址:https://github.com/ma762614600/MAHorizontalCollectionView

先看一下效果圖gif:


演示.gif
實(shí)現(xiàn)思路:

第一眼看到這個(gè)需求,就確定通過UICollectionView實(shí)現(xiàn),但是UICollectionView并不支持橫向換行分頁,后來通過網(wǎng)上找了個(gè)第三方類進(jìn)行修改,支持了橫向換行分頁,但是滑到分類最后一頁不能自動(dòng)切換到下個(gè)分類,分類之間只能通過點(diǎn)擊tab切換,由于向產(chǎn)品各種保證二期修改 o(╯□╰)o,第一版就這樣勉強(qiáng)上線了。

二期:經(jīng)過仔細(xì)分析需求和對(duì)以后產(chǎn)品擴(kuò)展,以及一些零碎的用戶體驗(yàn)等綜合考慮,決定采用下面實(shí)現(xiàn)方法,也即是目前實(shí)現(xiàn)方法。

實(shí)現(xiàn)方法:

1.橫屏和豎屏各寫一套布局,MAItemListPortraitView是豎屏布局,MAItemListLandscapeView是橫屏布局,將兩個(gè)布局加到父視圖MAItemListView上,默認(rèn)是豎屏布局。當(dāng)用戶旋轉(zhuǎn)手機(jī)屏幕,觸發(fā)方法,動(dòng)態(tài)切換布局:橫屏?xí)r隱藏豎屏布局,顯示橫屏布局;豎屏?xí)r隱藏橫屏布局,顯示豎屏布局。

2.豎屏?xí)rMAItemListPortraitView

聲明兩個(gè)參數(shù):
kRowCount:每一頁顯示多少行
kItemCountPerRow:每一行顯示多少個(gè)

為了既支持換行橫向分頁滑動(dòng),又支持不同類別間能互相滑動(dòng),在豎屏?xí)r使用一個(gè)UICollectionView,如果某個(gè)類別中最后一頁的禮物數(shù)小于kRowCountkItemCountPerRow的乘積,缺少多少,就補(bǔ)上相應(yīng)數(shù)目的假數(shù)據(jù),湊成整頁,整合完每個(gè)類別的數(shù)據(jù),將所有類別數(shù)據(jù)加到同一個(gè)數(shù)組中,供UICollectionView使用。

3.橫屏?xí)rMAItemListLandscapeView
為了既能左右滑動(dòng)切換不同類別禮物,又能在同一類別中上下滑動(dòng)查看該類別全部禮物,最底層是一個(gè)UIScrollView,設(shè)置只能橫向滑動(dòng),然后為每個(gè)類別初始化一個(gè)UICollectionView,將UICollectionView加到UIScrollView上,UICollectionView設(shè)置只能豎向滑動(dòng)。

具體實(shí)現(xiàn) 及 核心代碼

UIViewController接受sizeClass變化回調(diào),在其中觸發(fā)MAItemListView的布局改變:

- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [super willTransitionToTraitCollection:newCollection
                 withTransitionCoordinator:coordinator];
    [coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context)
     {
         if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
             //緊湊的高 橫屏
             [_itemListView setItemListLayoutType:ItemListLayoutTypeLandscape];
         } else {
             //正常的高
             [_itemListView setItemListLayoutType:ItemListLayoutTypePortrait];
         }
         [self.view setNeedsLayout];
     } completion:nil];
}

1.父視圖MAItemListView的頭文件

#import <UIKit/UIKit.h>
#import "MAApiItemListModel.h"

typedef NS_ENUM(NSInteger, ItemListViewLayoutType) {
    ItemListLayoutTypePortrait = 0,  //豎屏
    ItemListLayoutTypeLandscape  //橫屏
};

//禮物列表
@interface MAItemListView : UIView
//切換橫豎屏
@property (nonatomic, assign) ItemListViewLayoutType itemListLayoutType;
//數(shù)據(jù)源
@property (nonatomic, strong) NSArray *listsData;
@end

備注:重寫itemListLayoutType的setter方法,以切換橫豎屏布局

2.豎屏實(shí)現(xiàn):
① 在拿到數(shù)據(jù)源后,首先進(jìn)行計(jì)算數(shù)據(jù)源信息:

  • groupPageIndexs:每一類(組)的索引
  • groupPageCounts:每一類(組)的頁數(shù)
  • groupTotalPageCount:總頁數(shù)

② 整合數(shù)據(jù):
循環(huán)遍歷每一組數(shù)據(jù),將每一組最后一頁補(bǔ)齊,用于補(bǔ)齊的假數(shù)據(jù)打上isMock標(biāo),用于區(qū)分真?zhèn)螖?shù)據(jù),并將整合后的數(shù)據(jù)加到數(shù)組totalItemList中。

- (void)reDefineData
{
    NSMutableArray *tempArray = [NSMutableArray array];
    for (MAApiItemListModel *listModel in _listsData) {
        
        // 理論上每頁展示的item數(shù)目
        NSInteger itemCount = kItemCountPerRow * kRowCount;
        // 余數(shù)(用于確定最后一頁展示的item個(gè)數(shù))
        NSInteger remainder = listModel.itemList.count % itemCount;
        
        NSMutableArray *arr = [NSMutableArray arrayWithArray:listModel.itemList];
        
        if (remainder > 0 && remainder < itemCount) {
            //如果不夠一頁,就加人假數(shù)據(jù),湊成一頁
            for (NSInteger i = 0; i < itemCount - remainder; i++) {
                MAApiItemItemModel *itemModel = [[MAApiItemItemModel alloc] init];
                itemModel.isMock = YES;//標(biāo)記這條是假數(shù)據(jù)
                [arr addObject:itemModel];
            }
        }
        [tempArray addObjectsFromArray:arr];
    }
    self.totalItemList = tempArray;
}

3.橫屏實(shí)現(xiàn):
在拿到數(shù)據(jù)源后,根據(jù)listsData.count初始化對(duì)應(yīng)數(shù)目的UICollectionView,并設(shè)置不同的tag,注冊(cè)不同的重用標(biāo)識(shí)ReuseIdentifier

- (void)initCollectionViews
{
    _bgScrollView.contentSize = CGSizeMake(LandscapeItemListViewWidth * _listsData.count, self.height_t);
    

    CGFloat x_offset = 0;
    for (NSInteger i = 0; i < _listsData.count; i++) {
        
        UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
        flowLayout.itemSize = CGSizeMake(75, 75);
        flowLayout.minimumInteritemSpacing = 25/4;
        
        CGRect frame = CGRectMake(x_offset, 15, LandscapeItemListViewWidth, _bgScrollView.height_t - 15);
        UICollectionView *landscapeCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:flowLayout];
        landscapeCollectionView.tag = 100+i;
        landscapeCollectionView.dataSource = self;
        landscapeCollectionView.delegate = self;
        landscapeCollectionView.alwaysBounceHorizontal = NO;
        landscapeCollectionView.alwaysBounceVertical = YES;
        landscapeCollectionView.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
        landscapeCollectionView.showsHorizontalScrollIndicator = NO;
        landscapeCollectionView.showsVerticalScrollIndicator = NO;
        [_bgScrollView addSubview:landscapeCollectionView];
        
        NSString *identifier = [NSString stringWithFormat:@"ItemLandscapeCollectionCellIdentifier_%ld",landscapeCollectionView.tag];
        [landscapeCollectionView registerClass:[MAItemCell class] forCellWithReuseIdentifier:identifier];
        
        x_offset += LandscapeItemListViewWidth;
        [landscapeCollectionView reloadData];
    }
    [self layoutIfNeeded];
}

4.滑動(dòng)ScrollView,實(shí)現(xiàn)分頁效果:
scrollViewDidEndDecelerating方法中計(jì)算UIScrollView或UICollectionView的偏移量,來計(jì)算當(dāng)前是第幾組、該組有多少頁以及當(dāng)前是第幾頁。

注意事項(xiàng)

1.使用- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated;方法設(shè)置滾動(dòng)時(shí),一定要設(shè)置UIScrollView的contentSize。但在我的開發(fā)過程中,這個(gè)方法有時(shí)會(huì)失靈(原因不詳),推薦使用- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;設(shè)置滾動(dòng)。

2.在Autolayout與frame混用時(shí),在設(shè)置完約束后要緊接著調(diào)用[self layoutIfNeeded]進(jìn)行重繪,否則會(huì)出現(xiàn)UIView的frame為(0,0,0,0)的情況。

備注

1.demo中的- (void)locatedItem:(MAApiItemItemModel *)itemModel positionBlock:(void(^)(CGRect frame,CGPoint point))block方法是為了計(jì)算豎屏(橫屏)選中的cell在橫屏(豎屏)中的位置。

2.不經(jīng)常寫文章,在這班門弄斧了,水平有限,如若發(fā)現(xiàn)有錯(cuò)誤或者有更好的實(shí)現(xiàn)方法,歡迎留言溝通。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容