メインビューがtouchesBegan
とtouchesMoved
の両方を受け入れるため、1本の指でタッチしてドラッグするアプリがあります。 UIScrollView
を実装したいのですが、機能していますが、ドラッグをオーバーライドするため、contentViewがドラッグを受け取りません。 UIScrollview
を実装したいのですが、2本指のドラッグがスクロールを示し、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];
}
UIScrollViewをサブクラス化する必要があります(もちろん!)。次に、次のことを行う必要があります。
1本指のイベントを作成してコンテンツビューに移動する(簡単)
2本指のイベントでスクロールビューをスクロールさせます(簡単かもしれませんが、難しいかもしれませんが、不可能かもしれません)。
Patrickの提案は、通常は問題ありません。UIScrollViewサブクラスにコンテンツビューを通知し、タッチイベントハンドラーで指の数を確認して、それに応じてイベントを転送します。 (1)コンテンツビューに送信するイベントがレスポンダーチェーンを通じてUIScrollViewにバブリングしないことを確認してください(つまり、すべてを処理するようにしてください)、(2)通常のタッチイベントのフロー(つまりtouchesBegan、いくつかの{touchesBegan、touchesMoved、touchesEnded}、touchesEndedまたはtouchesCancelledで終了)、特にUIScrollViewを処理する場合。 #2は注意が必要です。
イベントがUIScrollViewに対するものであると判断した場合、もう1つのトリックは、UIScrollViewに2本指ジェスチャーが実際には1本指ジェスチャーであると信じ込ませることです(UIScrollViewは2本の指でスクロールできないため)。 1つの指のデータのみをsuperに渡してみてください((NSSet *)touches
引数—変更されたタッチのみが含まれることに注意してください—間違った指のイベントを完全に無視します)。
それがうまくいかない場合、あなたは困っています。理論的には、UITouchに似たクラスを作成することで、UIScrollViewにフィードする人工的なタッチを作成することができます。基礎となるCコードは型をチェックしないため、(YourTouch *)を(UITouch *)にキャストしても機能し、UIScrollViewをだまして、実際には発生しなかったタッチを処理させることができます。
あなたはおそらく読みたいと思います 高度なUIScrollViewトリックに関する私の記事 (そしてそこにまったく無関係のUIScrollViewサンプルコードをいくつか参照してください)。
もちろん、それを機能させることができない場合は、UIScrollViewの動きを手動で制御するか、完全にカスタム作成されたスクロールビューを使用するオプションが常にあります。 Three20ライブラリ ;にはTTScrollViewクラスがあります。それはユーザーにとっては気分が良くありませんが、プログラマにとっては気分が良いです。
SDK 3.2では、UIScrollViewのタッチ処理はジェスチャー認識機能を使用して処理されます。
デフォルトの1本指パンニングの代わりに2本指パンニングを実行する場合は、次のコードを使用できます。
for (UIGestureRecognizer *gestureRecognizer in scrollView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
}
}
IOS 5以降の場合、このプロパティを設定すると、Mike Laurenceの回答と同じ効果があります。
self.scrollView.panGestureRecognizer.minimumNumberOfTouches = 2;
1本の指のドラッグはpanGestureRecognizerによって無視されるため、1本の指のドラッグイベントがコンテンツビューに渡されます。
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];
他のすべての回答とコメントを読むことによってのみ正しい回答を見つけることができるため、この回答は混乱します(最も近い回答は質問を逆に取得しました)。受け入れられた回答は曖昧すぎて役に立たず、別の方法を提案しています。
合成、これは機能します
// 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.gestureRecognizers
とmyScrollView.gestureRecognizers
そして、あなたは行ってもいいです。
私が追加した唯一のことは、醜いキャストを避けるためにid
を使用することです:)
これは機能しますが、UIScrollViewでズームを実行したい場合は、かなり厄介になる可能性があります...ピンチツーズームとスクロールでそれがうまくいかないため、ジェスチャーが正しく機能しません。適切な答えを見つけてください。
上記のコードをさらに改善しました。問題は、setCanCancelContentTouches:NO
を設定した後でも、ズームジェスチャがコンテンツで中断するという問題がありました。コンテンツのタッチはキャンセルされませんが、その間ズームできます。これを防ぐために、minimumZoomScaleとmaximumZoomScaleを毎回同じ値に設定してズームをロックすると、タイマーが起動します。
非常に奇妙な動作は、許可された時間内に2本の指のジェスチャーによって1本の指のイベントがキャンセルされると、タイマーが遅延することです。 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
</ code>
私たちは、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から機能します
悪い知らせ:iPhone SDK 3.0以降、-touchesBegan:
および-touchesEnded:
** UIScrollview
** subclassメソッドにタッチを渡さないでください。同じではないtouchesShouldBegin
メソッドとtouchesShouldCancelInContentView
メソッドを使用できます。
本当にこのタッチを取得したい場合は、これを許可するhackを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
スーパークラスへのタッチをキャンセルすることはできません。
これをviewDidLoadメソッドに配置すると、2つのタッチパン動作を処理するスクロールビューと、1つのタッチパン動作を処理する別のパンジェスチャーハンドラーが完成します->
scrollView.panGestureRecognizer.minimumNumberOfTouches = 2
let panGR = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handlePan(_:)))
panGR.minimumNumberOfTouches = 1
panGR.maximumNumberOfTouches = 1
scrollView.gestureRecognizers?.append(panGR)
そして、ViewControllerに接続された関数であるhandlePanメソッドには、メソッドが入力されていることを確認するためのprintステートメントがあります->
@IBAction func handlePan(_ sender: UIPanGestureRecognizer) {
print("Entered handlePan numberOfTuoches: \(sender.numberOfTouches)")
}
HTH
私がしていることは、ビューコントローラーにスクロールビューをセットアップさせることです:
[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:
メッセージ。
これは、インターネット上のこの質問に最適なリソースのようです。別の近い解決策 ここで見つけることができます 。
この問題を別の方法で非常に満足できる方法で解決しました。本質的には、自分のジェスチャー認識機能を数式に置き換えることです。元の投稿者が要求した効果を達成しようとしている人は、UIScrollView
の積極的なサブクラス化よりもこの代替案を検討することを強くお勧めします。
次のプロセスが提供されます:
カスタムビューを含むUIScrollView
2本の指でズームおよびパン(UIPinchGestureRecognizer
を使用)
他のすべてのタッチに対するビューのイベント処理
まず、ビューコントローラとそのビューがあるとしましょう。 IBで、ビューをscrollViewのサブビューにして、サイズ変更しないようにビューのサイズ変更ルールを調整します。スクロールビューの属性で、「バウンス」と言うものをすべてオンにし、off「delaysContentTouches
」をオンにします。また、ズームの最小値と最大値をデフォルトの1.0以外に設定する必要があります。これは、Appleのドキュメントにあるように、ズームが機能するために必要です。
UIScrollView
のカスタムサブクラスを作成し、このスクロールビューをそのカスタムサブクラスにします。 scrollviewのビューコントローラにアウトレットを追加し、それらを接続します。これで完全に構成されました。
次のコードを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];
}
}
このメソッドには、ビューコントローラーのクラスファイルで定義する必要があるいくつかのプロパティへの参照があることに注意してください。
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のズーム動作でこれを見たことがあって、いい感じだと思った。
ケンシ の回答Swift 4
for gestureRecognizer: UIGestureRecognizer in self.gestureRecognizers! {
if (gestureRecognizer is UIPanGestureRecognizer) {
let panGR = gestureRecognizer as? UIPanGestureRecognizer
panGR?.minimumNumberOfTouches = 2
}
}
私のチェックアウト ソリューション :
#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本の指でタッチしても停止しません。それでも、タイマーを使用して、開始したばかりのワンタッチイベントをキャンセルできます。
はい、タッチを「上」に渡すには、UIScrollView
をサブクラス化し、その-touchesBegan:
および-touchesEnded:
メソッドをオーバーライドする必要があります。これには、UIView
メンバー変数を持つサブクラスも含まれるため、タッチを渡すことの意味を知ることができます。