在UIPageViewController中禁用反弹效果

我已经实现了一个包含两个页面的UIPageViewController 。 在最右侧的页面上,我可以向右滑动,并将页面拉回,这样当我释放时,它就会弹回来。 当我向左滑动时,左侧页面也会发生同样的情况。 (弹跳就像当你到达Safari浏览器页面底部时发生的事情)

有没有办法来禁用反弹效果? 谢谢!

到目前为止,没有任何答案实际上完全工作。 他们都失败的边缘情况是这样的:

  1. 滚动到第2页。
  2. 用一根手指向第1页拖动。
  3. 将另一个手指放在屏幕上并向第1页拖动。
  4. 抬起第一根手指。
  5. 重复,直到你拖过页面0。

在这种情况下,我所见过的每个解决scheme都已经超越了页面0的界限。核心问题是底层API被打破,并开始报告相对于页面0的内容偏移量,而不调用callback函数让我们知道它显示了一个不同的页面。 在整个过程中,API仍然声称正在显示页面1,即使页面0真正朝向页面-1,页面0仍然朝向页面0。

这个devise缺陷的解决方法是非常难看的,但这里是:

 @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); } } } 

基本上,它logging每个滚动事件的偏移量。 如果滚动位置移动了一个不可能的距离(我任意select了屏幕宽度的90%),则代码假定iOS正在对我们说谎,并且performance得像转换正确完成,将偏移量视为相对于新页面而不是旧页面。

这里有一个简单的解决

 fileprivate var currentIndex = 0 fileprivate var lastPosition: CGFloat = 0 override func viewDidLoad() { super.viewDidLoad() for view in view.subviews { if view is UIScrollView { (view as! UIScrollView).delegate = self break } } } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if completed { // Get current index let pageContentViewController = pageViewController.viewControllers![0] currentIndex = orderedViewControllers.index(of: pageContentViewController)! } } func scrollViewDidScroll(_ scrollView: UIScrollView) { self.lastPosition = scrollView.contentOffset.x if (currentIndex == orderedViewControllers.count - 1) && (lastPosition > scrollView.frame.width) { scrollView.contentOffset.x = scrollView.frame.width return } else if currentIndex == 0 && lastPosition < scrollView.frame.width { scrollView.contentOffset.x = scrollView.frame.width return } } 

也是诀窍,但我认为这是更好的,然后处理pageViewController.view.subviews数组

1)把你的UIPageViewController放在UIScrollView上

2)内容宽度必须大于滚动视图宽度,例如10.0f

 self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width + 10.0f, self.scrollView.frame.size.height); 

3)设置滚动视图反弹 – 没有

4)设置scrollview委托,并执行

 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { scrollView.contentOffset = CGPointMake(0.0, 0.0); } 

这里是在Swift中实现的@SahilS解决scheme。

