42

touchesBeganメインビューがとの両方を受け入れるアプリがあるtouchesMovedため、指一本でタッチしてドラッグします。を実装したいのですがUIScrollView、それを機能させていますが、ドラッグをオーバーライドするため、contentViewがドラッグを受信することはありません。UIScrollview2本の指でドラッグするとスクロールを示し、1本の指でドラッグするイベントがコンテンツビューに渡されるので、正常に実行されるように実装したいと思います。独自のサブクラスを作成する必要がありますUIScrollViewか?

appDelegateこれが、を実装した場所からのコードUIScrollViewです。

@implementation MusicGridAppDelegate

@synthesize window;
@synthesize viewController;
@synthesize scrollView;


- (void)applicationDidFinishLaunching:(UIApplication *)application {    

    // Override point for customization after app launch    
    //[application setStatusBarHidden:YES animated:NO];
    //[window addSubview:viewController.view];

    scrollView.contentSize = CGSizeMake(720, 480);
    scrollView.showsHorizontalScrollIndicator = YES;
    scrollView.showsVerticalScrollIndicator = YES;
    scrollView.delegate = self;
    [scrollView addSubview:viewController.view];
    [window makeKeyAndVisible];
}


- (void)dealloc {
    [viewController release];
    [scrollView release];
    [window release];
    [super dealloc];
}
4

14 に答える 14

65

SDK 3.2 では、UIScrollView のタッチ処理は Gesture Recognizer を使用して処理されます。

デフォルトの 1 本指のパンの代わりに 2 本指のパンを実行する場合は、次のコードを使用できます。

for (UIGestureRecognizer *gestureRecognizer in scrollView.gestureRecognizers) {     
    if ([gestureRecognizer  isKindOfClass:[UIPanGestureRecognizer class]]) {
        UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
        panGR.minimumNumberOfTouches = 2;               
    }
}
于 2010-07-04T00:12:48.933 に答える
45

iOS 5 以降の場合、このプロパティを設定すると、Mike Laurence による回答と同じ効果があります。

self.scrollView.panGestureRecognizer.minimumNumberOfTouches = 2;

1 本指のドラッグは panGestureRecognizer によって無視されるため、1 本指のドラッグ イベントがコンテンツ ビューに渡されます。

于 2013-08-04T02:24:49.637 に答える
14

iOS 3.2 以降では、2 本指スクロールが非常に簡単に実現できるようになりました。スクロール ビューにパン ジェスチャ レコグナイザーを追加し、その maximumNumberOfTouches を 1 に設定するだけです。1 本指のスクロールはすべて要求されますが、2 本以上の指でのスクロールはチェーンをスクロール ビューの組み込みのパン ジェスチャ レコグナイザーに渡すことができます (したがって、通常のスクロール動作を許可します)。

UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(recognizePan:)];
panGestureRecognizer.maximumNumberOfTouches = 1;
[scrollView addGestureRecognizer:panGestureRecognizer];
[panGestureRecognizer release];
于 2010-09-25T05:08:06.460 に答える
10

UIScrollViewをサブクラス化する必要があります(もちろん!)。次に、次のことを行う必要があります。

  • 1本の指でイベントを作成してコンテンツビューに移動し(簡単)、

  • 2本の指のイベントでスクロールビューをスクロールします(簡単な場合もあれば、難しい場合もありますが、不可能な場合もあります)。

