UIScrollView
ページングとスクロールがオンになっているを、特定の瞬間に垂直方向または水平方向にのみ移動させるにはどうすればよいですか?
私の理解では、directionalLockEnabled
プロパティはこれを達成する必要がありますが、斜めにスワイプすると、モーションが単一の軸に制限されるのではなく、ビューが斜めにスクロールします。
編集:明確にするために、ユーザーが水平方向または垂直方向にスクロールできるようにしたいのですが、両方を同時にスクロールすることはできません。
UIScrollView
ページングとスクロールがオンになっているを、特定の瞬間に垂直方向または水平方向にのみ移動させるにはどうすればよいですか?
私の理解では、directionalLockEnabled
プロパティはこれを達成する必要がありますが、斜めにスワイプすると、モーションが単一の軸に制限されるのではなく、ビューが斜めにスクロールします。
編集:明確にするために、ユーザーが水平方向または垂直方向にスクロールできるようにしたいのですが、両方を同時にスクロールすることはできません。
回答:
あなたは非常に厳しい状況にあります、私は言わなければなりません。
pagingEnabled=YES
ページを切り替えるには、UIScrollViewを使用する必要がありますがpagingEnabled=NO
、垂直方向にスクロールする必要があることに注意してください。
2つの可能な戦略があります。どちらが機能するか/実装が簡単かわからないので、両方を試してください。
最初:ネストされたUIScrollViews。率直に言って、私はこれを機能させる人をまだ見ていません。しかし、私は個人的に十分に努力していません。私の実践では、十分に努力すれば、UIScrollViewに好きなことをさせることができることが示されています。
したがって、戦略は、外側のスクロールビューが水平スクロールのみを処理し、内側のスクロールビューが垂直スクロールのみを処理するようにすることです。これを実現するには、UIScrollViewが内部でどのように機能するかを知っている必要があります。これはhitTest
メソッドをオーバーライドし、常にそれ自体を返すため、すべてのタッチイベントはUIScrollViewに入ります。次に内部touchesBegan
、touchesMoved
など、それをチェックし、イベントに興味を持っていて、いずれかのハンドルまたは内部部品へのそれを渡す場合。
タッチを処理するか転送するかを決定するために、UIScrollViewは最初にタッチしたときにタイマーを開始します。
150ミリ秒以内に指を大きく動かさなかった場合は、イベントが内部ビューに渡されます。
150ms以内に指を大きく動かすと、スクロールが開始されます(イベントが内部ビューに渡されることはありません)。
テーブル(スクロールビューのサブクラス)にタッチしてすぐにスクロールを開始すると、タッチした行が強調表示されないことに注意してください。
あなたがしている場合ではない150msの範囲内大幅に指を移動し、UIScrollViewのは、インナービューにイベントを渡し始めたが、その後、あなたが開始するためにスクロールするために十分に指を移動した、UIScrollViewを呼び出しtouchesCancelled
、内側のビューにしてスクロールを開始します。
テーブルに触れ、指を少し押したままスクロールを開始すると、触れた行が最初に強調表示され、後で強調表示が解除されることに注意してください。
これらの一連のイベントは、UIScrollViewの構成によって変更できます。
delaysContentTouches
NOの場合、タイマーは使用されません—イベントはすぐに内部コントロールに移動します(ただし、指を十分に動かすとキャンセルされます)cancelsTouches
NOの場合、イベントがコントロールに送信されると、スクロールは発生しません。それが受け取ることUIScrollViewのあることに注意して、すべての touchesBegin
、touchesMoved
、touchesEnded
および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
、あなたも転送しなければならないtouchesCancelled
とtouchesEnded
。あなたは有効にする必要がありますtouchesEnded
にtouchesCancelled
UIScrollViewのは解釈してしまうため、しかし、touchesBegan
、touchesEnded
タッチ・クリックなどのシーケンス、およびインナービューに転送します。すでに適切なイベントを自分で転送しているので、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を記憶しscrollViewWillBeginDragging
、scrollViewDidScroll
斜めの動きを検出した場合は内部のcontentOffsetを確認してリセットする必要があります。試すもう1つの戦略は、ユーザーの指がどちらの方向に進むかを決定したら、contentSizeをリセットして一方向へのスクロールのみを有効にすることです。(UIScrollViewは、デリゲートメソッドからcontentSizeとcontentOffsetをいじることについてかなり寛容なようです。)
それが機能しないか、見た目が悪くなる場合はtouchesBegan
、touchesMoved
などをオーバーライドする必要があり、斜めの移動イベントをUIScrollViewに転送しないでください。(ただし、この場合、ユーザーエクスペリエンスは最適ではありません。これは、斜めの動きを一方向に強制するのではなく無視する必要があるためです。本当に冒険心がある場合は、RevengeTouchのような独自のUITouchのそっくりさんを作成できます。Objective -Cは昔ながらのCであり、Cほどダックタイプのものはありません。オブジェクトの実際のクラスをチェックする人がいない限り、他のクラスと同じように見せることができます。これにより、オープンになります。好きなタッチを好きな座標で合成する可能性。)
バックアップ戦略:Three20ライブラリのUIScrollViewの正常な再実装であるTTScrollViewがあります。残念ながら、それはユーザーにとって非常に不自然で非iphonishに感じます。ただし、UIScrollViewを使用するたびに失敗した場合は、カスタムコード化されたスクロールビューにフォールバックできます。可能であれば、これに反対することをお勧めします。UIScrollViewを使用すると、将来のiPhone OSバージョンでどのように進化しても、ネイティブのルックアンドフィールを確実に取得できます。
さて、この小さなエッセイは少し長すぎました。数日前にScrollingMadnessで作業した後、私はまだUIScrollViewゲームに夢中です。
PSこれらのいずれかが機能し、共有したい場合は、関連するコードをandreyvit@gmail.comに電子メールで送信してください。ScrollingMadnessのトリックバッグに喜んで含めます。
PPSこの小さなエッセイをScrollingMadnessREADMEに追加します。
これはいつも私のために働きます...
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);
私のような怠惰な人々のために:
設定する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;
}
私はこの問題を解決するために次の方法を使用しました、それがあなたを助けることを願っています。
まず、コントローラーのヘッダーファイルで次の変数を定義します。
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;
}
}
- (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; }
私はここで重大な問題を抱えているかもしれませんが、同じ問題を解決しようとしていたときに、今日この投稿に出くわしました。これが私の解決策です、うまく機能しているようです。
画面一杯のコンテンツを表示したいのですが、ユーザーが他の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);
}
}
サブビューの高さをスクロールビューの高さとコンテンツサイズの高さと同じにするのと同じくらい簡単です。次に、垂直スクロールが無効になります。
これは、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
私もこの問題を解決する必要がありました。AndreyTarantsovの回答には、UIScrollViews
作業方法を理解するための有用な情報がたくさんありますが、解決策は少し複雑すぎると感じています。サブクラス化やタッチ転送を含まない私のソリューションは次のとおりです。
UIPanGestureRecognizers
。これも各方向に1つです。UIScrollViews
'panGestureRecognizerプロパティと、UIPanGestureRecognizer
UIScrollViewの目的のスクロール方向に垂直な方向に対応するダミーとの間に障害の依存関係を作成しますUIGestureRecognizer's
-requireGestureRecognizerToFail:
。-gestureRecognizerShouldBegin:
、あなたのダミーUIPanGestureRecognizersのためのコールバック、ジェスチャーの-translationInViewを使用して、パンの最初の方向を計算:セレクタを(あなたUIPanGestureRecognizers
あなたのタッチがパンとして登録するために十分に翻訳されるまで、このデリゲートのコールバックを呼び出すことはありません)。計算された方向に応じてジェスチャレコグナイザーの開始を許可または禁止します。これにより、垂直UIScrollViewパンジェスチャレコグナイザーの開始を許可するかどうかを制御できます。-gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
、ダミーUIPanGestureRecognizers
をスクロールと一緒に実行しないでください(追加したい追加の動作がない限り)。私が行ったのは、ビュー画面のサイズのフレームでページング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;
}
これは私にとって非常に不安定だった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;
}
将来の参考のために、私は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
}
}
}
お役に立てれば。
ドキュメントから:
「デフォルト値はNOです。これは、水平方向と垂直方向の両方でスクロールが許可されることを意味します。値がYESで、ユーザーが1つの一般的な方向(水平または垂直)にドラッグを開始すると、スクロールビューは他の方向へのスクロールを無効にします。 「」
重要なのは「ユーザーが一方向にドラッグし始めたら」だと思います。したがって、斜めにドラッグし始めても、これはうまくいきません。解釈に関しては、これらのドキュメントが常に信頼できるわけではありませんが、それはあなたが見ているものと一致しているようです。
これは実際には賢明なようです。私は尋ねなければなりません-なぜあなたはいつでも水平のみまたは垂直のみに制限したいのですか?おそらくUIScrollViewはあなたのためのツールではありませんか?
私のビューは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];
}
}
私にとっては魅力のように機能します!
およびで_isHorizontalScroll
NOにリセットする必要がtouchesEnded
ありtouchesCancelled
ます。
大きなスクロールビューがあり、斜めスクロールを制限したい場合http://chandanshetty01.blogspot.in/2012/07/restricting-diagonal-scrolling-in.html
@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に設定します。
それが新参者に役立つことを願っています!
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
}
}
インターフェイスビルダーを使用してインターフェイスを設計している場合、これは非常に簡単に実行できます。
Interface Builderで、UIScrollViewをクリックし、(インスペクターで)オプションを選択して一方向のスクロールのみに制限します。