到目前为止,没有一个答案真正完全有效。他们都失败的边缘情况是这样的:
- 滚动到第 2 页。
- 用一根手指向第 1 页拖动。
- 将第二根手指放在屏幕上并向第 1 页拖动。
- 抬起第一根手指。
- 重复直到拖过第 0 页。
在这种情况下,到目前为止我看到的每个解决方案都超出了第 0 页的范围。核心问题是底层 API 已损坏,并开始报告相对于第 0 页的内容偏移量,而没有调用我们的回调让我们知道它显示的是不同的页面。在整个过程中,API 仍然声称正在显示第 1 页,即使它实际上是在第 0 页,也可以向第 0 页显示。
这个设计缺陷的解决方法非常难看,但它是:
@property (weak,nonatomic) UIPageControl *pageControl;
@property (nonatomic,assign) BOOL shouldBounce;
@property (nonatomic,assign) CGFloat lastPosition;
@property (nonatomic,assign) NSUInteger currentIndex;
@property (nonatomic,assign) NSUInteger nextIndex;
- (void)viewDidLoad {
[super viewDidLoad];
...
self.shouldBounce = NO;
for (id testView in self.pageController.view.subviews) {
UIScrollView *scrollView = (UIScrollView *)testView;
if ([scrollView isKindOfClass:[UIScrollView class]]) {
scrollView.delegate = self;
// scrollView.bounces = self.shouldBounce;
}
}
}
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController{
return (NSInteger)self.currentIndex;
}
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers{
id controller = [pendingViewControllers firstObject];
self.nextIndex = [viewControllers indexOfObject:controller];
}
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
if(completed) {
// At this point, we can safely query the API to ensure
// that we are fully in sync, just in case.
self.currentIndex = [viewControllers indexOfObject:[pageViewController.viewControllers objectAtIndex:0]];
[self.pageControl setCurrentPage:self.currentIndex];
}
self.nextIndex = self.currentIndex;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
/* The iOS page view controller API is broken. It lies to us and tells us
that the currently presented view hasn't changed, but under the hood, it
starts giving the contentOffset relative to the next view. The only
way to detect this brain damage is to notice that the content offset is
discontinuous, and pretend that the page changed.
*/
if (self.nextIndex > self.currentIndex) {
/* Scrolling forwards */
if (scrollView.contentOffset.x < (self.lastPosition - (.9 * scrollView.bounds.size.width))) {
self.currentIndex = self.nextIndex;
[self.pageControl setCurrentPage:self.currentIndex];
}
} else {
/* Scrolling backwards */
if (scrollView.contentOffset.x > (self.lastPosition + (.9 * scrollView.bounds.size.width))) {
self.currentIndex = self.nextIndex;
[self.pageControl setCurrentPage:self.currentIndex];
}
}
/* Need to calculate max/min offset for *every* page, not just the first and last. */
CGFloat minXOffset = scrollView.bounds.size.width - (self.currentIndex * scrollView.bounds.size.width);
CGFloat maxXOffset = (([viewControllers count] - self.currentIndex) * scrollView.bounds.size.width);
NSLog(@"Page: %ld NextPage: %ld X: %lf MinOffset: %lf MaxOffset: %lf\n", (long)self.currentIndex, (long)self.nextIndex,
(double)scrollView.contentOffset.x,
(double)minXOffset, (double)maxXOffset);
if (!self.shouldBounce) {
CGRect scrollBounds = scrollView.bounds;
if (scrollView.contentOffset.x <= minXOffset) {
scrollView.contentOffset = CGPointMake(minXOffset, 0);
// scrollBounds.origin = CGPointMake(minXOffset, 0);
} else if (scrollView.contentOffset.x >= maxXOffset) {
scrollView.contentOffset = CGPointMake(maxXOffset, 0);
// scrollBounds.origin = CGPointMake(maxXOffset, 0);
}
[scrollView setBounds:scrollBounds];
}
self.lastPosition = scrollView.contentOffset.x;
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
/* Need to calculate max/min offset for *every* page, not just the first and last. */
CGFloat minXOffset = scrollView.bounds.size.width - (self.currentIndex * scrollView.bounds.size.width);
CGFloat maxXOffset = (([viewControllers count] - self.currentIndex) * scrollView.bounds.size.width);
if (!self.shouldBounce) {
if (scrollView.contentOffset.x <= minXOffset) {
*targetContentOffset = CGPointMake(minXOffset, 0);
} else if (scrollView.contentOffset.x >= maxXOffset) {
*targetContentOffset = CGPointMake(maxXOffset, 0);
}
}
}
基本上,它记录每个滚动事件的偏移量。如果滚动位置在与滚动方向相反的方向上移动了一个不可能的距离(我随意选择了屏幕宽度的 90%),则代码假定 iOS 对我们说谎,并且表现得好像过渡正确完成,将偏移量视为相对于新页面而不是旧页面。