UIScrollView:水平方向にページング、垂直方向にスクロールしますか?


88

UIScrollViewページングとスクロールがオンになっているを、特定の瞬間に垂直方向または水平方向にのみ移動させるにはどうすればよいですか?

私の理解では、directionalLockEnabledプロパティはこれを達成する必要がありますが、斜めにスワイプすると、モーションが単一の軸に制限されるのではなく、ビューが斜めにスクロールします。

編集:明確にするために、ユーザーが水平方向または垂直方向にスクロールできるようにしたいのですが、両方を同時にスクロールすることはできません。


ビューをHoriz / Vertのみでスクロールするように制限しますか、それともユーザーにHorizまたはVertをスクロールさせますが、両方を同時にスクロールさせませんか?
Hartung

お願いをして、タイトルを変更して、もう少し面白くて自明ではないように見せてもらえますか?「UIScrollView:水平方向にページング、垂直方向にスクロールしますか?」のようなもの
Andrey Tarantsov 2009年

残念ながら、(家に帰ったときに同じ名前で登録する前に)アクセスできなくなったコンピューターに未登録のユーザーとして質問を投稿したため、編集できません。これはまた、元の質問を編集するのではなく、下から下へのフォローアップを説明するはずです。今回はスタックのエチケットを破ってすみません!
ニック

回答:


117

あなたは非常に厳しい状況にあります、私は言わなければなりません。

pagingEnabled=YESページを切り替えるには、UIScrollViewを使用する必要がありますがpagingEnabled=NO、垂直方向にスクロールする必要があることに注意してください。

2つの可能な戦略があります。どちらが機能するか/実装が簡単かわからないので、両方を試してください。

最初:ネストされたUIScrollViews。率直に言って、私はこれを機能させる人をまだ見ていません。しかし、私は個人的に十分に努力していません。私の実践では、十分に努力すれば、UIScrollViewに好きなことをさせることができることが示されています

したがって、戦略は、外側のスクロールビューが水平スクロールのみを処理し、内側のスクロールビューが垂直スクロールのみを処理するようにすることです。これを実現するには、UIScrollViewが内部でどのように機能するかを知っている必要があります。これはhitTestメソッドをオーバーライドし、常にそれ自体を返すため、すべてのタッチイベントはUIScrollViewに入ります。次に内部touchesBegantouchesMovedなど、それをチェックし、イベントに興味を持っていて、いずれかのハンドルまたは内部部品へのそれを渡す場合。

タッチを処理するか転送するかを決定するために、UIScrollViewは最初にタッチしたときにタイマーを開始します。

  • 150ミリ秒以内に指を大きく動かさなかった場合は、イベントが内部ビューに渡されます。

  • 150ms以内に指を大きく動かすと、スクロールが開始されます(イベントが内部ビューに渡されることはありません)。

    テーブル(スクロールビューのサブクラス)にタッチしてすぐにスクロールを開始すると、タッチした行が強調表示されないことに注意してください。

  • あなたがしている場合ではない150msの範囲内大幅に指を移動し、UIScrollViewのは、インナービューにイベントを渡し始めたが、その後、あなたが開始するためにスクロールするために十分に指を移動した、UIScrollViewを呼び出しtouchesCancelled、内側のビューにしてスクロールを開始します。

    テーブルに触れ、指を少し押したままスクロールを開始すると、触れた行が最初に強調表示され、後で強調表示が解除されることに注意してください。

これらの一連のイベントは、UIScrollViewの構成によって変更できます。

  • delaysContentTouchesNOの場合、タイマーは使用されません—イベントはすぐに内部コントロールに移動します(ただし、指を十分に動かすとキャンセルされます)
  • cancelsTouchesNOの場合、イベントがコントロールに送信されると、スクロールは発生しません。

それが受け取ることUIScrollViewのあることに注意して、すべての touchesBegintouchesMovedtouchesEndedおよびtouchesCanceled(そのためにはCocoaTouchからイベントをhitTestそうするよう指示します)。次に、必要に応じて、必要に応じてそれらを内部ビューに転送します。