Patrickの提案は一般的に問題ありません。UIScrollViewサブクラスにコンテンツビューについて知らせてから、タッチイベントハンドラーで指の数を確認し、それに応じてイベントを転送します。(1)コンテンツビューに送信するイベントがレスポンダーチェーンを介してUIScrollViewにバブルバックしないこと(つまり、すべてを処理することを確認すること)、(2)タッチイベントの通常のフロー(つまり、touchesBeganより特にUIScrollViewを処理する場合、いくつかの{touchesBegan、touchesMoved、touchesEnded}、touchesEndedまたはtouchesCancelledで終了します。#2は注意が必要です。

イベントがUIScrollView用であると判断した場合、別のトリックは、UIScrollViewに2本の指のジェスチャが実際には1本の指のジェスチャであると認識させることです(UIScrollViewは2本の指でスクロールできないため)。1本の指のデータのみをsuperに渡してみてください((NSSet *)touches引数をフィルタリングし(変更されたタッチのみが含まれていることに注意してください)、間違った指のイベントを完全に無視します)。

それがうまくいかない場合は、問題が発生しています。理論的には、UITouchに似たクラスを作成することで、UIScrollViewにフィードするための人工的なタッチを作成することができます。基になるCコードは型をチェックしないため、(YourTouch *)を(UITouch *)にキャストすると機能し、UIScrollViewをだまして実際には発生しなかったタッチを処理できるようになります。

おそらく、高度なUIScrollViewのトリックに関する私の記事を読みたいと思うでしょう(そして、そこにまったく関係のないUIScrollViewサンプルコードがいくつかあります)。

もちろん、それを機能させることができない場合は、UIScrollViewの動きを手動で制御するか、完全にカスタム作成されたスクロールビューを使用するかのいずれかのオプションが常にあります。Three20ライブラリにはTTScrollViewクラスがあります。ユーザーにとっては気分が良くありませんが、プログラマーにとっては気持ちがいいです。

于 2009-05-10T06:02:20.980 に答える
8

他のすべての回答とコメントを読むことによってのみ正しい回答を見つけることができるため、この回答は混乱しています(最も近い回答は質問を逆にしました)。受け入れられた答えは漠然としていて役に立たないため、別の方法を提案しています。

合成、これは機能します

    // makes it so that only two finger scrolls go
    for (id gestureRecognizer in self.gestureRecognizers) {     
        if ([gestureRecognizer  isKindOfClass:[UIPanGestureRecognizer class]])
        {
            UIPanGestureRecognizer *panGR = gestureRecognizer;
            panGR.minimumNumberOfTouches = 2;              
            panGR.maximumNumberOfTouches = 2;
        }
    }   

これには、スクロールに 2 本の指が必要です。私はこれをサブクラスで行いましたが、そうでない場合は、に置き換えself.gestureRecognizersmyScrollView.gestureRecognizersください。

私が追加した唯一のことはid、醜いキャストを避けるために使用することです:)

これは機能しますが、UIScrollView にもズームを実行させたい場合は、かなり面倒になる可能性があります。適切な答えが見つかったら、これを更新します。

于 2011-03-09T14:00:42.800 に答える
2

上記のコードをさらに改善しました。問題は、設定した後でもsetCanCancelContentTouches:NO 、ズームジェスチャがコンテンツを中断するという問題がありました。コンテンツタッチはキャンセルされませんが、その間はズームできます。これを防ぐために、minimumZoomScaleとmaximumZoomScaleを毎回同じ値に設定してズームをロックすると、タイマーが起動します。

非常に奇妙な動作は、許可された時間内に1本の指のイベントが2本の指のジェスチャによってキャンセルされると、タイマーが遅延することです。touchCanceledイベントが呼び出された後に発生します。そのため、イベントがすでにキャンセルされているにもかかわらずズームをロックしようとするため、次のイベントのズームを無効にするという問題があります。この動作を処理するために、タイマーコールバックメソッドは、touchesCanceledが以前に呼び出されたかどうかをチェックします。 @implementation JWTwoFingerScrollView

#pragma mark -
#pragma mark Event Passing


