上篇文章[iOS]UIPageViewController使用--API篇简单介绍了UIPageViewController的底层一些API, 今天就来介绍一下其使用;
我们知道, UIPageViewController有两种样式:
- 滑动: 左右, 上下滑动的效果
- 翻页: 类似翻书的效果
一. 滑动效果 UIPageViewControllerTransitionStyleScroll
下面我们来做这样一种效果:
可以左右滑动, 也可以选择上面的选项卡滚动到相应的页面;
上面的选项卡是使用一个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 *)pendingViewControllers {
UIViewController *nextVC = [pendingViewControllers firstObject];
NSInteger index = [self.dataSource indexOfObject:nextVC];
ld_currentIndex = index;
}
这里的pendingViewControllers里包含的就是即将显示的那个控制器, 是一个数组, 如果是单页显示的话, 其中只有一个元素;
接下来就是设置选项卡了, 在什么时候来设置呢? 当然是动画结束之后:
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)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 - 使用总结