禁用UIPageViewController反弹

时间:2014-02-15 13:30:37

标签: ios iphone objective-c uipageviewcontroller

搜索了很多这个,但还没找到合适的解决方案。

是否可以停用UIPageViewController的退回效果并仍然使用UIPageViewControllerTransitionStyleScroll

14 个答案:

答案 0 :(得分:67)

禁用UIPageViewController的反弹

  1. <UIScrollViewDelegate>委托添加到您的UIPageViewController标题

  2. 将UIPageViewController的基础UIScrollView的委托设置为viewDidLoad中的父级:

    for (UIView *view in self.view.subviews) {
        if ([view isKindOfClass:[UIScrollView class]]) {
            ((UIScrollView *)view).delegate = self;
            break;
        }
    }
    
  3. scrollViewDidScroll 的实现是将contentOffset重置为原点( NOT(0,0),但是(bound.size.width,0)当用户超出界限时,如下所示:

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        if (_currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) {
            scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0);
        } else if (_currentPage == totalViewControllersInPageController-1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
            scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0);
        }
    }
    
  4. 最后, scrollViewWillEndDragging 的实现是当用户在第一页从左向右快速滑动时处理错误情况,第一页不会在左侧反弹(由于上面的功能),但是会在滑动的(可能)速度引起的右侧反弹。最后反弹时,UIPageViewController会触发一个页面翻转到第二页(当然,这不是预期的)。

    - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
        if (_currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) {
            *targetContentOffset = CGPointMake(scrollView.bounds.size.width, 0);
        } else if (_currentPage == totalViewControllersInPageController-1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
            *targetContentOffset = CGPointMake(scrollView.bounds.size.width, 0);
        }
    }
    
  5. Swift 4.0

    要放入viewDidLoad的代码:

    for subview in self.view.subviews {
        if let scrollView = subview as? UIScrollView {
            scrollView.delegate = self
            break;
        }
    }
    

    scrollViewDidScroll 的实施:

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if (currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) {
            scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0);
        } else if (currentPage == totalViewControllersInPageController - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
            scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0);
        }
    }
    

    scrollViewWillEndDragging 的实施:

    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        if (currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) {
            targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0);
        } else if (currentPage == totalViewControllersInPageController - 1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
            targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0);
        }
    }
    

答案 1 :(得分:13)

禁用UIPageViewController的反弹

Swift 2.2

Addition to answers

1)将UIScrollViewDelegate添加到UIPageViewController

extension PageViewController: UIScrollViewDelegate

2)添加到viewDidLoad

for view in self.view.subviews {
   if let scrollView = view as? UIScrollView {
      scrollView.delegate = self
   }
}

3)添加UIScrollViewDelegate方法

func scrollViewDidScroll(scrollView: UIScrollView) {
    if currentIndex == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width {
        scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
    } else if currentIndex == totalViewControllers - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width {
        scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
    }
}

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    if currentIndex == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width {
        scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
    } else if currentIndex == totalViewControllers - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width {
        scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
    }
}

答案 2 :(得分:2)

我不确定如何正确管理currentIndex但最终做了

extension Main: UIPageViewControllerDelegate {
    func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed {
            guard let viewController = pageViewController.viewControllers?.first,
                index = viewControllerDatasource.indexOf(viewController) else {
                fatalError("Can't prevent bounce if there's not an index")
            }
            currentIndex = index
        }
    }
}

答案 3 :(得分:1)

UIPageViewController实际上并没有为你做多少事情。您可以非常轻松地使用带有视图控制器的UIScrollView,并禁用它的反弹。

做一些像

这样的事情
int x=0;
for (NSString *storyboardID in storyboardIDs){
        UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:storyboardID];
        [self addChildViewController:vc];
        vc.view.frame = CGRectMake(x++*vc.view.frame.size.width, 0, vc.view.frame.size.width, vc.view.frame.size.height);
        [self.scrollView addSubview:vc.view];
        [vc didMoveToParentViewController:self];
        self.scrollView.contentSize = CGSizeMake(storyboardIDs.count*vc.view.frame.size.width, vc.view.frame.size.height);
}

答案 4 :(得分:1)

我在 Swift 5
中的解决方案 在我的场景中,我首先在第二页上加载UIPageViewController。我总共有三页,所以我在中间一页打开。

这是我的UIPageViewController

的代码
import UIKit

class MainPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIScrollViewDelegate {

  let idList = ["OverviewController", "ImportantItemsController", "ListMenuController"] // A list of all of my viewControllers' storyboard id
  var currentPage = 1 // Tracking the current page

  override func viewDidLoad() {
    super.viewDidLoad()
    setupPageController()

    for subview in self.view.subviews { // Getting the scrollView
      if let scrollView = subview as? UIScrollView {
        scrollView.delegate = self
        break;
      }
    }
  }

