禁用UIPageViewController反弹

问题描述 投票:45回答:9

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

是否有可能禁用UIPageViewController的反弹效果并仍然使用UIPageViewControllerTransitionStyleScroll

ios iphone objective-c uipageviewcontroller
9个回答
67
投票

Disable UIPageViewController's bounce

  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将触发页面翻转到第2页(当然,这不是预期的)。 - (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); } }

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

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
投票

我不知道如何正确管理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
        }
    }
}

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

1
投票

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


0
投票

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

如果你正在使用UIPageViewControllerDataSource,一个相对简单的解决方法(并且有点hacky)是每次调用pageViewController:viewControllerBeforeViewController:委托方法时禁用弹跳。这是一个示例实现:

@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

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"中导入类别文件baseVC,它使用UIPageViewController。

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

0
投票

编辑回答董马,其中:

  • 添加 - 尊重布局方向(例如希伯来语)
  • 固定 - 错误计数currentIndex时刷卡非常快

信息:

  • 用Swift 5.0编写
  • 在Xcode 10.2.1中进行构建和测试
  • iOS 12.0

如何:

  1. 让我们假设我们有一个UIViewController,其中UIPageViewController被添加为子VC。
class ViewController: UIViewController {
    var pageNavigationController: UIPageViewController! 

    private var lastPosition: CGFloat
    private var nextIndex: Int
    var currentIndex: Int     

    // rest of UI's setups  
}
  1. ViewController设为UIPageViewController的代表:
extension ViewController: UIPageViewControllerDataSource {

    func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
        guard
            let currentVisibleViewController = pageViewController.viewControllers?.first,
            let nextIndex = pageViewControllers.firstIndex(of: currentVisibleViewController)
        else {
            return
        }

        self.nextIndex = nextIndex
    }

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed, let currentVisibleViewController = pageViewController.viewControllers?.first, let newIndex = pageViewControllers.firstIndex(of: currentVisibleViewController) {
            self.currentIndex = newIndex
        }

        self.nextIndex = self.currentIndex
    }
}
  1. ViewController设置为UIPageController的数据源:
extension ViewController: UIPageViewControllerDataSource {

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        // provide next VC
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        // provide prev VC
    }

    // IMPORTANT: that's the key why it works, don't forget to add it
    func presentationIndex(for pageViewController: UIPageViewController) -> Int {
        return currentIndex
    }
}
  1. 通过将ViewController设置为UIPageViewControllerUIScrollView的代表来“禁用”弹跳:
// MARK: - UIScrollViewDelegate (disable bouncing for UIPageViewController)
extension BasePaginationVC: UIScrollViewDelegate {

    func attachScrollViewDelegate() {
        for subview in pageNavigationController.view.subviews {
            if let scrollView = subview as? UIScrollView {
                scrollView.delegate = self
                lastPosition = scrollView.contentOffset.x
                break
            }
        }
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        switch UIView.userInterfaceLayoutDirection(for: view.semanticContentAttribute) {
        case .leftToRight:
            if nextIndex > currentIndex {
                if scrollView.contentOffset.x < (lastPosition - (0.9 * scrollView.bounds.size.width)) {
                    currentIndex = nextIndex
                }
            } else {
                if scrollView.contentOffset.x > (lastPosition + (0.9 * scrollView.bounds.size.width)) {
                    currentIndex = nextIndex
                }
            }

            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 nextIndex > currentIndex {
                if scrollView.contentOffset.x > (lastPosition + (0.9 * scrollView.bounds.size.width)) {
                    currentIndex = nextIndex
                }
            } else {
                if scrollView.contentOffset.x < (lastPosition - (0.9 * scrollView.bounds.size.width)) {
                    currentIndex = nextIndex
                }
            }

            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)
            }
        @unknown default:
            fatalError("unknown default")
        }

        lastPosition = scrollView.contentOffset.x
    }

    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)
            }
        @unknown default:
            fatalError("unknown default")
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.