禁用UIPageViewController中的跳出效果

时间:2014-04-01 20:09:08

标签: ios iphone uipageviewcontroller bounce

我已经实现了包含两个页面的UIPageViewController。在最右边的页面上,我可以向右滑动,然后向后拉页面,这样当我释放时,它会反弹回来。当我向左滑动时左侧页面出现同样的情况。 (弹跳就像到达野生动物园页面底部时发生的情况)

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

6 个答案:

答案 0 :(得分:17)

到目前为止,没有一个答案真正完全有效。他们都失败的边缘情况是:

  1. 滚动到第2页。
  2. 用一根手指向第1页拖动。
  3. 将第二根手指放在屏幕上并向第1页拖动。
  4. 抬起第一根手指。
  5. 重复操作,直至拖过第0页。
  6. 在这种情况下,我到目前为止看到的每个解决方案都超出了第0页的界限。核心问题是底层API被破坏,并开始报告相对于第0页的内容偏移量而不调用我们的回调让我们知道它正在展示一个不同的页面。在整个过程中,API仍然声称显示第1页,即使在第0页真正位于第-1页时也会显示第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对我们说谎,并且表现得好像过渡正确完成,将偏移视为相对于新页面而不是旧页面。

答案 1 :(得分:4)

这是一个简单的解决方案

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
    }
}

答案 2 :(得分:0)

也是诀窍,但我认为最好处理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);
}

答案 3 :(得分:0)

这是在Swift中实现的@SahilS解决方案。

然而,对我来说似乎有些错误。

npm -v

答案 4 :(得分:-1)

for (UIView *view in self.pageViewController.view.subviews ) {
    if ([view isKindOfClass:[UIScrollView class]]) {
        UIScrollView *scroll = (UIScrollView *)view;
        scroll.bounces = NO;
    }
}

虽然有点骇人听闻 - 这可能是the original answer here投降的原因。

答案 5 :(得分:-1)

我做到了。

如果要禁用第一页(左侧弹跳)和最后一页(右侧弹跳)的UIPageViewController的弹跳效果,想法是实现底层的scrollView代理:

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

要实现委托,您可以

  1. 循环UIPageViewController.view的子视图,找到UIScrollView设置其委托
  2. 子类UIPageViewController
  3. 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);
            }
        }
    }