一、场景
最近在做一个包含阅读内容的项目,在电子书部分使用了 UIPageViewController 来满足仿真翻页与滑动翻页,在滑动翻页的过程遇到了各种奇葩的页号跳动问题。
也在百度上找了很多相关资料,都说它是一个 bug,缺陷。
大部分要么说放弃这个组件,使用 UIScraollView 来模拟。还有一个解决方案就是使用一段异步重赋值的代码:
__block XXViewController *blocksafeSelf = self;
[self.pageViewController setViewControllers:[NSArray arrayWithObject:[self viewControllerAtIndex:selectIndex]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) { //这里的YES是提供一个动画效果
if (finished) {
dispatch_async(dispatch_get_main_queue(), ^{
[blocksafeSelf.pageViewController setViewControllers:[NSArray arrayWithObject:[blocksafeSelf viewControllerAtIndex:selectIndex]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL]; //这是实际起到跳转作用的代码,animated:NO
});
}
}];
二、重新理解代理方法
UIPageViewController 有两个前一页与后一页的代理方法,在仿真翻页中可以正确工作。
- (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController;
- (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController;
我以前理解这两个方法就当前页的上一页与下一页,这种在仿真翻页中是没有问题的,但在滑动模式中,这种理解偏差大了。
正确的理解是,上一页下一页是相对于代理方法传入的那个视图控制器而言的,不能直接使用当前的章节与页数来计算。
三、解决方案
1、在指定当前页面时,不要使用动画模式。
[_pageViewController setViewControllers:[self makeCurrentReadViewControllers]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
如果使用动画模式,会导致第一次向左滑动的时候,重复显示第一页。
2、在上一页与下一页的代理方法中,使用相对计算章节与页数
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
_pageChange = _page;
_chapterChange = _chapter;
if (_isScrollModel) {
// 非常重要,否则在滚动模式下拿不到正确的页号,因为它并不是指当前页的上一页
LSYReadViewController *pageVC = (LSYReadViewController *) viewController;
_pageChange = pageVC.recordModel.page;
_chapterChange = pageVC.recordModel.chapter;
}
// 第一章第一页没有前一页了
if (_chapterChange == 0 &&_pageChange == 0) {
// 返回 nil 只会出现背景,没有新的vc
NSLog(@"已经是第一页了!!!!!");
return nil;
}
// 第一页回去就到了前一章
if (_pageChange == 0) {
_chapterChange--;
LSYChapterModel *chapterModel = _model.chapters[_chapterChange]; // 前一章的model
chapterModel.isReady = YES;
_pageChange = chapterModel.pageCount - 1;// 页号为前一章的最大页数
} else{
// 否则正常的减一页
_pageChange--;
}
return [self readViewWithChapter:_chapterChange page:_pageChange];
}
// 后一页
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
// 当前章节与页号
_pageChange = _page;
_chapterChange = _chapter;
if (_isScrollModel) {
// 非常重要,否则在滚动模式下拿不到正确的页号,因为它并不是指当前页的下一页
LSYReadViewController *pageVC = (LSYReadViewController *) viewController;
_pageChange = pageVC.recordModel.page;
_chapterChange = pageVC.recordModel.chapter;
}
// 如果到了最后一章的最后一页,返回nil
if (_chapterChange == _model.chapters.count - 1 &&
_pageChange == _model.chapters.lastObject.pageCount - 1
) {
return nil;
}
// 下一章
LSYChapterModel *chapterModel = _model.chapters[_chapterChange];
chapterModel.isReady = YES;
if (_pageChange == chapterModel.pageCount - 1) {
_chapterChange += 1;
_pageChange = 0;
} else {
// 本章下一节
_pageChange += 1;
}
return [self readViewWithChapter:_chapterChange page:_pageChange];
}
四、手动翻页
完成点击左右页面自动滑动翻页
// 动画向前翻页
- (void)moveToPrevPage
{
UIViewController *vc = [self pageViewController:self.pageViewController
viewControllerBeforeViewController:self.pageViewController.viewControllers.firstObject];
if (vc == nil) {
return;
}
__weak typeof(self) weakself = self;
[_pageViewController setViewControllers:@[vc]
direction:UIPageViewControllerNavigationDirectionReverse
animated:YES // 滑动
completion:^(BOOL finished) {
if (finished) {
// 完成后要重置一下,避免有 bug
dispatch_async(dispatch_get_main_queue(), ^{
[weakself.pageViewController
setViewControllers:@[vc]
direction:UIPageViewControllerNavigationDirectionReverse
animated:NO
completion:NULL];
});
}
}];
}
五、总结
这个组件其实是没有 bug 的,按苹果严谨的风格来说,这个类似问题出现在 iOS6的时候就有人提出来了,如果是问题,早就修复了。