  // UIPageViewControllerDataSource
  func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
    let index = idList.firstIndex(of: viewController.restorationIdentifier!)!
    if (index > 0) {
      return storyboard?.instantiateViewController(withIdentifier: idList[index - 1])
    }
    return nil
  }

  func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
    let index = idList.firstIndex(of: viewController.restorationIdentifier!)!
    if (index < idList.count - 1) {
      return storyboard?.instantiateViewController(withIdentifier: idList[index + 1])
    }
    return nil
  }

  func presentationCount(for pageViewController: UIPageViewController) -> Int {
    return idList.count
  }

  // UIPageViewControllerDelegate
  func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    if completed {
      guard let vc = pageViewController.viewControllers?.first else { return }
      switch vc {
      case is ImportantItemsController:
          currentPage = 1
      case is OverviewController:
          currentPage = 0
      default:
          currentPage = 2
      }
    }
  }

  // ScrollViewDelegate
  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let totalViewControllersInPageController = idList.count
    if (currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) {
      scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0);
    } else if (currentPage == totalViewControllersInPageController - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
      scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0);
    }
  }

  func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let totalViewControllersInPageController = idList.count
    if (currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) {
      targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0);
    } else if (currentPage == totalViewControllersInPageController - 1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
      targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0);
    }
  }

  fileprivate func setupPageController() {
    let controller = storyboard?.instantiateViewController(withIdentifier: idList[1]) as! ImportantItemsController // Loading on the second viewController
    setViewControllers([controller], direction: .forward, animated: true, completion: nil)
    dataSource = self
    delegate = self
  }
}

答案 5 :(得分:1)

如果您跟踪currentIndex,那么下面的内容就足够了,但是有点bug,因为在随机情况下它会完全停止滚动。

我认为scrollView.bounces有点bug,也许我错过了一些东西,因为在大多数情况下它都能正常工作,如果任何人都可以根据以下内容提供解决方案,那就太好了。

public func scrollViewDidScroll(_ scrollView: UIScrollView) {
    scrollView.bounces = currentIndex == 0 ||
        currentIndex == controllers.count - 1
        ? false 
        : true
}

答案 6 :(得分:0)

如果您尝试停用UIPageViewController.scrollView的退回功能,您肯定会受到打扰pageViewController:轻扫一下即可开始工作。所以,不要这样做:

self.theScrollView.alwaysBounceHorizontal = NO;
self.theScrollView.bounces = NO;

仅在scrollView子视图中搜索UIPageViewController引用时使用此解决方案才能完全禁用滚动:

@interface MyPageViewController : UIPageViewController
@property (nonatomic, assign) BOOL scrollEnabled;
@end

@interface MyPageViewController ()
@property (nonatomic, weak) UIScrollView *theScrollView;
@end

@implementation MyPageViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    for (UIView *view in self.view.subviews) {
        if ([view isKindOfClass:UIScrollView.class]) {
            self.theScrollView = (UIScrollView *)view;
            break;
        }
    }
}

- (void)setScrollEnabled:(BOOL)scrollEnabled
{
    _scrollEnabled = scrollEnabled;
    self.theScrollView.scrollEnabled = scrollEnabled;
}

@end

在UIPageViewController上禁用退回的解决方案:

  1. 创建UIScrollView类别(例如Custom.Scrolling)。 UIScrollView已经是他们手势识别器的代表。
  2. 请注意,您的目标UIViewController(内部为baseVC的{​​UIPageViewController)通过AppDelegate分享。否则,您可以使用运行时(#import <objc/runtime.h>)并将引用属性(到您的控制器baseVC)添加到该类别。
  3. 实施类别:

    @interface UIScrollView (CustomScrolling) <UIGestureRecognizerDelegate>
    @end
    
    @implementation UIScrollView (CustomScrolling)
    
    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
    {
        UIViewController * baseVC = [(AppDelegate *)[[UIApplication sharedApplication] delegate] baseVC];
        if (gestureRecognizer.view == baseVC.pageViewController.theScrollView) {
            NSInteger page = [baseVC selectedIndex];
            NSInteger total = [baseVC viewControllers].count;
            UIPanGestureRecognizer *recognizer = (UIPanGestureRecognizer *)gestureRecognizer;
            CGPoint velocity = [recognizer velocityInView:self];
            BOOL horizontalSwipe = fabs(velocity.x) > fabs(velocity.y);
            if (!horizontalSwipe) {
                return YES;
            }
            BOOL scrollingFromLeftToRight = velocity.x > 0;
            if ((scrollingFromLeftToRight && page > 0) || (!scrollingFromLeftToRight && page < (total - 1))) {
                return YES;
            }
            return NO;
        }
        return YES;
    }
    
    @end
    
  4. 导入#import "UIScrollView+CustomScrolling.h"中使用UIPageViewController的类别文件baseVC

答案 7 :(得分:0)

编辑:不要使用此解决方案。之后我了解到,这引入了一个大约5%的时间错误,用户无法向同一方向翻页。他们必须回页,然后再转发继续。

如果您正在使用UIPageViewControllerDataSource,则每次调用pageViewController:viewControllerBeforeViewController:委托方法时,相对简单的解决方法(以及有点hacky)都会禁用弹跳。以下是一个示例实现:

@interface YourDataSourceObject ()
@property (strong, nonatomic) UIScrollView *scrollView;
@end

@implementation
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
    if (!self.scrollView) {
        for (UIView *view in pageViewController.view.subviews) {
            if ([view isKindOfClass:[UIScrollView class]]) {
                self.scrollView = (UIScrollView *)view;
            }
        }
    }
    self.scrollView.bounces = NO;

    // Your other logic to return the correct view controller. 
}
@end

答案 8 :(得分:0)

另一个选项是设置ScrollView.bounce = false。 它解决了pageViewController的(当然不是有关ScrollView的)滚动反弹的问题。 跳动已禁用,所有页面都可以滚动而不会跳动。

答案 9 :(得分:0)

@Dong Ma的方法很完美,但可以进行一些改进和简化。

要放入 viewDidLoad 的代码:

for subview in view.subviews {
    if let scrollView = subview as? UIScrollView {
        scrollView.delegate = self
        break
    }
}

scrollViewDidScroll 的实现:

public func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if (currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) || (currentPage == totalNumberOfPages - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
      scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
    }
  }