- (id)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        for (UIGestureRecognizer* r in self.gestureRecognizers) {
            if ([r isKindOfClass:[UIPanGestureRecognizer class]]) {
                [((UIPanGestureRecognizer*)r) setMaximumNumberOfTouches:2];
                [((UIPanGestureRecognizer*)r) setMinimumNumberOfTouches:2];
                zoomScale[0] = -1.0;
                zoomScale[1] = -1.0;
            }
            timerWasDelayed = NO;
        }
    }
    return self;
}
-(void)lockZoomScale {    
    zoomScale[0] = self.minimumZoomScale;
    zoomScale[1] = self.maximumZoomScale;
    [self setMinimumZoomScale:self.zoomScale];
    [self setMaximumZoomScale:self.zoomScale];
        NSLog(@"locked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale);
}
-(void)unlockZoomScale {
    if (zoomScale[0] != -1 && zoomScale[1] != -1) {
        [self setMinimumZoomScale:zoomScale[0]];
        [self setMaximumZoomScale:zoomScale[1]];
        zoomScale[0] = -1.0;
        zoomScale[1] = -1.0;
        NSLog(@"unlocked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale);
    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"began %i",[event allTouches].count);
    [self setCanCancelContentTouches:YES];
     if ([event allTouches].count == 1){
         touchesBeganTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO];
         [touchesBeganTimer retain];
         [touchFilter touchesBegan:touches withEvent:event];
     }
 }

//if one finger touch gets canceled by two finger touch, this timer gets delayed
// so we can! use this method to disable zooming, because it doesnt get called when two finger touch events are wanted; otherwise we would disable zooming while zooming
-(void)firstTouchTimerFired:(NSTimer*)timer {
    NSLog(@"fired");
    [self setCanCancelContentTouches:NO];
    //if already locked: unlock
    //this happens because two finger gesture delays timer until touch event finishes.. then we dont want to lock!
    if (timerWasDelayed) {
        [self unlockZoomScale];
    }
    else {
        [self lockZoomScale];
    }
    timerWasDelayed = NO;
 }

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
//    NSLog(@"moved %i",[event allTouches].count);
    [touchFilter touchesMoved:touches withEvent:event];
}

 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"ended %i",[event allTouches].count);
    [touchFilter touchesEnded:touches withEvent:event];
    [self unlockZoomScale];
 }

 //[self setCanCancelContentTouches:NO];
 -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"canceled %i",[event allTouches].count);
    [touchFilter touchesCancelled:touches withEvent:event];
    [self unlockZoomScale];
     timerWasDelayed = YES;
 }

@end

于 2011-12-28T00:27:14.240 に答える
2

UIScrollView をサブクラス化し、シンプルで失礼な方法でタッチ数に応じてイベントをフィルタリングすることで、iPhone 描画アプリに同様の機能を実装することができました。

//OCRScroller.h
@interface OCRUIScrollView: UIScrollView
{
    double pass2scroller;
}
@end

//OCRScroller.mm
@implementation OCRUIScrollView
- (id)initWithFrame:(CGRect)aRect {
    pass2scroller = 0;
    UIScrollView* newv = [super initWithFrame:aRect];
    return newv;
}
- (void)setupPassOnEvent:(UIEvent *)event {
    int touch_cnt = [[event allTouches] count];
    if(touch_cnt<=1){
        pass2scroller = 0;
    }else{
        double timems = double(CACurrentMediaTime()*1000);
        pass2scroller = timems+200;
    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self setupPassOnEvent:event];
    [super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [self setupPassOnEvent:event];
    [super touchesMoved:touches withEvent:event];   
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    pass2scroller = 0;
    [super touchesEnded:touches withEvent:event];
}


- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
{
    return YES;
}

- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
    double timems = double(CACurrentMediaTime()*1000);
    if (pass2scroller == 0 || timems> pass2scroller){
        return NO;
    }
    return YES;
}
@end

ScrollView は次のようにセットアップされます。

scroll_view = [[OCRUIScrollView alloc] initWithFrame:rect];
scroll_view.contentSize = img_size;
scroll_view.contentOffset = CGPointMake(0,0);
scroll_view.canCancelContentTouches = YES;
scroll_view.delaysContentTouches = NO;
scroll_view.scrollEnabled = YES;
scroll_view.bounces = NO;
scroll_view.bouncesZoom = YES;
scroll_view.maximumZoomScale = 10.0f;
scroll_view.minimumZoomScale = 0.1f;
scroll_view.delegate = self;
self.view = scroll_view;