UIScrollViewについてすべて理解したので、その動作を変更できます。ユーザーがビューに触れて指を(少しでも)動かし始めると、ビューが垂直方向にスクロールし始めるように、垂直スクロールを優先したいと思うことは間違いありません。ただし、ユーザーが指を水平方向に十分に動かした場合は、垂直スクロールをキャンセルして水平スクロールを開始する必要があります。

外側のUIScrollViewをサブクラス化して(たとえば、クラスにRemorsefulScrollViewという名前を付けます)、デフォルトの動作の代わりに、すべてのイベントをすぐに内側のビューに転送し、大きな水平方向の動きが検出された場合にのみスクロールします。

RemorsefulScrollViewをそのように動作させるにはどうすればよいですか?

  • 垂直スクロールを無効にdelaysContentTouchesしてNOに設定すると、ネストされたUIScrollViewが機能するように見えます。残念ながら、そうではありません。UIScrollViewは、高速モーション(無効にすることはできません)に対して追加のフィルタリングを行うように見えるため、UIScrollViewは水平方向にしかスクロールできない場合でも、常に十分な速度の垂直方向のモーションを消費します(無視します)。

    影響が非常に大きいため、ネストされたスクロールビュー内の垂直スクロールは使用できません。(この設定が正確に行われているようです。試してみてください。指を150ミリ秒間押し続けてから、垂直方向に移動します。ネストされたUIScrollViewは期待どおりに機能します!)

  • これは、UIScrollViewのコードをイベント処理に使用できないことを意味します。RemorsefulScrollViewの4つのタッチ処理メソッドすべてをオーバーライドし、最初に独自の処理を実行する必要がありますsuper。水平スクロールを使用することにした場合にのみ、イベントを(UIScrollView)に転送します。

  • ただしtouchesBegan、UIScrollViewに渡す必要があります。これ、将来の水平スクロールのために基本座標を記憶する必要があるためです(後で水平スクロールである判断した場合)。引数をtouchesBegan格納できないため、後でUIScrollViewに送信することはできません。touches引数には、次のtouchesMovedイベントの前に変更されるオブジェクトが含まれており、古い状態を再現することはできません。

    したがってtouchesBegan、すぐにUIScrollViewに渡す必要がありますが、touchesMoved水平方向にスクロールすることを決定するまで、UIScrollViewからそれ以上のイベントを非表示にします。いいえtouchesMovedはスクロールがないことを意味するので、このイニシャルtouchesBeganは害を及ぼしません。ただしdelaysContentTouches、追加のサプライズタイマーが干渉しないように、NOに設定してください。

    (オフトピック—あなたとは異なり、UIScrollViewタッチを適切に保存し、元のtouchesBeganイベントを後で再現して転送できます。未公開のAPIを使用するという不公平な利点があるため、変更される前にタッチオブジェクトのクローンを作成できます。)

  • あなたは常に前進することを考えるとtouchesBegan、あなたも転送しなければならないtouchesCancelledtouchesEnded。あなたは有効にする必要がありますtouchesEndedtouchesCancelledUIScrollViewのは解釈してしまうため、しかし、touchesBegantouchesEndedタッチ・クリックなどのシーケンス、およびインナービューに転送します。すでに適切なイベントを自分で転送しているので、UIScrollViewに何も転送させたくありません。

基本的に、ここにあなたがする必要があることの擬似コードがあります。簡単にするために、マルチタッチイベントが発生した後は水平スクロールを許可しません。

// RemorsefulScrollView.h

@interface RemorsefulScrollView : UIScrollView {
  CGPoint _originalPoint;
  BOOL _isHorizontalScroll, _isMultitouch;
  UIView *_currentChild;
}
@end

// RemorsefulScrollView.m

// the numbers from an example in Apple docs, may need to tune them
#define kThresholdX 12.0f
#define kThresholdY 4.0f

@implementation RemorsefulScrollView

- (id)initWithFrame:(CGRect)frame {
  if (self = [super initWithFrame:frame]) {
    self.delaysContentTouches = NO;
  }
  return self;
}

