Swipes(3)~マルチプルスワイプ(イベント処理メソッド)

2012. 03. 01
Swipes(1)~イベント処理メソッド』では、一本指によるシングルスワイプを処理していましたが、これを複数指によるマルチプルスワイプに対応させます。

旧来のタッチイベント処理メソッド(touchesBegan:withEvent:touchesMoved:withEvent:touchesEnded:withEvent:touchesCancelled:withEvent:)を使用するので、前回の『Swipes(2)~Gesture Recognizer』ではなく、コピーしていたオリジナルからの改造になります。

これは原著であるApress社のiOS 3.x版のサンプルコードでいうと『13 Multi-Swipes』に相当します。

iOS 4.x版のサンプルコードでは、Gesture Recognizerを使ったマルチプルスワイプの改造になりますので、Gesture Recognizer版のSwipesプロジェクトは別のフォルダなどにコピーして残しておくと便利です。


●SwipesViewController.hヘッダファイルの編集


ジェスチャがスワイプ以外、または水平か垂直スワイプかを示す識別子を宣言します。
(太字が追加した部分)

#define kMinimumGestureLength 25
#define kMaximumVariance 5

typedef enum {
    kNoSwipe = 0,
    kHorizontalSwipe,
    kVerticalSwipe
} SwipeType;


#import <UIKit/UIKit.h>

@interface SwipesViewController : UIViewController {
    UILabel *label;
    CGPoint gestureStartPoint;
}

@property (nonatomic, retain) IBOutlet UILabel *label;
@property CGPoint gestureStartPoint;

- (void)eraseText;

@end

7841

kNoSwipeスワイプ以外
kHorizontalSwipe水平スワイプ
kVerticalSwipe垂直スワイプ


●SwipesViewController.mソースファイルの編集

マルチプルスワイプに対応するため、touchesMoved:withEvent:メソッドを改修します。
(太字が追加修正した部分)

#import "SwipesViewController.h"

@implementation SwipesViewController

@synthesize label;
@synthesize gestureStartPoint;

- (void)eraseText {
    label.text = @"";
}

// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;

    self.label = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [label release];
    [super dealloc];
}

#pragma mark -

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    gestureStartPoint = [touch locationInView:self.view];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    SwipeType swipeType = kNoSwipe;

    for (UITouch *touch in touches) {
        CGPoint currentPosition = [touch locationInView:self.view];

        CGFloat deltaX = fabsf(currentPosition.x - gestureStartPoint.x);
        CGFloat deltaY = fabsf(currentPosition.y - gestureStartPoint.y);

        if (deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance)
            swipeType = kHorizontalSwipe;
        else if (deltaY >= kMinimumGestureLength && deltaX <= kMaximumVariance)
            swipeType = kVerticalSwipe;
    }

    BOOL allFingersFarEnoughAway = YES;

    if (swipeType != kNoSwipe) {
        for (UITouch *touch in touches) {
            CGPoint currentPosition = [touch locationInView:self.view];

            CGFloat distance;

            if (swipeType == kHorizontalSwipe)
                distance = fabsf(currentPosition.x - gestureStartPoint.x);
            else
                distance = fabsf(currentPosition.y - gestureStartPoint.y);

            if (distance < kMinimumGestureLength)
                allFingersFarEnoughAway = NO;
        }
    }

    if (allFingersFarEnoughAway && swipeType != kNoSwipe) {
        NSString *swipeCountString = nil;

        if ([touches count] == 2)
            swipeCountString = @"Double ";
        else if ([touches count] == 3)
            swipeCountString = @"Triple ";
        else if ([touches count] == 4)
            swipeCountString = @"Quadruple ";
        else
            swipeCountString = @"";

        NSString *swipeTypeString = (swipeType == kHorizontalSwipe) ?
            @"Horizontal" : @"Vertical";

        NSString *message = [[NSString alloc] initWithFormat:
            @"%@%@ Swipe Detected.", swipeCountString, swipeTypeString];

        label.text = message;
        [message release];

        [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
    }
}

@end

7842


1)touchesMoved:withEvent:メソッド

一本指のスワイプを検出する場合、touchesMoved:withEvent:メソッドでは、touchesBegan:withEvent:メソッドで取得したジェスチャの始点gestureStartPointと、移動した点currentPosition間でx座標とy座標の差分を取り、スワイプと認識される条件かどうかを判定するだけでした。

