web-dev-qa-db-fra.com

Désactiver l'effet de rebond dans UIPageViewController

J'ai implémenté une UIPageViewController qui contient deux pages. Sur la page la plus à droite, je peux glisser vers la droite et tirer la page en arrière de sorte que lorsque je le relâche, il rebondit. La même chose se produit sur la page de gauche lorsque je glisse vers la gauche. (Le rebond ressemble à ce qui se passe lorsque vous atteignez le bas d'une page de safari)

Existe-t-il un moyen de désactiver l'effet de rebond? Merci!

14
Drew K.

Jusqu'ici, aucune des réponses ne fonctionne réellement. L'affaire Edge sur laquelle ils échouent tous est la suivante:

  1. Passez à la page 2.
  2. À l’aide d’un doigt, faites glisser vers la page 1.
  3. Placez un deuxième doigt sur l’écran et faites-le glisser vers la page 1.
  4. Soulevez le premier doigt.
  5. Répétez cette opération jusqu'à ce que vous ayez fait glisser la page 0.

Dans cette situation, chaque solution que j'ai vue jusqu'ici dépasse les limites de la page 0. Le problème essentiel est que l'API sous-jacente est endommagée et commence à signaler un décalage de contenu relatif à la page 0 sans appeler notre rappel pour nous informer que il montre une page différente. Tout au long de ce processus, l’API prétend toujours afficher la page 1, en allant vers la page zéro alors même qu’elle est vraiment à la page zéro en allant vers la page -1.

La solution de contournement à ce défaut de conception est remarquablement laide, mais la voici:

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

Fondamentalement, il enregistre le décalage pour chaque événement de défilement. Si la position du défilement a été déplacée d’une distance impossible (j’ai choisi arbitrairement 90% de la largeur de l’écran) dans le sens opposé à celui du défilement, le code suppose que iOS nous ment, et se comporte comme si la transition terminé correctement, en traitant les décalages comme étant relatifs à la nouvelle page au lieu de l’ancienne.

17
dgatwood

Voici une solution simple

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

Aussi l'astuce, mais je pense qu'il vaut mieux alors traiter avec le tableau pageViewController.view.subviews

1) placez votre UIPageViewController sur UIScrollView

2) la largeur du contenu doit être plus grande que la largeur de scrollview, par exemple de 10.0f

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

3) définir le rebond de la vue de défilement - NON

4) définir le délégué scrollview et mettre en œuvre

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

Voici la solution @SahilS mise en œuvre dans Swift. 

Cela semble cependant être un buggy pour moi.

 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