- (id)initWithCoder:(NSCoder *)coder {
  if (self = [super initWithCoder:coder]) {
    self.delaysContentTouches = NO;
  }
  return self;
}

- (UIView *)honestHitTest:(CGPoint)point withEvent:(UIEvent *)event {
  UIView *result = nil;
  for (UIView *child in self.subviews)
    if ([child pointInside:point withEvent:event])
      if ((result = [child hitTest:point withEvent:event]) != nil)
        break;
  return result;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event]; // always forward touchesBegan -- there's no way to forward it later
    if (_isHorizontalScroll)
      return; // UIScrollView is in charge now
    if ([touches count] == [[event touchesForView:self] count]) { // initial touch
      _originalPoint = [[touches anyObject] locationInView:self];
    _currentChild = [self honestHitTest:_originalPoint withEvent:event];
    _isMultitouch = NO;
    }
  _isMultitouch |= ([[event touchesForView:self] count] > 1);
  [_currentChild touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  if (!_isHorizontalScroll && !_isMultitouch) {
    CGPoint point = [[touches anyObject] locationInView:self];
    if (fabsf(_originalPoint.x - point.x) > kThresholdX && fabsf(_originalPoint.y - point.y) < kThresholdY) {
      _isHorizontalScroll = YES;
      [_currentChild touchesCancelled:[event touchesForView:self] withEvent:event]
    }
  }
  if (_isHorizontalScroll)
    [super touchesMoved:touches withEvent:event]; // UIScrollView only kicks in on horizontal scroll
  else
    [_currentChild touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  if (_isHorizontalScroll)
    [super touchesEnded:touches withEvent:event];
    else {
    [super touchesCancelled:touches withEvent:event];
    [_currentChild touchesEnded:touches withEvent:event];
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  [super touchesCancelled:touches withEvent:event];
  if (!_isHorizontalScroll)
    [_currentChild touchesCancelled:touches withEvent:event];
}

@end

私はこれを実行したりコンパイルしたりしようとはしていません(そしてプレーンテキストエディターでクラス全体を入力しました)が、上記から始めてうまくいけば動作させることができます。

私が見る唯一の隠れた落とし穴は、UIScrollView以外の子ビューをRemorsefulScrollViewに追加した場合、子がUIScrollViewのように常にタッチを処理しない場合、子に転送したタッチイベントがレスポンダーチェーンを介して戻ってくる可能性があることです。防弾のRemorsefulScrollView実装は、touchesXxx再突入から保護します。

2番目の戦略:何らかの理由でネストされたUIScrollViewが機能しないか、正しく実行するのが難しい場合は、1つのUIScrollViewだけを使用pagingEnabledして、scrollViewDidScrollデリゲートメソッドからそのプロパティをオンザフライで切り替えることができます。

斜めのスクロールを防ぐには、最初にのcontentOffsetを記憶しscrollViewWillBeginDraggingscrollViewDidScroll斜めの動きを検出した場合は内部のcontentOffsetを確認してリセットする必要があります。試すもう1つの戦略は、ユーザーの指がどちらの方向に進むかを決定したら、contentSizeをリセットして一方向へのスクロールのみを有効にすることです。(UIScrollViewは、デリゲートメソッドからcontentSizeとcontentOffsetをいじることについてかなり寛容なようです。)

それが機能しないか、見た目が悪くなる場合はtouchesBegantouchesMovedなどをオーバーライドする必要があり、斜めの移動イベントをUIScrollViewに転送しないでください。(ただし、この場合、ユーザーエクスペリエンスは最適ではありません。これは、斜めの動きを一方向に強制するのではなく無視する必要があるためです。本当に冒険心がある場合は、RevengeTouchのような独自のUITouchのそっくりさんを作成できます。Objective -Cは昔ながらのCであり、Cほどダックタイプのものはありません。オブジェクトの実際のクラスをチェックする人がいない限り、他のクラスと同じように見せることができます。これにより、オープンになります。好きなタッチを好きな座標で合成する可能性。)

バックアップ戦略:Three20ライブラリのUIScrollViewの正常な再実装であるTTScrollViewがあります。残念ながら、それはユーザーにとって非常に不自然で非iphonishに感じます。ただし、UIScrollViewを使用するたびに失敗した場合は、カスタムコード化されたスクロールビューにフォールバックできます。可能であれば、これに反対することをお勧めします。UIScrollViewを使用すると、将来のiPhone OSバージョンでどのように進化しても、ネイティブのルックアンドフィールを確実に取得できます。

さて、この小さなエッセイは少し長すぎました。数日前にScrollingMadnessで作業した後、私はまだUIScrollViewゲームに夢中です。

PSこれらのいずれかが機能し、共有したい場合は、関連するコードをandreyvit@gmail.comに電子メールで送信してください。ScrollingMadnessのトリックバッグに喜んで含めます。

PPSこの小さなエッセイをScrollingMadnessREADMEに追加します。


7
このネストされたスクロールビューの動作は、SDKですぐに機能し、面白いビジネスは必要ありません
andygeers 2011年

1
+1されたandygeersはコメントし、それが正確ではないことに気づきました。開始点から斜めにドラッグすることで通常のUIScrollViewを「壊す」ことができます。その後、「スクローラーを引っ張って負け」ます。離すまで方向ロックは無視され、神はどこにいるのかがわかります。
カレ2011

素晴らしい舞台裏の説明をありがとう!以下のMattiasWadmanのソリューションを使用しました。これは、少し侵襲性の低いIMOに見えます。
Ortwin Gentz 2014

UIScrollViewがパンジェスチャレコグナイザーを公開したので、この回答が更新されるのを見るのは素晴らしいことです。(しかし、おそらくそれは元の範囲外です。)
jtbandes 2014

この投稿が更新される可能性はありますか?素晴らしい投稿とあなたの入力に感謝します。
パバン2015

56

これはいつも私のために働きます...

scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);

20
この質問に出くわした他の人のために:この回答は、質問を明確にする編集の前に投稿されたと思います。これにより、水平方向にのみスクロールできるようになります。垂直方向または水平方向にスクロールできるが、斜め方向にはスクロールできないという問題には対処していません。
andygeers 2011

私にとっては魅力のように機能します。ページング付きの非常に長い水平scrollViewがあり、各ページにUIWebViewがあり、必要な垂直スクロールを処理します。
トムレッドマン

優秀な!しかし、誰かがこれがどのように機能するかを説明できますか(contentSizeの高さを1に設定)!
Praveen 2013年

それは本当に機能しますが、なぜそしてどのように機能するのですか?誰かがそれを理解できますか?
マルコフランチェコビッチ2013年

27

私のような怠惰な人々のために:

設定するscrollview.directionalLockEnabledにはYES

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

- (void) scrollViewDidScroll: (UIScrollView *) scrollView
{
    if (scrollView.contentOffset.x != self.oldContentOffset.x)
    {
        scrollView.pagingEnabled = YES;
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
                                               self.oldContentOffset.y);
    }
    else
    {
        scrollView.pagingEnabled = NO;
    }
}

- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

16

私はこの問題を解決するために次の方法を使用しました、それがあなたを助けることを願っています。

まず、コントローラーのヘッダーファイルで次の変数を定義します。

CGPoint startPos;
int     scrollDirection;

startPosは、デリゲートがscrollViewWillBeginDraggingメッセージを受信したときに、contentOffset値を保持します。したがって、この方法ではこれを行います。

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    startPos = scrollView.contentOffset;
    scrollDirection=0;
}

次に、これらの値を使用して、scrollViewDidScrollメッセージでユーザーが意図したスクロール方向を決定します。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

   if (scrollDirection==0){//we need to determine direction
       //use the difference between positions to determine the direction.
       if (abs(startPos.x-scrollView.contentOffset.x)<abs(startPos.y-scrollView.contentOffset.y)){          
          NSLog(@"Vertical Scrolling");
          scrollDirection=1;
       } else {
          NSLog(@"Horitonzal Scrolling");
          scrollDirection=2;
       }
    }
//Update scroll position of the scrollview according to detected direction.     
    if (scrollDirection==1) {
       [scrollView setContentOffset:CGPointMake(startPos.x,scrollView.contentOffset.y) animated:NO];
    } else if (scrollDirection==2){
       [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x,startPos.y) animated:NO];
    }
 }

