滾動(dòng)視圖分頁(yè)
UIScrollView的pagingEnabled屬性用于控制是否按分頁(yè)進(jìn)行滾動(dòng)胜嗓。在一些應(yīng)用中會(huì)應(yīng)用到這一個(gè)特性员帮,最典型的就是手機(jī)桌面的應(yīng)用圖標(biāo)列表惦蚊。這些界面中往往每一頁(yè)功能都比較獨(dú)立,系統(tǒng)也提供了UIPageViewController來實(shí)現(xiàn)這種分頁(yè)滾動(dòng)的功能躬窜。
實(shí)現(xiàn)分頁(yè)滾動(dòng)的UI實(shí)現(xiàn)一般是最外層一個(gè)UIScrollView浇垦。然后UIScrollView里面是一個(gè)總體的容器視圖containerView。容器視圖添加N個(gè)頁(yè)視圖荣挨,對(duì)于水平分頁(yè)滾動(dòng)來說容器視圖的高度和滾動(dòng)視圖一樣男韧,而寬度則是滾動(dòng)視圖的寬度乘以頁(yè)視圖的數(shù)量,頁(yè)視圖的尺寸則和滾動(dòng)視圖保持一致默垄,對(duì)于垂直分頁(yè)滾動(dòng)來說容器視圖的寬度和滾動(dòng)視圖一樣此虑,而高度則是滾動(dòng)視圖的高度乘以頁(yè)視圖的數(shù)量,頁(yè)視圖的尺寸則和滾動(dòng)視圖保持一致厕倍。每個(gè)頁(yè)視圖中在添加各自的條目視圖寡壮。整體效果圖如下:
AutoLayout實(shí)現(xiàn)分頁(yè)滾動(dòng)的方法
根據(jù)上面的UI結(jié)構(gòu)這里用AutoLayout的代碼來實(shí)現(xiàn)水平分頁(yè)的滾動(dòng)贩疙。這里的約束設(shè)置代碼是iOS9以后提供的相關(guān)API讹弯。
- (void)loadView {
UIScrollView *scrollView = [[UIScrollView alloc] init];
if (@available(iOS 11.0, *)) {
scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
// Fallback on earlier versions
}
scrollView.pagingEnabled = YES;
scrollView.backgroundColor = [UIColor whiteColor];
self.view = scrollView;
//建立容器視圖
UIView *containerView = [UIView new];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[scrollView addSubview:containerView];
//設(shè)置容器的四個(gè)邊界和滾動(dòng)視圖保持一致的約束。
[containerView.leftAnchor constraintEqualToAnchor:scrollView.leftAnchor].active = YES;
[containerView.topAnchor constraintEqualToAnchor:scrollView.topAnchor].active = YES;
[containerView.rightAnchor constraintEqualToAnchor:scrollView.rightAnchor].active = YES;
[containerView.bottomAnchor constraintEqualToAnchor:scrollView.bottomAnchor].active = YES;
//容器視圖的高度和滾動(dòng)視圖保持一致这溅。
[containerView.heightAnchor constraintEqualToAnchor:scrollView.heightAnchor].active = YES;
//添加頁(yè)視圖
NSArray<UIColor*> *colors = @[[UIColor redColor],[UIColor greenColor], [UIColor blueColor]];
NSMutableArray<UIView*> *pageViews = [NSMutableArray arrayWithCapacity:colors.count];
NSLayoutXAxisAnchor *prevLeftAnchor = containerView.leftAnchor;
for (int i = 0; i < colors.count; i++)
{
//建立頁(yè)視圖
UIView *pageView = [UIView new];
pageView.backgroundColor = colors[i];
pageView.translatesAutoresizingMaskIntoConstraints = NO;
[containerView addSubview:pageView];
//頁(yè)視圖分別從左往右排列组民,第1頁(yè)的左邊約束是容器視圖的左邊,其他頁(yè)的左邊約束則是前面兄弟視圖的右邊悲靴。
[pageView.leftAnchor constraintEqualToAnchor:prevLeftAnchor].active = YES;
//每頁(yè)的頂部約束是容器視圖臭胜。
[pageView.topAnchor constraintEqualToAnchor:containerView.topAnchor].active = YES;
//每頁(yè)的寬度約束是滾動(dòng)視圖
[pageView.widthAnchor constraintEqualToAnchor:scrollView.widthAnchor].active = YES;
//每頁(yè)的高度約束是滾動(dòng)視圖
[pageView.heightAnchor constraintEqualToAnchor:scrollView.heightAnchor].active = YES;
prevLeftAnchor = pageView.rightAnchor;
[pageViews addObject:pageView];
}
//關(guān)鍵的一步,如果需要左右滾動(dòng)則將容器視圖中的最右部子視圖這里是B的右邊邊界依賴于容器視圖的右邊邊界。
[pageViews.lastObject.rightAnchor constraintEqualToAnchor:containerView.rightAnchor].active = YES;
//這里可以為每個(gè)頁(yè)視圖添加不同的條目視圖耸三,具體實(shí)現(xiàn)大家自行添加代碼吧乱陡。
}
下面是運(yùn)行時(shí)的效果圖:
MyLayout實(shí)現(xiàn)分頁(yè)滾動(dòng)的方法
你也可以用MyLayout布局庫(kù)來實(shí)現(xiàn)分頁(yè)滾動(dòng)的能力。MyLayout布局庫(kù)是筆者開源的一套功能強(qiáng)大的UI布局庫(kù)仪壮。
您可以從github地址: https://github.com/youngsoft/MyLinearLayout 下載或者從podfile中導(dǎo)入:
pod 'MyLayout'
來使用MyLayout憨颠。下面是具體用MyLayout來實(shí)現(xiàn)分頁(yè)滾動(dòng)的代碼。
//
#import <MyLayout.h>
- (void)loadView {
UIScrollView *scrollView = [[UIScrollView alloc] init];
if (@available(iOS 11.0, *)) {
scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
// Fallback on earlier versions
}
scrollView.pagingEnabled = YES;
scrollView.backgroundColor = [UIColor whiteColor];
self.view = scrollView;
//建立一個(gè)水平線性布局容器視圖
MyLinearLayout *containerView = [MyLinearLayout linearLayoutWithOrientation:MyOrientation_Horz];
containerView.myVertMargin = 0; //水平線性布局的上下邊界和滾動(dòng)視圖保持一致积锅,這里也會(huì)確定線性布局的高度爽彤。
containerView.gravity = MyGravity_Vert_Fill | MyGravity_Horz_Fill; //設(shè)置線性布局中的所有子視圖均分和填充線性布局的高度和寬度。
[scrollView addSubview:containerView];
//添加頁(yè)視圖
NSArray<UIColor*> *colors = @[[UIColor redColor],[UIColor greenColor], [UIColor blueColor]];
NSMutableArray<UIView*> *pageViews = [NSMutableArray arrayWithCapacity:colors.count];
for (int i = 0; i < colors.count; i++)
{
//建立頁(yè)視圖
UIView *pageView = [UIView new];
pageView.backgroundColor = colors[i];
[containerView addSubview:pageView];
//因?yàn)榫€性布局通過屬性gravity的設(shè)置就可以確定子頁(yè)視圖的高度和寬度缚陷,再加上線性布局的特性适篙,所以頁(yè)視圖不需要設(shè)置任何附加的約束。
[pageViews addObject:pageView];
}
//關(guān)鍵的一步, 設(shè)置線性布局的寬度是滾動(dòng)視圖的倍數(shù)
containerView.widthSize.equalTo(scrollView.widthSize).multiply(colors.count);
//這里可以為每個(gè)頁(yè)視圖添加不同的條目視圖箫爷,具體實(shí)現(xiàn)大家自行添加代碼吧嚷节。
}
MyLayout實(shí)現(xiàn)桌面的圖標(biāo)列表分頁(yè)功能
MyLayout中的流式布局MyFlowLayout所具備的能力和flex-box相似,甚至有些特性要強(qiáng)于后者蝶缀。流式布局用于一些子視圖有規(guī)律排列的場(chǎng)景丹喻,就比如本例子中的滾動(dòng)分頁(yè)的圖標(biāo)列表的能力。下面就是具體的實(shí)現(xiàn)代碼翁都。
- (void)loadView {
UIScrollView *scrollView = [[UIScrollView alloc] init];
if (@available(iOS 11.0, *)) {
scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
// Fallback on earlier versions
}
scrollView.pagingEnabled = YES;
scrollView.backgroundColor = [UIColor whiteColor];
self.view = scrollView;
//建立一個(gè)垂直數(shù)量約束流式布局:每列展示3個(gè)子視圖,每頁(yè)展示9個(gè)子視圖碍论,整體從左往右滾動(dòng)。
MyFlowLayout *containerView = [MyFlowLayout flowLayoutWithOrientation:MyOrientation_Vert arrangedCount:3];
containerView.pagedCount = 9; //pagedCount設(shè)置為非0時(shí)表示開始分頁(yè)展示的功能柄慰,這里表示每頁(yè)展示9個(gè)子視圖鳍悠,這個(gè)數(shù)量必須是arrangedCount的倍數(shù)。
containerView.wrapContentWidth = YES; //設(shè)置布局視圖的寬度由子視圖包裹坐搔,當(dāng)垂直流式布局的這個(gè)屬性設(shè)置為YES藏研,并和pagedCount搭配使用會(huì)產(chǎn)生分頁(yè)從左到右滾動(dòng)的效果。
containerView.myVertMargin = 0; //容器視圖的高度和滾動(dòng)視圖保持一致概行。
containerView.subviewHSpace = 10;
containerView.subviewVSpace = 10; //設(shè)置子視圖的水平和垂直間距蠢挡。
containerView.padding = UIEdgeInsetsMake(5, 5, 5, 5); //布局視圖的內(nèi)邊距設(shè)置。
[scrollView addSubview:containerView];
//建立條目視圖
for (int i = 0; i < 40; i++)
{
UILabel *label = [UILabel new];
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = [UIColor greenColor];
label.text = [NSString stringWithFormat:@"%d",i];
[containerView addSubview:label];
}
//獲取流式布局的橫屏size classes凳忙,并且設(shè)置設(shè)備處于橫屏?xí)r,每排數(shù)量由3個(gè)變?yōu)?個(gè)业踏,每頁(yè)的數(shù)量由9個(gè)變?yōu)?8個(gè)。
MyFlowLayout *containerViewSC = [containerView fetchLayoutSizeClass:MySizeClass_Landscape copyFrom:MySizeClass_wAny | MySizeClass_hAny];
containerViewSC.arrangedCount = 6;
containerViewSC.pagedCount = 18;
從上面的代碼可以看出要實(shí)現(xiàn)分頁(yè)滾動(dòng)的圖標(biāo)列表的能力涧卵,主要是對(duì)充當(dāng)容器視圖的流式布局設(shè)置一些屬性即可勤家,不需要為條目設(shè)置任何約束,而且還支持橫豎屏下每頁(yè)的不同數(shù)量的展示能力柳恐。整個(gè)功能代碼量少伐脖,對(duì)比用UICollectionView來實(shí)現(xiàn)相同的功能要簡(jiǎn)潔和容易得多热幔。下面是程序運(yùn)行的效果:
橫豎屏切換
對(duì)于帶有分頁(yè)功能的滾動(dòng)視圖來說,當(dāng)需要支持橫豎屏?xí)r就有可能會(huì)出現(xiàn)橫豎屏切換時(shí)界面停留在兩個(gè)頁(yè)面中間而不是按頁(yè)進(jìn)行滾動(dòng)的效果讼庇。其原因是無論是分頁(yè)滾動(dòng)還是不分頁(yè)滾動(dòng)绎巨,在滾動(dòng)時(shí)都是通過調(diào)整滾動(dòng)視圖的contentOffset來實(shí)現(xiàn)的。而當(dāng)滾動(dòng)視圖進(jìn)行橫豎屏切換時(shí)不會(huì)調(diào)整對(duì)應(yīng)的contentOffset值蠕啄,這樣就導(dǎo)致了在屏幕方向切換時(shí)的滾動(dòng)位置出現(xiàn)異常认烁。解決的辦法就是在屏幕滾動(dòng)時(shí)的相應(yīng)回調(diào)處理方法中修正這個(gè)contentOffset的值來解決這個(gè)問題。比如我們可以在屏幕切換的sizeclass變化的視圖控制器的協(xié)議方法中添加如下代碼:
- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
UIScrollView *scrollView = (UIScrollView*)self.view;
//根據(jù)當(dāng)前的contentOffset調(diào)整到正確的contentOffset
int pageIndex = scrollView.contentOffset.x / scrollView.frame.size.width;
int pages = scrollView.contentSize.width / scrollView.frame.size.width;
if (pageIndex >= pages)
pageIndex = pages - 1;
if (pageIndex < 0)
pageIndex = 0;
scrollView.contentOffset = CGPointMake(pageIndex * scrollView.frame.size.width, scrollView.contentOffset.y);
}