[iOS]UIPageViewController使用--实战

上篇文章[iOS]UIPageViewController使用--API篇简单介绍了UIPageViewController的底层一些API, 今天就来介绍一下其使用;

我们知道, UIPageViewController有两种样式:

  • 滑动: 左右, 上下滑动的效果
  • 翻页: 类似翻书的效果

一. 滑动效果 UIPageViewControllerTransitionStyleScroll

下面我们来做这样一种效果:

滑动效果.gif

可以左右滑动, 也可以选择上面的选项卡滚动到相应的页面;
上面的选项卡是使用一个UICollectionView来实现的, 我简单的进行了封装, 这里不做过多的介绍, 今天主要说的是UIPageViewController一些设置;
首先, 初始化一个UIPageViewController

- (UIPageViewController *)pageViewController {
    if (_pageViewController == nil) {
        NSDictionary *option = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:10] forKey:UIPageViewControllerOptionInterPageSpacingKey];
        _pageViewController = [[UIPageViewController alloc]initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:option];
        _pageViewController.delegate = self;
        _pageViewController.dataSource = self;
        
        [self addChildViewController:_pageViewController];
        [self.view addSubview:_pageViewController.view];
    }
    
    return _pageViewController;
}

这里的主要设置是水平滑动样式, 然后设置页边距为10, 即option参数;
设置代理数据源, 添加到当前控制器上;
然后在合适的地方设置当前显示的控制器, 即调用setViewControllers:direction:animated:completion:方法; 因为我要验证数据源是否有值, 所以我放在了UIViewController的viewWillLayoutSubviews方法里进行设置:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    
    NSAssert(self.dataSource.count > 0, @"Must have one childViewCpntroller at least");
    NSAssert(self.segmentTitles.count == self.dataSource.count, @"The childViewController's count doesn't equal to the count of segmentTitles");
    
    UIViewController *vc = [self.dataSource objectAtIndex:self.selectedIndex];
    [self.pageViewController setViewControllers:@[vc] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
    
    self.segmentView.frame = CGRectMake(0, 0, self.view.frame.size.width, 30);
}

这里除了UIPageViewcontroller的设置, 还有其他的设置, self.selectedIndex是当前控制的一个属性, 用于设置当前选择的控制器以及self.segmentView当前选择的索引;
然后, 实现UIPageViewController的数据源方法:

- (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
    
    NSInteger index = [self.dataSource indexOfObject:viewController];
    
    if (index == 0 || (index == NSNotFound)) {
        
        return nil;
    }
    
    index--;
    
    return [self.dataSource objectAtIndex:index];
}

- (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
    
    NSInteger index = [self.dataSource indexOfObject:viewController];
    
    if (index == self.dataSource.count - 1 || (index == NSNotFound)) {
        
        return nil;
    }
    
    index++;
    
    return [self.dataSource objectAtIndex:index];
}

这两个方法的使用很相似, 都是根据当前的控制器, 获取当前控制器的索引, 然后修改索引(加1或者减1)来获取下一个控制器, 并返回;
下面一个问题,就是如何获取下一个控制器的索引, 在上面两个数据源方法里无法获取准确的索引, 而应该在下面这个方法里获取:

- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers {
    
    UIViewController *nextVC = [pendingViewControllers firstObject];
    
    NSInteger index = [self.dataSource indexOfObject:nextVC];
    
    ld_currentIndex = index;
}

这里的pendingViewControllers里包含的就是即将显示的那个控制器, 是一个数组, 如果是单页显示的话, 其中只有一个元素;
接下来就是设置选项卡了, 在什么时候来设置呢? 当然是动画结束之后:

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed {
    
    NSLog(@"didFinishAnimating");
    
    NSLog(@"%d", completed);
    if (completed) {
        
        self.segmentView.selectedIndex = ld_currentIndex ;
        
        NSLog(@">>>>>>>>> %ld", (long)ld_currentIndex);
    }  
}

这里我们需要使用completed来判断是否真的切换到下一个, 然后设置选项卡的选中项, 这些都是在我们滑动的时候修改状态的, 接下来, 就是在点击选项卡的某一项的时候, 将UIPageViewController滚动到相应的页面

- (void)segmentView:(LDSegmentView *)view didSelectedIndex:(NSInteger)index {
    
    UIViewController *vc = [self.dataSource objectAtIndex:index];
    
    if (index > ld_currentIndex) {
        
        [self.pageViewController setViewControllers:@[vc] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) {
            
        }];
    } else {
        
        [self.pageViewController setViewControllers:@[vc] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:^(BOOL finished) {
            
        }];
    }
    
    ld_currentIndex = index;
}

这个是我设置的LDSegmentView的代理方法, 判断当前选择的索引和记录的上一个索引的大小, 来设置是从前面还是从后面切换;

具体代码可查看demo:LDSegmentViewController, 简单封装了一下, 可以直接使用, 实现前面示意图中的效果, 如果有帮助还请star支持.

二. 翻页效果 UIPageViewControllerTransitionStylePageCurl

实现翻页效果和滑动效果的区别只是在初始化的时候, 其他的一些配置数据源以及代理使用很相似, 这里只简单给出初始化的示例代码, 其他的可以自己摸索尝试:

- (UIPageViewController *)pageViewController {
    if (_pageViewController == nil) {
        
        NSDictionary *option = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger: UIPageViewControllerSpineLocationMin] forKey:UIPageViewControllerOptionSpineLocationKey];
        _pageViewController = [[UIPageViewController alloc]initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation: UIPageViewControllerNavigationOrientationHorizontal options:option];
        
        _pageViewController.delegate = self;
        _pageViewController.dataSource = self;
        
        [self addChildViewController:_pageViewController];
        [self.view addSubview:_pageViewController.view];
        [_pageViewController didMoveToParentViewController:self];
    }
    
    return _pageViewController;
}

以上便是设置水平翻页, 且是单页显示的,
还有一个需要注意的地方是, 在初始化第一个视图的时候不要使用动画, 即调用下面的方法时, 如下设置:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    
    NSAssert(self.dataSource.count > 0, @"Must have one childController at least");
    UIViewController *vc = [self.dataSource objectAtIndex:0];
    
    NSMutableArray *vcs = [NSMutableArray arrayWithCapacity:2];
    [vcs addObject:vc];
    if (self.style == LDBookViewStyleDouble) {
        
        NSAssert(self.dataSource.count > 1, @"Must have two childControllers at least");
        UIViewController *second = [self.dataSource objectAtIndex:1];
        [vcs addObject:second];
    }
    
    [self.pageViewController setViewControllers:vcs direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {
        
    }];
}

上面有个判断, 如果是双面显示, 要初始化两个控制器;

还有一个需要注意的是, 在使用双面显示的时候, 需要处理画面的方向, 以及转屏的适配

效果如下:
单页翻页效果
双页翻页效果

其中双页的翻页效果使用的早些时间的一个demo演示的, 其中的代码没有特别整理, 添加了一些注释, 可以作为参考:LQQPageControllerDemo, 以及另一篇对应此demo的文章: iOS UIPageViewController - 使用总结

(完)

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

推荐阅读更多精彩内容