単純なタップは何もしません (必要な方法で処理できます)。2 本の指でタップすると、期待どおりにビューがスクロール/ズームされます。GestureRecognizer を使用しないため、iOS 3.1 から動作します。

于 2011-03-10T09:29:36.297 に答える
1

私がしていることは、View Controllerにスクロールビューを設定させることです:

[scrollView setCanCancelContentTouches:NO];
[scrollView setDelaysContentTouches:NO];

そして、私の子ビューでは、2 本指のタッチは通常、1 本の指の後に 2 本の指がすぐに続くので、タイマーがあります。

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // Hand tool or two or more touches means a pan or zoom gesture.
    if ((selectedTool == kHandToolIndex) || (event.allTouches.count > 1)) {
        [[self parentScrollView] setCanCancelContentTouches:YES];
        [firstTouchTimer invalidate];
        firstTouchTimer = nil;
        return;
    }

    // Use a timer to delay first touch because two-finger touches usually start with one touch followed by a second touch.
    [[self parentScrollView] setCanCancelContentTouches:NO];
    anchorPoint = [[touches anyObject] locationInView:self];
    firstTouchTimer = [NSTimer scheduledTimerWithTimeInterval:kFirstTouchTimeInterval target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO];
    firstTouchTimeStamp = event.timestamp;
}

2 つ目の touchesBegan: イベントが複数の指で発生した場合、スクロール ビューはタッチをキャンセルできます。したがって、ユーザーが 2 本の指でパンすると、このビューにtouchesCanceled:メッセージが表示されます。

于 2010-01-12T06:05:44.467 に答える
1

-touchesBegan:悪いニュース: iPhone SDK 3.0 以降では、タッチをand - touchesEnded:** UIScrollview** サブクラス メソッドに渡さないでください。同じではない メソッドtouchesShouldBeginとメソッドを使用できます。touchesShouldCancelInContentView

本当にこのタッチを取得したい場合は、これを可能にするハックを 1 つ用意してください。

サブクラスのUIScrollViewオーバーライドでは、次のhitTestようなメソッドを使用します。

- (UIView *)hitTest:(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;
}

UIScrollViewこれにより、このタッチがサブクラスに渡されますが、タッチをスーパー クラスにキャンセルすることはできません。

于 2009-11-03T16:48:59.077 に答える
1

これは、インターネット上でこの質問に最適なリソースのようです。別の密接な解決策は、ここで見つけることができます

私はこの問題を別の方法で非常に満足のいく方法で解決しました。本質的には、私自身のジェスチャ認識機能を方程式に置き換えることによってです。元の投稿者が要求した効果を達成しようとしている人は誰でも、UIScrollView.

次のプロセスにより、以下が提供されます。

  • UIScrollViewカスタム ビューを含む

  • 2 本の指でズームとパン ( 経由UIPinchGestureRecognizer)

  • 他のすべてのタッチに対するビューのイベント処理

まず、View Controller とそのビューがあるとします。IB では、ビューを scrollView のサブビューにし、サイズが変更されないようにビューのサイズ変更ルールを調整します。スクロールビューの属性で、「バウンス」と表示されているものをすべてオンにし、「 」をオフdelaysContentTouchesにします。また、ズームの最小値と最大値をデフォルトの 1.0 以外に設定する必要があります。これは、Apple のドキュメントにあるように、ズームが機能するために必要です。

のカスタム サブクラスを作成し、UIScrollViewこのスクロール ビューをそのカスタム サブクラスにします。スクロールビュー用のビュー コントローラーにアウトレットを追加し、それらを接続します。これで完全に構成されました。

次のコードをUIScrollViewサブクラスに追加して、タッチ イベントを透過的に渡す必要があります (サブクラスを完全にバイパスすることで、よりエレガントに実行できると思います)。

#pragma mark -
#pragma mark Event Passing

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.nextResponder touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.nextResponder touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.nextResponder touchesEnded:touches withEvent:event];
}
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
    return NO;
}

