最近做直播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:

實(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ù)小于kRowCount與kItemCountPerRow的乘積,缺少多少,就補(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)方法,歡迎留言溝通。