scrollViewWillEndDragging 的实现:

public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    if (currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) || (currentPage == totalNumberOfPages - 1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
      targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0)
    }
  }

答案 10 :(得分:0)

修复从右到左的语言

马东的回答有一个问题。它阻止了rightToLeft用户界面布局方向的第一页和最后一页的滚动。

我解决了。仅第三步将被更改:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    switch UIView.userInterfaceLayoutDirection(for: view.semanticContentAttribute) {
    case .leftToRight:
        if currentIndex == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width {
            scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
        } else if currentIndex == pageViewControllers.count - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width {
            scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
        }
    case .rightToLeft:
        if currentIndex == pageViewControllers.count - 1 && scrollView.contentOffset.x < scrollView.bounds.size.width {
            scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
        } else if currentIndex == 0 && scrollView.contentOffset.x > scrollView.bounds.size.width {
            scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
        }
    }
}

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    switch UIView.userInterfaceLayoutDirection(for: view.semanticContentAttribute) {
    case .leftToRight:
        if currentIndex == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width {
            targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0)
        } else if currentIndex == pageViewControllers.count - 1 && scrollView.contentOffset.x >= scrollView.bounds.size.width {
            targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0)
        }
    case .rightToLeft:
        if currentIndex == pageViewControllers.count - 1 && scrollView.contentOffset.x <= scrollView.bounds.size.width {
            targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0)
        } else if currentIndex == 0 && scrollView.contentOffset.x >= scrollView.bounds.size.width {
            targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0)
        }
    }
}

答案 11 :(得分:0)

唯一可行的解​​决方案,2020年

以下是一个完整的解决方案,与其他答案不同,它不需要您编写/测试自己的代码来跟踪当前显示页面的索引。

假设您的页面存储在sourcePageViewControllers不变数组中,并且在将UIPageViewController创建为myPageViewController之后:

let scrollView = myPageViewController.view.subviews.compactMap({ $0 as? UIScrollView }).first!
scrollView.delegate = <instance of YourScrollViewDelegateClass>

然后:

extension YourScrollViewDelegateClass: UIScrollViewDelegate {

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard sourcePageViewControllers.count > 1 else {
            scrollView.isScrollEnabled = false
            return
        }
        
        guard let viewControllers = myPageViewController.viewControllers, viewControllers.count != 0 else { return }

        let baseRecord =
            viewControllers
            .map { [superview = myPageViewController.view!] viewController -> (viewController: UIViewController, originX: CGFloat) in
                let originX = superview.convert(viewController.view.bounds.origin, from: viewController.view).x
                return (viewController: viewController, originX: originX)
            }
            .sorted(by: { $0.originX < $1.originX })
            .first!

        guard let baseIndex = sourcePageViewControllers.firstIndex(of: baseRecord.viewController) else { return }
        let baseViewControllerOffsetXRatio = -baseRecord.originX/scrollView.bounds.width

        let progress = (CGFloat(baseIndex) + baseViewControllerOffsetXRatio)/CGFloat(sourcePageViewControllers.count - 1)
        if !(0...1 ~= progress) {
            scrollView.isScrollEnabled = false
            scrollView.isScrollEnabled = true
        }
    }

}

答案 12 :(得分:0)

如果您希望完全禁用int colorIndex = 700; MaterialColor primarySwatch = getMaterialColor(Theme.of(_context).primaryColor); Paint paintFill = Paint() ..style = PaintingStyle.fill ..strokeWidth = 6.0 ..color = primarySwatch[colorIndex]; 子类上的滚动视图,可以使用下面的代码段。请注意,这不仅禁用了跳动功能,还禁用了页面的水平滚动。

就我而言,我有一个UISegmentedControl可以在PageViewController中的页面之间进行切换,因此禁用垂直滚动完全可以并为我工作。希望它也能帮助其他人。

UIPageViewController

答案 13 :(得分:-2)

这在 viewDidLoad 中对我有用:

for subview in pager.view.subviews {
    if let scrollView = subview as? UIScrollView {
        scrollView.bounces = false
        scrollView.alwaysBounceHorizontal = false
        break
    }
}