UIPageViewControlller详解

简介

UIPageViewController 可以实现内容页之间的导航功能,每一页的内容都由它自己的view controller来管理

主要的展示方式为以下七种

黄建武的_iPhone_实时桌面.png

下边以过渡方式来说明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.

默认doubleSidedNO,如果参数是UIPageViewControllerSpineLocationMid则必须是YES,不然就崩溃。
看效果:

  1. 书脊UIPageViewControllerSpineLocationMin参数doubleSidedNO
021.gif
  1. 书脊UIPageViewControllerSpineLocationMin参数doubleSidedYES
022.gif
  1. 书脊UIPageViewControllerSpineLocationMid
023.gif
  1. 书脊UIPageViewControllerSpineLocationMaX
024.gif

当书脊位置在左边时候,doubleSidedYES时候,下一个界面当做翻页时候过渡界面了

书脊位置(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 locationYES时候,是这么说的

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
025.gif
  • 间隔为20
026.gif

间隔参数在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

027.gif

要显示出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才能对应上,不然会错乱。

代码

稍后上传

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

推荐阅读更多精彩内容