最後に、ユーザーがドラッグを終了したときに、すべての更新操作を停止する必要があります。

 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    if (decelerate) {
       scrollDirection=3;
    }
 }

うーん...実際には、さらに調査した後、それはかなりうまく機能しますが、問題は減速フェーズ中に方向の制御を失うことです-したがって、指を斜めに動かし始めると、水平または垂直にしか動かないように見えます手放すまで、しばらくの間対角線方向に減速します
andygeers 2011

場合@andygeersは、それは良い仕事かもしれません- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (decelerate) { scrollDirection=3; } }に変更された- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (!decelerate) { scrollDirection=0; } }- (void)scrollViewDidEndDecelerating{ scrollDirection=0; }
cduck

私のために働いていませんでした。飛行中にcontentOffsetを設定すると、ページング動作が破壊されるようです。MattiasWadmanのソリューションを使用しました。
Ortwin Gentz 2014

13

私はここで重大な問題を抱えているかもしれませんが、同じ問題を解決しようとしていたときに、今日この投稿に出くわしました。これが私の解決策です、うまく機能しているようです。

画面一杯のコンテンツを表示したいのですが、ユーザーが他の4つの画面一杯のいずれかに上下左右にスクロールできるようにします。サイズが320x480でcontentSizeが960x1440の単一のUIScrollViewがあります。コンテンツオフセットは320,480から始まります。scrollViewDidEndDecelerating:で、5つのビュー(中央のビューとその周囲の4つのビュー)を再描画し、コンテンツオフセットを320,480にリセットします。

これが私のソリューションの要であるscrollViewDidScroll:メソッドです。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{   
    if (!scrollingVertically && ! scrollingHorizontally)
    {
        int xDiff = abs(scrollView.contentOffset.x - 320);
        int yDiff = abs(scrollView.contentOffset.y - 480);

        if (xDiff > yDiff)
        {
            scrollingHorizontally = YES;
        }
        else if (xDiff < yDiff)
        {
            scrollingVertically = YES;
        }
    }

    if (scrollingHorizontally)
    {
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, 480);
    }
    else if (scrollingVertically)
    {
        scrollView.contentOffset = CGPointMake(320, scrollView.contentOffset.y);
    }
}

ユーザーがスクロールビューを離したときに、このコードがうまく「減速」しないことに気付くでしょう。それは完全に停止します。減速したくない場合は、これで問題ないかもしれません。
andygeers 2011

4

サブビューの高さをスクロールビューの高さとコンテンツサイズの高さと同じにするのと同じくらい簡単です。次に、垂直スクロールが無効になります。


2
この質問に出くわした他の人のために:この回答は、質問を明確にする編集の前に投稿されたと思います。これにより、水平方向にのみスクロールできるようになります。垂直方向または水平方向にスクロールできるが、斜め方向にはスクロールできないという問題には対処していません。
andygeers 2011

3

これは、UIScrollView直交スクロールのみを許可するページの実装です。必ずに設定pagingEnabledしてくださいYES

PagedOrthoScrollView.h

@interface PagedOrthoScrollView : UIScrollView
@end

PagedOrthoScrollView.m

#import "PagedOrthoScrollView.h"

typedef enum {
  PagedOrthoScrollViewLockDirectionNone,
  PagedOrthoScrollViewLockDirectionVertical,
  PagedOrthoScrollViewLockDirectionHorizontal
} PagedOrthoScrollViewLockDirection; 

@interface PagedOrthoScrollView ()
@property(nonatomic, assign) PagedOrthoScrollViewLockDirection dirLock;
@property(nonatomic, assign) CGFloat valueLock;
@end