複数指のスワイプの場合はそれに加えて、
・始点と移動点間の距離の確認
・タッチポイント数の取得
を行うことでマルチプルスワイプに対応しています。

スワイプ識別子の初期化)

ヘッダファイルで追加したスワイプ識別子SwipeTypeの変数を、kNoSwipe(スワイプ以外)に初期化します。

スワイプの検出)

水平/垂直スワイプの検出方法は基本的に『Swipes(1)~イベント処理メソッドの場合』と同じですが、今回は複数のタッチを対象とするため、全ての移動点touchを高速列挙で確認しています。

タッチポイント間のマージン確保)

複数指でタッチされた場合、始点と移動点の単純な座標の比較ではジェスチャを誤認する場合があります。

例えば平行に揃えた二本指で水平スワイプを行うと、スワイプに必要な距離(今回の場合25ピクセル)を移動させなくても認識されることがあります。

始点となるタッチポイントの座標が(100, 100)で、もう一つのタッチポイント座標が(124, 100)とすると、ほんの1ピクセル右に移動させただけで水平スワイプと認識されてしまいます。

このようにタッチポイント間が近接している場合を排除するための処理を追加します。

allFingersFarEnoughAwayはタッチポイント間が十分離れているかを示すブール値で、初期値をYESと設定します。

ジェスチャが水平/垂直スワイプと認識された場合、スワイプ検出と同様に高速列挙で全ての移動点を対象に始点との差分を求めます。

水平スワイプの場合はx座標、垂直スワイプの場合はy座標の差分distanceが、スワイプと認識するのに必要な距離kMinimumGestureLengthより小さい時は、フラグallFingersFarEnoughAwayをNOにします。

つまり始点からのスワイプ方向に近接しているタッチポイントが存在する場合、フラグを立ててスワイプとは認めないようにしています。

タッチポイント数の検出とラベルへの出力)

タッチポイント数を示す文字列swipeCountStringの初期値をnilに設定します。

タッチポイント数はcountメソッドで取得し、数に応じて文字列swipeCountStringを設定します。
(初版では4本指まで設定していますが、iOS 3.x版のサンプルコードでは5本指『Quintuple』まで設定しています)

swipeTypeStringは水平か垂直かのスワイプの種類を示す文字列です。

後はタッチポイント数swipeCountStringとスワイプの種類swipeTypeStringを示す文字列をinitWithFormat:メソッドで組み合わせ、textプロパティでラベルに設定します。


●実行結果と問題点

実行すると下図のようにタッチポイント数とスワイプの種類が反映された文字列がラベルに表示されます。

7843

色々操作してみると気付くと思いますが、このコードにはいくつか問題があります。

まずスワイプの認識が『ある(1つの)始点と、いくつかの移動点の内のいずれかとの差分』で行われているという点です。

これによりコード自体は容易に実装できるものの、始点と移動点を一対一で比較しているわけではないので、極端な話、始点となるタッチポイントが水平移動していても、別のタッチポイントが始点に対する垂直スワイプの認識圏内に入れば垂直スワイプと判定される可能性があります。

そこまで極端ではなくとも、(本書では回避できるように書かれていますが)水平/垂直方向にピンチイン/アウトすれば、条件によってスワイプと認識されます。

更に、タッチポイント間のマージンを確保するコードは、スワイプ認識に必要な距離を誤認することがあります。

例えば始点座標が(100, 100)、別のタッチポイントの座標が(60, 60)に置かれたとし、両点共に右方向へ平行移動するとします。

水平スワイプと認識された時(始点座標が(125, 100))、別のタッチポイントの座標は(85, 60)となって始点からのマージン圏内に入ってしまうため、この時点ではラベルに反映されません。

反映されるのは別のタッチポイントがマージン圏外に出る座標(125, 60)となり、実際の認識に65ピクセルも必要になります。

(他のジェスチャも認識させるなど)どのような実装を行うかにもよりますが、精密に認識させようとする場合は『iOSイベント処理ガイド』の『複雑なマルチタッチシーケンスの処理(p.28)』にあるように、タッチオブジェクトのセットを辞書にキャッシュして、各タッチポイントの移動をトレースする工夫が必要になります。



参考文献

iOSイベント処理ガイド

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る






Bose Solo 5 TV sound system
0 Comments
Leave a comment
管理者にだけ表示を許可する
Top
0 Trackbacks
Top
Calendar
07 | 2017/08 | 09
Sun Mon Tue Wed Thu Fri Sat
- - 1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

