2014-04-01 11 views
13

Zaimplementowałem UIPageViewController zawierający dwie strony. Po prawej stronie jestem w stanie przesunąć w prawo i odciągnąć stronę tak, że po jej zwolnieniu cofa się. To samo dzieje się na lewej stronie, gdy przesuwaj palcem w lewo. (Odbicie jest podobne do tego, co dzieje się po dotarciu na dół strony safari).Wyłącz efekt odbicia w UIPageViewController

Czy istnieje sposób na wyłączenie efektu odbicia? Dzięki!

+0

Co klasa to 'view' Twojego UIPageViewController? Jeśli jest to UIScrollView (lub jego podklasa), możesz ustawić właściwość 'bounce' na' NO' w widoku. – Tim

+0

Z tego co rozumiem, UIPageViewController implementuje UIScrollView, ale sama klasa nadal jest UIPageViewController. –

+0

Nie sam kontroler - "widok" kontrolera, który jest właściwością w UIViewController, nadklasie UIPageViewController. – Tim

Odpowiedz

16

Jak dotąd żadna z odpowiedzi faktycznie działa w pełni. Przypadek kant, że wszystkie one nie są w ten sposób:

  1. Przejdź do strony 2.
  2. jednym palcem, opór w kierunku strony 1.
  3. Połóż drugi palec na ekranie i przeciągnij w kierunku strony 1.
  4. Podnieś pierwszy palec.
  5. Powtórz dopóki nie przeciągać przeszłość strona 0.

W takiej sytuacji każde rozwiązanie widziałem tak daleko mija granice stronie 0. Podstawowym problemem jest to, że podstawowa API jest złamane, a rozpoczyna raportowanie przesunięcia treści względem strony 0 bez wywoływania naszego wywołania zwrotnego, aby poinformować nas, że wyświetla inną stronę. W całym tym procesie API wciąż twierdzi, że pokazuje stronę 1, zbliżając się do strony zerowej, nawet jeśli naprawdę znajduje się na stronie zero w kierunku strony -1.

Rozwiązaniem dla tego projektu wada jest wyjątkowo brzydki, ale tutaj jest:

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

Zasadniczo, to rejestruje przesunięcie dla każdego zdarzenia przewijania. Jeśli pozycja przewijania przesunęła odległość, która jest niemożliwa (arbitralnie wybrałem 90% szerokości ekranu) w kierunku przeciwnym do kierunku przewijania, kod zakłada, że ​​iOS nas okłamuje i zachowuje się tak, jakby zmiana zakończone poprawnie, traktując przesunięcia jako względne względem nowej strony zamiast starej.

+0

Działa bardzo dobrze. Thx – Praveen

+0

Czy możesz wyjaśnić, w jaki sposób Apple działa we własnych aplikacjach? Jeśli zastosują hack podobny do twojego, powinni zdać sobie sprawę z tego, jak bardzo jest to bzdurne. – fruitcoder

+0

Domyślam się, że albo nie używają kontrolera strony, albo mają jakiś SPI, który pozwala im go odpowiednio wyłączyć. – dgatwood

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

To trochę hacky chociaż - to pewnie dlatego the original answer here ale downvoted.

+0

Przepraszamy za spóźnioną odpowiedź, ale implementacja tego właśnie całkowicie uniemożliwiła przewijanie. jakieś pomysły? –

+0

@ user2551625 cóż, czy naprawdę potrzebujesz używać PageViewController tylko na 2 strony? W każdym razie należy wyłączyć odrzucanie tylko wtedy, gdy użytkownik chce przesunąć pierwszą i ostatnią stronę. Po prostu sprawdź bieżący indeks strony w ** viewControllerBeforeViewController ** i ** viewControllerAfterViewController ** metodach źródeł danych (pageIndex = 0 i pageIndex = pages.count odpowiednio) –

-1

Zrobiłem to.

Jeśli chcesz wyłączyć efekt odbijania UIPageViewController na 1 stronie (odrzuceń po lewej) i ostatnią stronę (bounce po prawej), idea jest wdrożenie leżących u podstaw Scrollview za Delegat:

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

celu realizacji delegata można

  1. pętla subviews z UIPageViewController.view i znaleźć UIScrollView aby ustawić jego delegata
  2. podklasa UIPageViewController

Implementacja scrollViewDidScroll jest zresetować contentOffset do pochodzenia (NIE (0,0), ale (bound.size.width, 0)), gdy użytkownik jest dotarcie z granicami, tak:

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

i wdrożenie do scrollViewWillEndDragging ma do czynienia ze scenariuszem błędów, gdy użytkownik szybko przejeżdża od lewej do prawej strony na pięści, pierwsza strona nie będzie odbijać w lewo (z powodu do powyższej funkcji), ale odbija się po prawej stronie (może) vel Okropność przesunięcia. I w końcu, gdy zostanie odesłany, UIPageViewController wywoła zrzut strony na drugą stronę (co jest, z przyczyny, nieoczekiwane).

- (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); 
     } 
    } 
} 
+3

wypróbował to na iOS 8 - i to nie działa –

+3

Próbowano również na iOS i to działa. @Charpak, dlaczego nie wyjaśnisz swojego problemu, zamiast pisać ogólne oświadczenie. – wuf810

0

Również sztuczka, ale myślę, że lepiej wtedy do czynienia z pageViewController.view.subviews tablicy

1) umieścić UIPageViewController na UIScrollView

2) szerokość zawartość musi być większy niż Scrollview szerokość, na przykład, przez 10.0f

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

3) zestaw przewijania widok odbicia - NR

4) Zestaw Scrollview delegat i wdrożyć

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

sposób, w jaki sugerujesz, nie jest rozwiązaniem. Przewijanie w górę iw dół czasami nie działa. – mkjwa

0

Oto rozwiązanie @SahilS wdrożone w Swift.

To jednak wydaje się być dla mnie problemem.

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) 

     } 
    } 


    } 


} 
3

Oto proste rozwiązanie

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

to działało dla mnie, dzięki! –

+0

Proszę podać mi to rozwiązanie dla Celu C. –

Powiązane problemy