简介
UIPageViewController
可以实现内容页之间的导航功能,每一页的内容都由它自己的view controller来管理
主要的展示方式为以下七种
下边以过渡方式来说明UIPageViewController的用法。
页面过渡有两种方式
typedef NS_ENUM(NSInteger, UIPageViewControllerTransitionStyle) {
UIPageViewControllerTransitionStylePageCurl = 0, // Navigate between views via a page curl transition.
UIPageViewControllerTransitionStyleScroll = 1 // Navigate between views by scrolling.
};
- UIPageViewControllerTransitionStyle.PageCurl(翻书效果)
- UIPageViewControllerTransitionStyle.Scroll(滚动效果)
PageCurl
重要概念
双面显示(doubleSided)
The default value for this property is NO.
If the back of pages has no content (the value is NO), then the content on the front of the page will partially show through to the back when turning pages.
If the spine is located in the middle, the value must be YES. Setting it to NO with the spine located in the middle raises an exception.
默认doubleSided
是NO
,如果参数是UIPageViewControllerSpineLocationMid
则必须是YES
,不然就崩溃。
看效果:
- 书脊
UIPageViewControllerSpineLocationMin
参数doubleSided
是NO
- 书脊
UIPageViewControllerSpineLocationMin
参数doubleSided
是YES
- 书脊
UIPageViewControllerSpineLocationMid
- 书脊
UIPageViewControllerSpineLocationMaX
当书脊位置在左边时候,doubleSided
为YES
时候,下一个界面当做翻页时候过渡界面了
书脊位置(spineLocation)
typedef NS_ENUM(NSInteger, UIPageViewControllerSpineLocation) {
UIPageViewControllerSpineLocationNone = 0, // Returned if 'spineLocation' is queried when 'transitionStyle' is not 'UIPageViewControllerTransitionStylePageCurl'.
UIPageViewControllerSpineLocationMin = 1, // Requires one view controller.
UIPageViewControllerSpineLocationMid = 2, // Requires two view controllers.
UIPageViewControllerSpineLocationMax = 3 // Requires one view controller.
}; // Only pertains to 'UIPageViewControllerTransitionStylePageCurl'.
上边表达的意思是书脊位置只在UIPageViewControllerTransitionStylePageCurl
方式有效,并且影响下边Select的参数viewControllers
的内容。参数是UIPageViewControllerSpineLocationMid
时候,要求有2个ViewController
,其他情况只能传一个。
- (void)setViewControllers:(nullable NSArray<UIViewController *> *)viewControllers direction:(UIPageViewControllerNavigationDirection)direction animated:(BOOL)animated completion:(void (^ __nullable)(BOOL finished))completion
Spline location | Double sided | whate to pass |
---|---|---|
.Mid(中间位置) | true | 传要显示的左边页面和右边页面 |
.Min .Max | true | 传将要显示页面的前页面和后页面,后页面用于动画 |
.Min or .Max | false | 传将要显示页面的前面页面 |
注意:
按照官方文档的表格,当书脊为Min/Max,而ine location
为YES
时候,是这么说的
Pass the front of the page to be displayed and the back of the previously-displayed page. The back is used for the page turning animation.
事实却是只需用传递一个参数就可以了,并且枚举UIPageViewControllerSpineLocation
.h解释也是只有Mid才会传2个参数。这块理解费劲,但左书脊带双边代码是一个controller而不是两个。
UIViewController *currentViewController = self.pageViewController.viewControllers[0];
NSArray *viewControllers = @[currentViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
self.pageViewController.doubleSided = self.doubleSided;
return self.location;
使用方法
初始化
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Configure the page view controller and add it as a child view controller.
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
DataViewController *startingViewController = [self.dataSource viewControllerAtIndex:5];
NSArray *viewControllers = @[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
self.pageViewController.dataSource = self.dataSource;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
}
注意Controller添加子Controller的方式,这是另外一个技术点。
实现Datasource
Datasource
封装置后通用,核心实现以下两个selector
即可。viewControllerBeforeViewController
范湖前一个ViewController
,viewControllerAfterViewController
返回后一个viewControllerAfterViewController
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(DataViewController *)viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(DataViewController *)viewController];
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [self.pageData count]) {
return nil;
}
return [self viewControllerAtIndex:index ];
}
实现Delegate
此selector
在- setViewControllers:direction:animated:completion:
调用之后执行,书脊
不在中间传递当前Controller
,在中间根据当前Controller
是奇数还是偶数,来传递的是前一个Controller
+当前Controller
,还是当前Controller
+后一个Controller
原理
参照横屏书脊
是Mid
可以看出一月
跟二月
是这本书显示出的当前界面,是左边一月Controller
+右边二月Controller
。
如果当前Controller
是一月
则是奇数,要传递当前一月
+下一个月二月
,如果是偶数二月
,则要传递前一个月一月
+当前月二月
。
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation {
// BOOL b = UIInterfaceOrientationIsPortrait(orientation);
// UIUserInterfaceIdiom idiom = [[UIDevice currentDevice] userInterfaceIdiom];
if (UIInterfaceOrientationIsPortrait(orientation) && self.location != UIPageViewControllerSpineLocationMid/*|| ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)*/) {
// In portrait orientation or on iPhone: Set the spine position to "min" and the page view controller's view controllers array to contain just one view controller. Setting the spine position to 'UIPageViewControllerSpineLocationMid' in landscape orientation sets the doubleSided property to YES, so set it to NO here.
UIViewController *currentViewController = self.pageViewController.viewControllers[0];
NSArray *viewControllers = @[currentViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
self.pageViewController.doubleSided = self.doubleSided;
return self.location;
}
// In landscape orientation: Set set the spine location to "mid" and the page view controller's view controllers array to contain two view controllers. If the current page is even, set it to contain the current and next view controllers; if it is odd, set the array to contain the previous and current view controllers.
DataViewController *currentViewController = self.pageViewController.viewControllers[0];
NSArray *viewControllers = nil;
NSUInteger indexOfCurrentViewController = [self.dataSource indexOfViewController:currentViewController];
if (indexOfCurrentViewController == 0 || indexOfCurrentViewController % 2 == 0) {
UIViewController *nextViewController = [self.dataSource pageViewController:self.pageViewController viewControllerAfterViewController:currentViewController];
viewControllers = @[currentViewController, nextViewController];
} else {
UIViewController *previousViewController = [self.dataSource pageViewController:self.pageViewController viewControllerBeforeViewController:currentViewController];
viewControllers = @[previousViewController, currentViewController];
}
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
self.pageViewController.doubleSided = YES;
return UIPageViewControllerSpineLocationMid;
}
Scroll
重要概念
滚动方向
滚动方向只在
Scroll
过渡方式有效,在PageCurl
方式指定成任何一个效果都是一样的。
typedef NS_ENUM(NSInteger, UIPageViewControllerNavigationDirection) {
UIPageViewControllerNavigationDirectionForward,
UIPageViewControllerNavigationDirectionReverse
}; // For 'UIPageViewControllerNavigationOrientationHorizontal', 'forward' is right-to-left, like pages in a book. For 'UIPageViewControllerNavigationOrientationVertical', bottom-to-top, like pages in a wall calendar.
InterPageSpacing
滚动Controller的间隔
- 间隔为0
- 间隔为20
间隔参数在UIPageViewController
初始化时候提供
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:UIPageViewControllerSpineLocationNone],UIPageViewControllerOptionSpineLocationKey,[NSNumber numberWithFloat:self.spacing],UIPageViewControllerOptionInterPageSpacingKey, nil];
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:dic];
PageControl
要显示出
PageControl
只要presentationCountForPageViewController
的返回值大于0则自动显示。
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{
if(_showPageControl)
{
return [self.pageData count];
}
return 0;
}
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
return _currentIndex;
}
presentationIndexForPageViewController:
具体要返回什么?
Return Value
The number of items to be reflected in the page indicator.
绝大部分Demo里都是直接返回0,官方文档解释也不太容易理解,其实就是返回当前的Index.这里的值取决于初始化时候传递的参数。
setViewControllers:direction:animated:completion:
会自动调用presentationCountForPageViewController:
以及presentationIndexForPageViewController
DataViewController *startingViewController = [self.dataSource viewControllerAtIndex:2];
NSArray *viewControllers = @[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
上边代码里传入Index
为2,则对应的presentationIndexForPageViewController:
返回2,pageControl
才能对应上,不然会错乱。
代码
稍后上传