技术重塑iOSiOS第三方资料收集

[iOS]UIPageViewController使用--实战

2017-02-24  本文已影响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 - 使用总结

(完)

上一篇下一篇

猜你喜欢

热点阅读