但它似乎对我来说是越野车。

  override func viewDidLoad() { super.viewDidLoad() for view in view.subviews { if view is UIScrollView { (view as! UIScrollView).delegate = self break } } extension PageViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let viewControllerIndex = orderedViewControllers?.index(of: viewController) else { return nil } let previousIndex = viewControllerIndex - 1 guard previousIndex >= 0 else { return nil } guard (orderedViewControllers?.count)! > previousIndex else { return nil } print("in viewControllerBefore") return orderedViewControllers?[previousIndex] } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let viewControllerIndex = orderedViewControllers?.index(of: viewController) else { return nil } let nextIndex = viewControllerIndex + 1 let orderedViewControllersCount = orderedViewControllers?.count guard orderedViewControllersCount != nextIndex else { return nil } print("in viewControllerAfter") return orderedViewControllers?[nextIndex] } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if completed { // Get current index let pageContentViewController = pageViewController.viewControllers![0] currentIndex = (orderedViewControllers?.index(of: pageContentViewController))! } self.nextIndex = self.currentIndex } func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { print("willTransitionTo") let controller = pendingViewControllers.first if let i = viewControllers?.index(of: controller!) { print("Jason is at index \(i)") self.currentIndex = i } else { print("Jason isn't in the array") } } func presentationIndex(for pageViewController: UIPageViewController) -> Int { return self.currentIndex } } extension PageViewController: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { /* 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. */ let poop = self.lastPosition + (0.9 * scrollView.bounds.size.width) print("poop is \(poop)") if (self.nextIndex > self.currentIndex) { /* Scrolling forwards */ if (scrollView.contentOffset.x < (self.lastPosition - (0.9 * scrollView.bounds.size.width))) { self.currentIndex = self.nextIndex; } } else { /* Scrolling backwards */ if (scrollView.contentOffset.x > (self.lastPosition + (0.9 * scrollView.bounds.size.width))) { self.currentIndex = self.nextIndex; } } /* Need to calculate max/min offset for *every* page, not just the first and last. */ let minXOffset = scrollView.bounds.size.width - (CGFloat(self.currentIndex) * scrollView.bounds.size.width); let maxXOffset = (CGFloat(((viewControllers?.count)! - self.currentIndex)) * scrollView.bounds.size.width) if (!self.shouldBounce) { let scrollBounds = scrollView.bounds; if (scrollView.contentOffset.x <= minXOffset) { scrollView.contentOffset = CGPoint(x: minXOffset, y: 0) } else if (scrollView.contentOffset.x >= maxXOffset) { scrollView.contentOffset = CGPoint(x: maxXOffset, y: 0) } scrollView.bounds = scrollBounds } self.lastPosition = scrollView.contentOffset.x } func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { var scrollOffset = targetContentOffset.pointee let minXOffset = scrollView.bounds.size.width - (CGFloat(self.currentIndex) * scrollView.bounds.size.width); let maxXOffset = (CGFloat(((viewControllers?.count)! - self.currentIndex)) * scrollView.bounds.size.width) if (!self.shouldBounce) { if (scrollView.contentOffset.x <= minXOffset) { scrollOffset = CGPoint(x: minXOffset, y: 0) } else if (scrollView.contentOffset.x >= maxXOffset) { scrollOffset = CGPoint(x: maxXOffset, y: 0) } } } } 
 for (UIView *view in self.pageViewController.view.subviews ) { if ([view isKindOfClass:[UIScrollView class]]) { UIScrollView *scroll = (UIScrollView *)view; scroll.bounces = NO; } } 

虽然这有点不好意思 – 这也许就是为什么这里原来的答案被压低了。

我做到了。

如果你想禁用UIPageViewController的第一页(反弹左侧)和最后一页(反弹右侧)的弹跳效果,这个想法是实现底层的scrollView的委托:

 - (void)scrollViewDidScroll:(UIScrollView *)scrollView - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset 

要实现委托,你可以

  1. 循环UIPageViewController.view的子视图,并findUIScrollView来设置它的委托
  2. 子类UIPageViewController

scrollViewDidScroll的实现是在用户超出边界时将contentOffset重置为原点( NOT(0,0),但是(bound.size.width,0) ),如下所示:

 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (NO == isPageToBounce) { if (_currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) { scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0); } if (_currentPage == [listVCs count]-1 && scrollView.contentOffset.x > scrollView.bounds.size.width) { scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0); } } // more } 

scrollViewWillEndDragging的实现是为了处理一个错误的场景,当用户在第一页从左向右快速滑动时,第一页不会在左边反弹(由于上面的函数),但是会在右边反弹由(可能)速度的滑动造成的。 最后,当弹回时,UIPageViewController会触发页面翻转到第二页(这是原因,不是预期的)。

 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { if (NO == isPageToBounce) { if (_currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) { velocity = CGPointZero; *targetContentOffset = CGPointMake(scrollView.bounds.size.width, 0); } if (_currentPage == [listVCs count]-1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) { velocity = CGPointZero; *targetContentOffset = CGPointMake(scrollView.bounds.size.width, 0); } } }