@implementation PagedOrthoScrollView
@synthesize dirLock;
@synthesize valueLock;

- (id)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self == nil) {
    return self;
  }

  self.dirLock = PagedOrthoScrollViewLockDirectionNone;
  self.valueLock = 0;

  return self;
}

- (void)setBounds:(CGRect)bounds {
  int mx, my;

  if (self.dirLock == PagedOrthoScrollViewLockDirectionNone) {
    // is on even page coordinates, set dir lock and lock value to closest page 
    mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds));
    if (mx != 0) {
      self.dirLock = PagedOrthoScrollViewLockDirectionHorizontal;
      self.valueLock = (round(CGRectGetMinY(bounds) / CGRectGetHeight(self.bounds)) *
                        CGRectGetHeight(self.bounds));
    } else {
      self.dirLock = PagedOrthoScrollViewLockDirectionVertical;
      self.valueLock = (round(CGRectGetMinX(bounds) / CGRectGetWidth(self.bounds)) *
                        CGRectGetWidth(self.bounds));
    }

    // show only possible scroll indicator
    self.showsVerticalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionVertical;
    self.showsHorizontalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionHorizontal;
  }

  if (self.dirLock == PagedOrthoScrollViewLockDirectionHorizontal) {
    bounds.origin.y = self.valueLock;
  } else {
    bounds.origin.x = self.valueLock;
  }

  mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds));
  my = abs((int)CGRectGetMinY(bounds) % (int)CGRectGetHeight(self.bounds));

  if (mx == 0 && my == 0) {
    // is on even page coordinates, reset lock
    self.dirLock = PagedOrthoScrollViewLockDirectionNone;
  }

  [super setBounds:bounds];
}

@end

1
iOS 7でもうまく機能します。ありがとう!
Ortwin Gentz 2014

3

私もこの問題を解決する必要がありました。AndreyTarantsovの回答には、UIScrollViews作業方法を理解するための有用な情報がたくさんありますが、解決策は少し複雑すぎると感じています。サブクラス化やタッチ転送を含まない私のソリューションは次のとおりです。

  1. 各方向に1つずつ、2つのネストされたスクロールビューを作成します。
  2. 2つのダミーを作成しますUIPanGestureRecognizers。これも各方向に1つです。
  3. セレクターを使用して、UIScrollViews'panGestureRecognizerプロパティと、UIPanGestureRecognizerUIScrollViewの目的のスクロール方向に垂直な方向に対応するダミーとの間に障害の依存関係を作成しますUIGestureRecognizer's -requireGestureRecognizerToFail:
  4. では-gestureRecognizerShouldBegin:、あなたのダミーUIPanGestureRecognizersのためのコールバック、ジェスチャーの-translationInViewを使用して、パンの最初の方向を計算:セレクタを(あなたUIPanGestureRecognizersあなたのタッチがパンとして登録するために十分に翻訳されるまで、このデリゲートのコールバックを呼び出すことはありません)。計算された方向に応じてジェスチャレコグナイザーの開始を許可または禁止します。これにより、垂直UIScrollViewパンジェスチャレコグナイザーの開始を許可するかどうかを制御できます。
  5. では-gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:、ダミーUIPanGestureRecognizersをスクロールと一緒に実行しないでください(追加したい追加の動作がない限り)。

2

私が行ったのは、ビュー画面のサイズのフレームでページングUIScrollViewを作成し、コンテンツサイズをすべてのコンテンツに必要な幅と高さ1に設定することでした(Tonetelに感謝)。次に、各ページにフレームを設定し、ビュー画面のサイズを設定してメニューUIScrollViewページを作成し、各ページのコンテンツサイズを画面の幅と各ページに必要な高さに設定しました。次に、各メニューページをページングビューに追加するだけです。ページングスクロールビューではページングが有効になっているが、メニュービューでは無効になっていることを確認してください(デフォルトでは無効になっているはずです)。

