Подтвердить что ты не робот

Отключить эффект отказов в UIPageViewController

Я реализовал UIPageViewController, который содержит две страницы. Справа на самой странице, я могу провести пальцем вправо и вытащить страницу назад, чтобы при отпускании она отскакивала назад. То же самое происходит на левой странице, когда я сажусь влево. (Подпрыгивание похоже на то, что происходит, когда вы достигаете нижней части страницы сафари)

Есть ли способ отключить эффект отскока? Спасибо!

4b9b3361

Ответ 1

До сих пор ни один из ответов не работал полностью. Кромка, в которой все они не работают, такова:

  • Перейдите к странице 2.
  • Используя один палец, перетащите его на страницу 1.
  • Поместите второй палец на экран и перетащите его на страницу 1.
  • Поднимите первый палец.
  • Повторите, пока вы не перетащили предыдущую страницу.

В этой ситуации каждое решение, которое я видел до сих пор, проходит через границы страницы 0. Основная проблема заключается в том, что базовый API нарушен и начинает сообщать о смещении контента относительно страницы 0, не вызывая наш обратный вызов, чтобы позволить мы знаем, что он показывает другую страницу. На протяжении всего этого процесса API по-прежнему утверждает, что показывает страницу 1, идя на нуль страницы, даже когда она действительно находится на странице, равной направлению страницы -1.

Обходной путь для этого недостатка дизайна удивительно уродлив, но вот он:

@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 лежит нам, и ведет себя так, как будто переход правильно обработаны, обрабатывая смещения как относительно новой страницы, а не старой.

Ответ 2

Здесь простое решение

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

Ответ 3

Также трюк, но я думаю, что это лучше, чем обращение к массиву 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) установите отклонение вида прокрутки - NO

4) установите делегат scrollview и выполните

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

Ответ 4

Вот решение @SahilS, реализованное в Swift.

Однако мне кажется, что это плохо.

 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)

      }
    }


  }


}

Ответ 5

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

Это своего рода взломанный, хотя, возможно, почему оригинальный ответ здесь получил downvoted.

Ответ 6

Я сделал это.

Если вы хотите отключить эффект отскока UIPageViewController для первой страницы (отскок слева) и последнюю страницу (отскок справа), идея заключается в реализации основного делегата scrollView:

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

Чтобы реализовать делегат, вы можете

  • зациклируйте подпрограммы UIPageViewController.view и найдите UIScrollView для установки своего делегата
  • подкласс UIPageViewController

Реализация для scrollViewDidScroll заключается в reset 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);
        }
    }
}