web-dev-qa-db-ja.com

UIPageViewControllerバウンスを無効にする

これをたくさん検索しましたが、まだ適切な解決策が見つかりませんでした。

UIPageViewControllerのバウンス効果を無効にして、引き続きUIPageViewControllerTransitionStyleScrollを使用することは可能ですか?

45
Mario

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をOriginにリセットすることです(NOT(0,0)、but(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);
    }
}
67
Dong Ma

UIPageViewControllerのバウンスを無効にする

Swift 2.2

回答への追加

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

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
        }
    }
}
2
mwright

別のオプションは、ScrollView.bounce = falseを設定することです。 pageViewControllerの(もちろんScrollViewについてではありません)スクロールバウンスの問題を解決しました。バウンスは無効であり、すべてのページはバウンスなしでスクロールできます。

1
PERIPERI

UIPageViewControllerは実際にはあまり役に立ちません。 UIScrollViewをView Controllerで非常に簡単に使用でき、そのバウンスを無効にできます。

次のようなことをしてください

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
arsenius

Dong Maの編集済み回答、ここで:

  • 追加-レイアウトの方向を尊重(ヘブライ語など)
  • 修正-非常に素早くスワイプしたときに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. ViewControllerUIPageViewControllerのデリゲートとしてUIScrollViewを設定することにより、バウンスを「無効にする」:
// 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")
        }
    }
}
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
KrLx_roller

編集:このソリューションは使用しないでください。その後、これによりバグが発生し、約5%の時間、ユーザーが同じ方向にページを移動できないことがわかりました。戻るにはページを戻してから、もう一度進む必要があります。

UIPageViewControllerDataSourceを使用している場合、比較的単純な回避策(および少しハッキング)は、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
guptron

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カテゴリを作成します(例:CustomScrolling)。 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. UIPageViewControllerを使用するカテゴリファイル#import "UIScrollView+CustomScrolling.h"baseVCにインポートします。

0
sig