美文网首页技术重塑iOSiOS第三方资料收集
[iOS]UIPageViewController使用--实战

[iOS]UIPageViewController使用--实战

作者: 流火绯瞳 | 来源:发表于2017-02-24 10:30 被阅读7527次

上篇文章[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 - 使用总结

(完)

相关文章

网友评论

  • 南游魂:楼主,在其中一个控制器中加上TableView,在实现左滑删除时,手势冲突咋解决啊?
  • 机器猫的百宝袋:pageVC包含A、B、C三个子控制器。滑动到C,然后缓慢连续滑动到A,使A页面完全贴合屏幕四周时松开手。此时pageVC代理方法didFinishAnimating方法不走。不知楼主遇到了没,有事如何解决的
    流火绯瞳:这种情况确实在缓慢滑动的时候会出现,可以尝试在开始的代理方法里做些事情
    - (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers

    一般在使用的时候,也不会有这么滑动的吧,如果非要纠结这一点,那就在上面的代理方法里想想办法吧:joy:
    没错就是豪哥灬:同样的问题 ,
  • 7hriller:这个pagevc的子vc数组是不是不会被复用,这样内存管理上会不会爆
    f0b5bd2b217f:我也担心这样的问题,请问你最后是怎么做的?我有上千条数据,所以我采用3个VC的数组复用,但是当前页的index一直处理不好。
  • Buger123:有个功能为滚动指定的页面,停止滚动,这个怎么做到
    Buger123:@流火绯瞳 嗯,我有需求,就是类似京东界面的,商品详情上面的时候可以滚动,下面的时候,不能禁止滚动
    流火绯瞳:@青石板的小巷123
    滚动多个页面?
  • 2e3aaeec9719:请问一下楼主除了这两种系统自带的翻页动画,是否还可以自定义翻页动画?
    流火绯瞳:@a1137746545 不是,你搜一下转场动画,不知道你要的是不是这种效果
    2e3aaeec9719:@流火绯瞳 是CATransition吗?请问有没有demo?
    流火绯瞳:@a1137746545 有自定义转场动画
  • 枫韵海:我这个有个问题,当翻页效果设置UIPageViewControllerTransitionStylePageCurl时,向后翻页会偶尔调用(nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController。如果在里面计算东西, 很容易会失败,不知道这个怎么解决
    流火绯瞳:@枫韵海 耗时的计算 还是什么?
  • 阿兹尔:楼主问下,单双也怎么切换
    流火绯瞳:@阿兹尔 双页显示的话, 数据源要初始化两个, 而且每次返回两个数据, 文章链接里有个双页显示的demo, 你可以看看
    阿兹尔:@流火绯瞳 UIPageViewControllerSpineLocationMin 单页显示

    UIPageViewControllerSpineLocationMid 双页显示 就是换这个代码? 但是运行就崩了!
    流火绯瞳:切换?什么意思单双页是在创建的时候设置的

本文标题:[iOS]UIPageViewController使用--实战

本文链接:https://www.haomeiwen.com/subject/ixjbwttx.html