UICollectionView:支持横向滑动分页

最近做直播APP,坑爹的产品想要实现一个礼物列表,列表中有多种类型的礼物,每个类型下面又有n个礼物,礼物分页展示,可以左右滑动,当滑到分类最后一页自动切换到下个分类,且礼物列表支持横竖屏切换。

备注:下文中“每一类”、“组”和“类别”概念相同,都指一类礼物

横竖屏切换:

竖屏时,每个分类要显示当前多少页,以及定位当前第几页,当pageCount<=1时,不显示页码。
横屏时,不显示页码,左右滑动可以切换类别,同一类别可以上下滑动。

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

先看一下效果图gif:


演示.gif
实现思路:

第一眼看到这个需求,就确定通过UICollectionView实现,但是UICollectionView并不支持横向换行分页,后来通过网上找了个第三方类进行修改,支持了横向换行分页,但是滑到分类最后一页不能自动切换到下个分类,分类之间只能通过点击tab切换,由于向产品各种保证二期修改 o(╯□╰)o,第一版就这样勉强上线了。

二期:经过仔细分析需求和对以后产品扩展,以及一些零碎的用户体验等综合考虑,决定采用下面实现方法,也即是目前实现方法。

实现方法:

1.横屏和竖屏各写一套布局,MAItemListPortraitView是竖屏布局,MAItemListLandscapeView是横屏布局,将两个布局加到父视图MAItemListView上,默认是竖屏布局。当用户旋转手机屏幕,触发方法,动态切换布局:横屏时隐藏竖屏布局,显示横屏布局;竖屏时隐藏横屏布局,显示竖屏布局。

2.竖屏时MAItemListPortraitView

声明两个参数:
kRowCount:每一页显示多少行
kItemCountPerRow:每一行显示多少个

为了既支持换行横向分页滑动,又支持不同类别间能互相滑动,在竖屏时使用一个UICollectionView,如果某个类别中最后一页的礼物数小于kRowCountkItemCountPerRow的乘积,缺少多少,就补上相应数目的假数据,凑成整页,整合完每个类别的数据,将所有类别数据加到同一个数组中,供UICollectionView使用。

3.横屏时MAItemListLandscapeView
为了既能左右滑动切换不同类别礼物,又能在同一类别中上下滑动查看该类别全部礼物,最底层是一个UIScrollView,设置只能横向滑动,然后为每个类别初始化一个UICollectionView,将UICollectionView加到UIScrollView上,UICollectionView设置只能竖向滑动。

具体实现 及 核心代码

UIViewController接受sizeClass变化回调,在其中触发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;
//数据源
@property (nonatomic, strong) NSArray *listsData;
@end

备注:重写itemListLayoutType的setter方法,以切换横竖屏布局

2.竖屏实现:
① 在拿到数据源后,首先进行计算数据源信息:

  • groupPageIndexs:每一类(组)的索引
  • groupPageCounts:每一类(组)的页数
  • groupTotalPageCount:总页数

② 整合数据:
循环遍历每一组数据,将每一组最后一页补齐,用于补齐的假数据打上isMock标,用于区分真伪数据,并将整合后的数据加到数组totalItemList中。

- (void)reDefineData
{
    NSMutableArray *tempArray = [NSMutableArray array];
    for (MAApiItemListModel *listModel in _listsData) {
        
        // 理论上每页展示的item数目
        NSInteger itemCount = kItemCountPerRow * kRowCount;
        // 余数(用于确定最后一页展示的item个数)
        NSInteger remainder = listModel.itemList.count % itemCount;
        
        NSMutableArray *arr = [NSMutableArray arrayWithArray:listModel.itemList];
        
        if (remainder > 0 && remainder < itemCount) {
            //如果不够一页,就加人假数据,凑成一页
            for (NSInteger i = 0; i < itemCount - remainder; i++) {
                MAApiItemItemModel *itemModel = [[MAApiItemItemModel alloc] init];
                itemModel.isMock = YES;//标记这条是假数据
                [arr addObject:itemModel];
            }
        }
        [tempArray addObjectsFromArray:arr];
    }
    self.totalItemList = tempArray;
}

3.横屏实现:
在拿到数据源后,根据listsData.count初始化对应数目的UICollectionView,并设置不同的tag,注册不同的重用标识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.滑动ScrollView,实现分页效果:
scrollViewDidEndDecelerating方法中计算UIScrollView或UICollectionView的偏移量,来计算当前是第几组、该组有多少页以及当前是第几页。

注意事项

1.使用- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated;方法设置滚动时,一定要设置UIScrollView的contentSize。但在我的开发过程中,这个方法有时会失灵(原因不详),推荐使用- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;设置滚动。

2.在Autolayout与frame混用时,在设置完约束后要紧接着调用[self layoutIfNeeded]进行重绘,否则会出现UIView的frame为(0,0,0,0)的情况。

备注

1.demo中的- (void)locatedItem:(MAApiItemItemModel *)itemModel positionBlock:(void(^)(CGRect frame,CGPoint point))block方法是为了计算竖屏(横屏)选中的cell在横屏(竖屏)中的位置。

2.不经常写文章,在这班门弄斧了,水平有限,如若发现有错误或者有更好的实现方法,欢迎留言沟通。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容