ページングスクロールビューは水平方向にスクロールしますが、コンテンツの高さのために垂直方向にはスクロールしません。各ページは垂直方向にスクロールしますが、コンテンツサイズの制約があるため、水平方向にはスクロールしません。

その説明が望まれる何かを残した場合のコードは次のとおりです。

UIScrollView *newPagingScrollView =  [[UIScrollView alloc] initWithFrame:self.view.bounds]; 
[newPagingScrollView setPagingEnabled:YES];
[newPagingScrollView setShowsVerticalScrollIndicator:NO];
[newPagingScrollView setShowsHorizontalScrollIndicator:NO];
[newPagingScrollView setDelegate:self];
[newPagingScrollView setContentSize:CGSizeMake(self.view.bounds.size.width * NumberOfDetailPages, 1)];
[self.view addSubview:newPagingScrollView];

float pageX = 0;

for (int i = 0; i < NumberOfDetailPages; i++)
{               
    CGRect pageFrame = (CGRect) 
    {
        .origin = CGPointMake(pageX, pagingScrollView.bounds.origin.y), 
        .size = pagingScrollView.bounds.size
    };
    UIScrollView *newPage = [self createNewPageFromIndex:i ToPageFrame:pageFrame]; // newPage.contentSize custom set in here        
    [pagingScrollView addSubview:newPage];

    pageX += pageFrame.size.width;  
}           

2

Tonetelに感謝します。私はあなたのアプローチを少し変更しましたが、それはまさに私が水平スクロールを防ぐために必要なものでした。

self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 1);

2

これは私にとって非常に不安定だったicompotの改善されたソリューションです。これは正常に機能し、実装は簡単です。

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

- (void) scrollViewDidScroll: (UIScrollView *) scrollView
{    

    float XOffset = fabsf(self.oldContentOffset.x - scrollView.contentOffset.x );
    float YOffset = fabsf(self.oldContentOffset.y - scrollView.contentOffset.y );

        if (scrollView.contentOffset.x != self.oldContentOffset.x && (XOffset >= YOffset) )
        {

            scrollView.pagingEnabled = YES;
            scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
                                                   self.oldContentOffset.y);

        }
        else
        {
            scrollView.pagingEnabled = NO;
               scrollView.contentOffset = CGPointMake( self.oldContentOffset.x,
                                                   scrollView.contentOffset.y);
        }

}


- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

2

将来の参考のために、私はAndrey Tanasovの答えに基づいて、正常に機能する次のソリューションを考え出しました。

まず、contentOffsetを格納する変数を定義し、それを0,0座標に設定します

var positionScroll = CGPointMake(0, 0)

次に、scrollViewデリゲートに以下を実装します。

override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
    // Note that we are getting a reference to self.scrollView and not the scrollView referenced passed by the method
    // For some reason, not doing this fails to get the logic to work
    self.positionScroll = (self.scrollView?.contentOffset)!
}

override func scrollViewDidScroll(scrollView: UIScrollView) {

    // Check that contentOffset is not nil
    // We do this because the scrollViewDidScroll method is called on viewDidLoad
    if self.scrollView?.contentOffset != nil {

        // The user wants to scroll on the X axis
        if self.scrollView?.contentOffset.x > self.positionScroll.x || self.scrollView?.contentOffset.x < self.positionScroll.x {

            // Reset the Y position of the scrollView to what it was BEFORE scrolling started
            self.scrollView?.contentOffset = CGPointMake((self.scrollView?.contentOffset.x)!, self.positionScroll.y);
            self.scrollView?.pagingEnabled = true
        }

        // The user wants to scroll on the Y axis
        else {
            // Reset the X position of the scrollView to what it was BEFORE scrolling started
            self.scrollView?.contentOffset = CGPointMake(self.positionScroll.x, (self.scrollView?.contentOffset.y)!);
            self.collectionView?.pagingEnabled = false
        }

    }

}

お役に立てれば。


1

ドキュメントから:

「デフォルト値はNOです。これは、水平方向と垂直方向の両方でスクロールが許可されることを意味します。値がYESで、ユーザーが1つの一般的な方向(水平または垂直)にドラッグを開始すると、スクロールビューは他の方向へのスクロールを無効にします。 「」

