滾動檢視分頁
UIScrollView的pagingEnabled屬性用於控制是否按分頁進行滾動。在一些應用中會應用到這一個特性,最典型的就是手機桌面的應用圖示列表。這些介面中往往每一頁功能都比較獨立,系統也提供了UIPageViewController來實現這種分頁滾動的功能。 實現分頁滾動的UI實現一般是最外層一個UIScrollView。然後UIScrollView裡面是一個總體的容器檢視containerView。容器檢視新增N個頁檢視,對於水平分頁滾動來說容器檢視的高度和滾動檢視一樣,而寬度則是滾動檢視的寬度乘以頁檢視的數量,頁檢視的尺寸則和滾動檢視保持一致,對於垂直分頁滾動來說容器檢視的寬度和滾動檢視一樣,而高度則是滾動檢視的高度乘以頁檢視的數量,頁檢視的尺寸則和滾動檢視保持一致。每個頁檢視中在新增各自的條目檢視。整體效果圖如下:
AutoLayout實現分頁滾動的方法
根據上面的UI結構這裡用AutoLayout的程式碼來實現水平分頁的滾動。這裡的約束設定程式碼是iOS9以後提供的相關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];
//設定容器的四個邊界和滾動檢視保持一致的約束。
[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;
//容器檢視的高度和滾動檢視保持一致。
[containerView.heightAnchor constraintEqualToAnchor:scrollView.heightAnchor].active = YES;
//新增頁檢視
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++)
{
//建立頁檢視
UIView *pageView = [UIView new];
pageView.backgroundColor = colors[i];
pageView.translatesAutoresizingMaskIntoConstraints = NO;
[containerView addSubview:pageView];
//頁檢視分別從左往右排列,第1頁的左邊約束是容器檢視的左邊,其他頁的左邊約束則是前面兄弟檢視的右邊。
[pageView.leftAnchor constraintEqualToAnchor:prevLeftAnchor].active = YES;
//每頁的頂部約束是容器檢視。
[pageView.topAnchor constraintEqualToAnchor:containerView.topAnchor].active = YES;
//每頁的寬度約束是滾動檢視
[pageView.widthAnchor constraintEqualToAnchor:scrollView.widthAnchor].active = YES;
//每頁的高度約束是滾動檢視
[pageView.heightAnchor constraintEqualToAnchor:scrollView.heightAnchor].active = YES;
prevLeftAnchor = pageView.rightAnchor;
[pageViews addObject:pageView];
}
//關鍵的一步,如果需要左右滾動則將容器檢視中的最右部子檢視這裡是B的右邊邊界依賴於容器檢視的右邊邊界。
[pageViews.lastObject.rightAnchor constraintEqualToAnchor:containerView.rightAnchor].active = YES;
//這裡可以為每個頁檢視新增不同的條目檢視,具體實現大家自行新增程式碼吧。
}
複製程式碼
下面是執行時的效果圖:
MyLayout實現分頁滾動的方法
你也可以用MyLayout佈局庫來實現分頁滾動的能力。MyLayout佈局庫是筆者開源的一套功能強大的UI佈局庫。 您可以從github地址: github.com/youngsoft/M… 下載或者從podfile中匯入:
pod 'MyLayout'
複製程式碼
來使用MyLayout。下面是具體用MyLayout來實現分頁滾動的程式碼。
//
#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;
//建立一個水平線性佈局容器檢視
MyLinearLayout *containerView = [MyLinearLayout linearLayoutWithOrientation:MyOrientation_Horz];
containerView.myVertMargin = 0; //水平線性佈局的上下邊界和滾動檢視保持一致,這裡也會確定線性佈局的高度。
containerView.gravity = MyGravity_Vert_Fill | MyGravity_Horz_Fill; //設定線性佈局中的所有子檢視均分和填充線性佈局的高度和寬度。
[scrollView addSubview:containerView];
//新增頁檢視
NSArray<UIColor*> *colors = @[[UIColor redColor],[UIColor greenColor], [UIColor blueColor]];
NSMutableArray<UIView*> *pageViews = [NSMutableArray arrayWithCapacity:colors.count];
for (int i = 0; i < colors.count; i++)
{
//建立頁檢視
UIView *pageView = [UIView new];
pageView.backgroundColor = colors[i];
[containerView addSubview:pageView];
//因為線性佈局通過屬性gravity的設定就可以確定子頁檢視的高度和寬度,再加上線性佈局的特性,所以頁檢視不需要設定任何附加的約束。
[pageViews addObject:pageView];
}
//關鍵的一步, 設定線性佈局的寬度是滾動檢視的倍數
containerView.widthSize.equalTo(scrollView.widthSize).multiply(colors.count);
//這裡可以為每個頁檢視新增不同的條目檢視,具體實現大家自行新增程式碼吧。
}
複製程式碼
MyLayout實現桌面的圖示列表分頁功能
MyLayout中的流式佈局MyFlowLayout所具備的能力和flex-box相似,甚至有些特性要強於後者。流式佈局用於一些子檢視有規律排列的場景,就比如本例子中的滾動分頁的圖示列表的能力。下面就是具體的實現程式碼。
- (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;
//建立一個垂直數量約束流式佈局:每列展示3個子檢視,每頁展示9個子檢視,整體從左往右滾動。
MyFlowLayout *containerView = [MyFlowLayout flowLayoutWithOrientation:MyOrientation_Vert arrangedCount:3];
containerView.pagedCount = 9; //pagedCount設定為非0時表示開始分頁展示的功能,這裡表示每頁展示9個子檢視,這個數量必須是arrangedCount的倍數。
containerView.wrapContentWidth = YES; //設定佈局檢視的寬度由子檢視包裹,當垂直流式佈局的這個屬性設定為YES,並和pagedCount搭配使用會產生分頁從左到右滾動的效果。
containerView.myVertMargin = 0; //容器檢視的高度和滾動檢視保持一致。
containerView.subviewHSpace = 10;
containerView.subviewVSpace = 10; //設定子檢視的水平和垂直間距。
containerView.padding = UIEdgeInsetsMake(5, 5, 5, 5); //佈局檢視的內邊距設定。
[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,並且設定裝置處於橫屏時,每排數量由3個變為6個,每頁的數量由9個變為18個。
MyFlowLayout *containerViewSC = [containerView fetchLayoutSizeClass:MySizeClass_Landscape copyFrom:MySizeClass_wAny | MySizeClass_hAny];
containerViewSC.arrangedCount = 6;
containerViewSC.pagedCount = 18;
複製程式碼
從上面的程式碼可以看出要實現分頁滾動的圖示列表的能力,主要是對充當容器檢視的流式佈局設定一些屬性即可,不需要為條目設定任何約束,而且還支援橫豎屏下每頁的不同數量的展示能力。整個功能程式碼量少,對比用UICollectionView來實現相同的功能要簡潔和容易得多。下面是程式執行的效果:
橫豎屏切換
對於帶有分頁功能的滾動檢視來說,當需要支援橫豎屏時就有可能會出現橫豎屏切換時介面停留在兩個頁面中間而不是按頁進行滾動的效果。其原因是無論是分頁滾動還是不分頁滾動,在滾動時都是通過調整滾動檢視的contentOffset來實現的。而當滾動檢視進行橫豎屏切換時不會調整對應的contentOffset值,這樣就導致了在螢幕方向切換時的滾動位置出現異常。解決的辦法就是在螢幕滾動時的相應回撥處理方法中修正這個contentOffset的值來解決這個問題。比如我們可以在螢幕切換的sizeclass變化的檢視控制器的協議方法中新增如下程式碼:
- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
UIScrollView *scrollView = (UIScrollView*)self.view;
//根據當前的contentOffset調整到正確的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);
}
複製程式碼