web-dev-qa-db-ja.com

UIPageViewControllerでバウンス効果を無効にする

2ページを含むUIPageViewControllerを実装しました。右端のページでは、右にスワイプしてページを引き戻すことができるので、離すと跳ね返ります。左にスワイプすると、左のページでも同じことが起こります。 (バウンスは、サファリページの下部に到達したときに発生するようなものです)

バウンス効果を無効にする方法はありますか?ありがとう!

15
Drew K.

これまでのところ、どの答えも実際には完全には機能しません。それらがすべて失敗するエッジケースはこれです:

  1. 2ページまでスクロールします。
  2. 1本の指を使用して、1ページに向かってドラッグします。
  3. 画面に人差し指を置き、1ページに向かってドラッグします。
  4. 人差し指を持ち上げます。
  5. ページ0を超えてドラッグするまで繰り返します。

そのような状況では、これまでに見たすべてのソリューションがページ0の境界を超えています。主要な問題は、基盤となるAPIが壊れており、コールバックを呼び出さずにページ0に関連するコンテンツオフセットの報告を開始することです。別のページを表示しています。このプロセス全体を通じて、APIはページ1を表示していると主張しますが、実際にはページ0にあり、ページ-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が私たちに横たわっていると想定し、遷移のように動作しますオフセットを古いページではなく新しいページに関連するものとして扱い、適切に終了しました。

18
dgatwood

ここに簡単な解決策があります

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

また、トリックですが、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);
}
0
Pozhidaev S.

これがSwiftに実装された@SahilSソリューションです。

しかし、それは私にはバグがあるようです。

 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)

      }
    }


  }


}
0
mparrish91