2014-04-01 49 views
13

我已經實現了包含兩個頁面的UIPageViewController。在最右側的頁面上,我可以向右滑動並將頁面拉回,這樣當我釋放時,它會反彈回來。當我向左滑動時,左側頁面會發生同樣的情況。 (彈跳就像當您到達Safari瀏覽器頁面底部時發生的情況)禁用UIPageViewController中的彈跳效果

有沒有辦法禁用反彈效果?謝謝!

+0

什麼類是'你UIPageViewController的view'?如果它是UIScrollView(或其子類),則可以在視圖上將「反彈」屬性設置爲「否」。 – Tim

+0

據我所知,UIPageViewController實現了一個UIScrollView,但類本身仍然是UIPageViewController。 –

+0

不是控制器本身 - 控制器的'view',它是UIViewController(UIPageViewController的超類)上的一個屬性。 – Tim

回答

16

迄今爲止,沒有一個答案其實完全工作。邊緣的情況下,他們都在失敗是這樣的:

  1. 滾動到第2頁。
  2. 用一根手指,向着第1頁拖動。
  3. 將第二根手指放在屏幕上並朝第1頁拖動。
  4. 擡起第一根手指。
  5. 重複操作,直至拖過頁0

在這種情況下,每一個迄今爲止我見過的解決方案去通過第0頁的邊界的核心問題是底層API被打破,開始報告相對於頁面0的內容偏移量,而不調用回調函數讓我們知道它正在顯示不同的頁面。在整個過程中,API仍然聲稱正在顯示第1頁,即使頁面0真正朝向頁面-1,頁面0也正在朝向頁面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對我們說謊,並且表現得像轉換正確完成,將偏移視爲相對於新頁面而不是舊頁面。

+0

工作得很好。 Thx – Praveen

+0

你能解釋蘋果如何在自己的應用程序中工作嗎?如果他們使用類似於你的黑客,他們應該意識到它是多麼的怪異。 – fruitcoder

+0

我的猜測是,他們不使用頁面控制器,或者他們有一些SPI,讓他們正確地禁用它。 – dgatwood

-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得到了downvoted。

+0

對不起,遲到的回覆,但實施這完全禁用滾動。有什麼想法嗎? –

+0

@ user2551625好吧,你真的需要使用PageViewController僅僅2頁嗎?無論如何,只有當用戶想要滑動第一頁和最後一頁時,才應禁用反彈。只需在** viewControllerBeforeViewController **和** viewControllerAfterViewController **數據源方法(pageIndex = 0和pageIndex = pages.count相應地)中檢查當前頁面索引 –

-1

我做到了。

如果要禁用UIPageViewController爲第1頁(左反彈)和最後一頁(反彈右側)的彈跳效果,這種想法是落實基本滾動視圖的委託:

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

爲了實現委託,您可以

  1. 環UIPageViewController.view的子視圖,並找到UIScrollView的設置其委託
  2. 子UIPageViewController

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的實施是爲了對付一個錯誤的情況時,從拳頭頁面從左到右快速揮筆用戶,第一頁將不會在左側的反彈(因到上面的函數),但會在(可能)vel造成的右側反彈刷新的時間。最後,當彈回時,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); 
     } 
    } 
} 
+3

在iOS 8上嘗試了這一點 - 它不起作用 –

+3

也嘗試在iOS它確實有效。 @Charpak你爲什麼不解釋你的問題,而不是寫一個全面的聲明。 – wuf810

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)組滾動視圖彈跳 - NO

4)設置滾動視圖的委託,並實現

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

您建議的方式不是解決方案。滾動上下動作有時不起作用。 – mkjwa

0

這裏是在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) 

     } 
    } 


    } 


} 
3

這裏一個簡單的解決方案

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

這工作對我來說,謝謝! –

+0

請爲我提供Objective C的解決方案。 –

相關問題