重要なのは「ユーザー一方向にドラッグし始めたら」だと思います。したがって、斜めにドラッグし始めても、これはうまくいきません。解釈に関しては、これらのドキュメントが常に信頼できるわけではありませんが、それはあなたが見ているものと一致しているようです。

これは実際には賢明なようです。私は尋ねなければなりません-なぜあなたはいつでも水平のみまたは垂直のみに制限したいのですか?おそらくUIScrollViewはあなたのためのツールではありませんか?


1

私のビューは10個の水平方向のビューで構成されており、それらのビューの一部は複数の垂直方向のビューで構成されているため、次のようになります。


1.0   2.0   3.1    4.1
      2.1   3.1
      2.2
      2.3

横軸と縦軸の両方でページングが有効になっている

ビューには、次の属性もあります。

[self setDelaysContentTouches:NO];
[self setMultipleTouchEnabled:NO];
[self setDirectionalLockEnabled:YES];

斜めスクロールを防ぐには、次のようにします。

-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
    int pageWidth = 768;
    int pageHeight = 1024;
    int subPage =round(self.contentOffset.y / pageHeight);
    if ((int)self.contentOffset.x % pageWidth != 0 && (int)self.contentOffset.y % pageHeight != 0) {
        [self setContentOffset:CGPointMake(self.contentOffset.x, subPage * pageHeight];
    } 
}

私にとっては魅力のように機能します!


1
これがどのように機能するのか理解できません...サンプルプロジェクトをアップロードできますか?
hfossli 2010

1

およびで_isHorizontalScrollNOにリセットする必要がtouchesEndedありtouchesCancelledます。



0

3.0ベータ版でこれを試したことがない場合は、試してみることをお勧めします。現在、スクロールビュー内で一連のテーブルビューを使用していますが、期待どおりに機能します。(まあ、とにかく、スクロールはします。まったく異なる問題の修正を探しているときにこの質問を見つけました...)


0

@AndreyTarantsovの2番目のソリューションでSwiftを探している人にとっては、コードで次のようになります。

    var positionScroll:CGFloat = 0

    func scrollViewWillBeginDragging(scrollView: UIScrollView) {
        positionScroll = self.theScroll.contentOffset.x
    }

    func scrollViewDidScroll(scrollView: UIScrollView) {
        if self.theScroll.contentOffset.x > self.positionScroll || self.theScroll.contentOffset.x < self.positionScroll{
             self.theScroll.pagingEnabled = true
        }else{
            self.theScroll.pagingEnabled = false
        }
    }

ここで行っているのは、スクロールビューがドラッグされる直前の現在の位置を保持し、xの現在の位置と比較してxが増加または減少したかどうかを確認することです。はいの場合は、pagingEnabledをtrueに設定し、いいえ(yが増加/減少した)の場合は、pagingEnabledをfalseに設定します。

それが新参者に役立つことを願っています!


0

scrollViewDidBeginDragging(_ :)を実装し、基になるUIPanGestureRecognizerの速度を確認することで、これを解決することができました。

注意:このソリューションでは、斜めになっていたすべてのパンがscrollViewによって無視されます。

override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    super.scrollViewWillBeginDragging(scrollView)
    let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView)
    if velocity.x != 0 && velocity.y != 0 {
        scrollView.panGestureRecognizer.isEnabled = false
        scrollView.panGestureRecognizer.isEnabled = true
    }
}

-3

インターフェイスビルダーを使用してインターフェイスを設計している場合、これは非常に簡単に実行できます。

Interface Builderで、UIScrollViewをクリックし、(インスペクターで)オプションを選択して一方向のスクロールのみに制限します。


1
IBにはこれを行うためのオプションはありません。参照している可能性のある2つのオプションは、実際のスクロールではなく、スクロールバーの表示に関係しています。さらに、方向ロックは、スクロールが開始されると方向をロックするだけです。
Sophtware 2010年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.