Author:水月杏香
永遠の初心者プログラマ。

Wish List
WACOM


ARC
Technical Q&A
情報プロパティリストキー
Start Developing iOS Apps Today
BOSE

ボーズ・オンラインストア
Reference
NSApplicationDelegateプロトコル
NSArrayクラス
NSAutoreleasePoolクラス
NSBundleクラス
NSBundle UIKit追加分
NSCalendarクラス
NSCoderクラス
NSCodingプロトコル
NSCopyingプロトコル
NSDataクラス
NSDateクラス
NSDateFormatterクラス
NSDictionaryクラス
NSEntityDescriptionクラス
NSEnumeratorクラス
NSErrorクラス
NSExceptionクラス
NSFetchRequestクラス
NSFileHandleクラス
NSFileManagerクラス
NSIndexPathクラス
NSIndexPath UIKit追加分
NSKeyedArchiverクラス
NSKeyedUnarchiverクラス
NSKeyValueCodingプロトコル
NSLocaleクラス
NSManagedObjectクラス
NSManagedObjectContextクラス
NSManagedObjectModelクラス
NSMutableArrayクラス
NSMutableCopyingプロトコル
NSMutableDictionaryクラス
NSMutableSetクラス
NSNotificationクラス
NSNotificationCenterクラス
NSNullクラス
NSNumberクラス
NSObjectクラス
NSObject UIKit追加分
NSObjectプロトコル
NSPersistentStoreクラス
NSPersistentStoreCoordinatorクラス
NSPredicateクラス
NSPropertyListSerializationクラス
NSRunLoopクラス
NSSetクラス
NSStringクラス
NSString UIKit追加分
NSTimerクラス
NSTimeZoneクラス
NSURLクラス
NSURLProtectionSpaceクラス
NSURLRequestクラス
NSUserDefaultsクラス
NSValueクラス

UIActionSheetクラス
UIActionSheetDelegateプロトコル
UIActivityIndicatorViewクラス
UIAlertViewクラス
UIAlertViewDelegateプロトコル
UIApplicationクラス
UIApplicationDelegateプロトコル
UIBarButtonItemクラス
UIBarItemクラス
UIButtonクラス
UIColorクラス
UIControlクラス
UIDatePickerクラス
UIDeviceクラス
UIEventクラス
UIFontクラス
UIGestureRecognizerクラス
UIImageクラス
UIImageViewクラス
UIKit Function
UILabelクラス
UINavigationControllerクラス
UINavigationItemクラス
UIPickerViewクラス
UIPickerViewDataSourceプロトコル
UIPickerViewDelegateプロトコル
UIPinchGestureRecognizerクラス
UIResponderクラス
UIScreenクラス
UIScrollViewクラス
UISearchBarクラス
UISearchBarDelegateプロトコル
UISegmentedControlクラス
UISliderクラス
UISwipeGestureRecognizerクラス
UISwitchクラス
UITableViewクラス
UITableViewCellクラス
UITableViewControllerクラス
UITableViewDataSourceプロトコル
UITableViewDelegateプロトコル
UITapGestureRecognizerクラス
UITextFieldクラス
UITextFieldDelegateプロトコル
UITextInputTraitsプロトコル
UITextViewクラス
UITextViewDelegateプロトコル
UIToolbarクラス
UITouchクラス
UIViewクラス
UIViewControllerクラス
UIWebViewクラス
UIWebViewDelegateプロトコル
UIWindowクラス

AVAudioPlayerクラス
AVAudioPlayerDelegateプロトコル

CADisplayLinkクラス
CAEAGLLayerクラス
CALayerクラス

CGAffineTransform
CGBitmapContext
CGColor
CGColorSpace
CGContext
CGGeometry
CGImage
CGPath

EAGLContextクラス
EAGLDrawableプロトコル

Foundation Constants
Foundation Data Types
Foundation Functions

MPMediaItemクラス
MPMediaItemArtworkクラス
MPMediaPlaylistクラス
MPMediaPropertyPredicateクラス
MPMediaQueryクラス
MPMusicPlayerControllerクラス

Randomization Services

System Sound Services
Amazon


OpenGL ES
SQLite
Monthly Archives
Recent Comments
Recent TrackBacks
RSS Link
Visitors
QR Code
QR