次のコードをビュー コントローラーに追加します。

- (void)setupGestures {
    UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    [self.view addGestureRecognizer:pinchGesture];
    [pinchGesture release];
}

- (IBAction)handlePinchGesture:(UIPinchGestureRecognizer *)sender {
if ( sender.state == UIGestureRecognizerStateBegan ) {
    //Hold values
    previousLocation = [sender locationInView:self.view];
    previousOffset = self.scrollView.contentOffset;
    previousScale = self.scrollView.zoomScale;
} else if ( sender.state == UIGestureRecognizerStateChanged ) {
    //Zoom
    [self.scrollView setZoomScale:previousScale*sender.scale animated:NO];

    //Move
    location = [sender locationInView:self.view];
    CGPoint offset = CGPointMake(previousOffset.x+(previousLocation.x-location.x), previousOffset.y+(previousLocation.y-location.y));
    [self.scrollView setContentOffset:offset animated:NO];  
} else {
    if ( previousScale*sender.scale < 1.15 && previousScale*sender.scale > .85 )
        [self.scrollView setZoomScale:1.0 animated:YES];
}

}

このメソッドには、View Controller のクラス ファイルで定義する必要がある多くのプロパティへの参照があることに注意してください。

  • CGFloat previousScale;
  • CGPoint previousOffset;
  • CGPoint previousLocation;
  • CGPoint location;

わかりました!

残念ながら、ジェスチャー中に scrollView にスクローラーを表示させることができませんでした。私はこれらの戦略をすべて試しました:

//Scroll indicators
self.scrollView.showsVerticalScrollIndicator = YES;
self.scrollView.showsVerticalScrollIndicator = YES;
[self.scrollView flashScrollIndicators];
[self.scrollView setNeedsDisplay];

私が本当に楽しんだことの 1 つは、最後の行を見ると、約 100% の最終的なズームを取得し、それに合わせて丸めていることに気付くでしょう。許容レベルを調整できます。これは Pages のズーム動作で見たことがあり、いい感じだと思いました。

于 2010-05-25T21:06:27.127 に答える
0

私のソリューションをチェックしてください:

#import “JWTwoFingerScrollView.h”

@implementation JWTwoFingerScrollView

- (id)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        for (UIGestureRecognizer* r in self.gestureRecognizers) {
            NSLog(@“%@”,[r class]);
            if ([r isKindOfClass:[UIPanGestureRecognizer class]]) {
                [((UIPanGestureRecognizer*)r) setMaximumNumberOfTouches:2];
                [((UIPanGestureRecognizer*)r) setMinimumNumberOfTouches:2];
            }
        }
    }
    return self;
}

-(void)firstTouchTimerFired:(NSTimer*)timer {
    [self setCanCancelContentTouches:NO];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self setCanCancelContentTouches:YES];
    if ([event allTouches].count == 1){
        touchesBeganTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(firstTouchTimerFired:) userInfo: nil repeats:NO];
        [touchesBeganTimer retain];
        [touchFilter touchesBegan:touches withEvent:event];
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [touchFilter touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@“ended %i”,[event allTouches].count);
    [touchFilter touchesEnded:touches withEvent:event];
}

-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@“canceled %i”,[event allTouches].count);
    [touchFilter touchesCancelled:touches withEvent:event];
}

@end

最初のタッチを遅らせず、ユーザーが 1 本の指を使用した後に 2 本の指でタッチしても停止しません。それでも、タイマーを使用して開始したばかりのワンタッチ イベントをキャンセルすることができます。

于 2011-12-15T16:39:41.727 に答える
0

はい、タッチを「上」に渡すには、そのメソッドとメソッドをサブクラス化UIScrollViewしてオーバーライドする必要があります。これにはおそらく、メンバー変数を持つサブクラスも含まれるため、タッチを何に渡すかを知ることができます。touchesBegan:-touchesEnded:UIView

于 2009-04-24T21:47:09.703 に答える