PinchMe(1)~イベント処理メソッド

2012. 03. 07
PinchMeはピンチイン/アウトを検出してラベルに表示します。

7867


●プロジェクトの作成

View-based Applicationテンプレートで、プロジェクト名を『PinchMe』とします。

7815

7868


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

ピンチを認識するための定数と認識したピンチを表示するラベルのアウトレット、ジェスチャ開始時の2つのタッチポイントの間隔を保持するプロパティ、そしてラベルを表示してから一定時間後にラベルを消去するメソッドの宣言を行います。
(太字が追加した部分)

#import <UIKit/UIKit.h>

#define kMinimumPinchDelta 100


@interface PinchMeViewController : UIViewController {
    UILabel *label;
    CGFloat initialDistance;

}

@property (nonatomic, retain) IBOutlet UILabel *label;
@property CGFloat initialDistance;

- (void)eraseLabel;


@end

7869


1)定数

定数kMinimumPinchDeltaは、ジェスチャをピンチとして認識するのに必要な移動距離を示すもので、ここでは100ピクセルに設定します。


2)アウトレット

ピンチを検出した際に表示するラベルlabelと、ジェスチャ開始時の2つのタッチポイントの間隔initialDistanceのインスタンス変数とプロパティをアウトレットとして宣言します。


3)消去メソッド


各ラベルを表示から一定時間後に消去するメソッドeraseLabel:を宣言します。


●PinchMeViewController.xibの編集

PinchMeViewController.hヘッダファイルの変更が終わったら保存し、ビューコントローラのnibファイルPinchMeViewController.xibを開いてラベルの追加を行います。


1)ラベルの追加

LibraryウィンドウのLabelを選択し、Viewウィンドウの上方にラベルを1つ設置します。

7870


2)ラベルへのアウトレットの接続

DocumentウィンドウでFile's Ownerを選択し、InspectorウィンドウでConnectionsタブを開き、OutletsのラベルlabelをViewウィンドウのラベルに接続します。

7871


3)ラベルのサイズ変更

Documentウィンドウで各ラベルを選択し、InspectorウィンドウでSizeタブを開き、X:20、Y:32、W:280、H:21とサイズを変更します。


4)ラベルの属性変更

InspectorウィンドウのAttributesタブを開き、属性の変更を行います。

Label)

・Text
予め入っている文字列『Label』を削除します。
これにより初期状態では何も表示されないことになります。

・Layout:Alignment
文字列のレイアウトを左寄せから中央揃えに変更します。

View)

・Mode
サンプルコードではLeftではなくScale To Fillになっていますが、今回は関係無いのでそのままにしています。

7872


5)ビューの属性変更

DocumentウィンドウでViewを選択し、InspectorウィンドウでAttributesタブを開き、ViewのInteractionの『Multiple Touch』にチェックを入れます。

7852

全ての変更が済んだらnibファイルを保存します。


●CGPointUtilsクラスの追加

原著であるApress社のiOS 3.x版のサンプルコードの『13 PinchMe』のClassesフォルダ下にあるCGPointUtils.hとCGPointUtils.cファイルを自身のPinchMeプロジェクトのClassesフォルダにコピーし、プロジェクトに追加します。

CGPointUtilsクラスでは3つの関数が実装されていますが、PinchMeプロジェクトで使用するのはdistanceBetweenPoints関数のみです。


1)CGPointUtils.hヘッダファイル

#import <CoreGraphics/CoreGraphics.h>

CGFloat distanceBetweenPoints (CGPoint first, CGPoint second);
CGFloat angleBetweenPoints(CGPoint first, CGPoint second);
CGFloat angleBetweenLines(CGPoint line1Start, CGPoint line1End, CGPoint line2Start, CGPoint lin2End);

7873

CGPointUtilsクラスは、以下の3つの数値を求める関数が記述されているクラスで、ヘッダファイルでは関数の宣言を行っています。

・2点間の距離(distanceBetweenPoints)
・2点間の角度(angleBetweenPoints)
・2線分間の角度(angleBetweenLines)


2)CGPointUtils.cソースファイル

#include "CGPointUtils.h"
#include <math.h>

#define degreesToRadian(x) (M_PI * x / 180.0)
#define radiansToDegrees(x) (180.0 * x / M_PI)

CGFloat distanceBetweenPoints (CGPoint first, CGPoint second) {
    CGFloat deltaX = second.x - first.x;
    CGFloat deltaY = second.y - first.y;
    return sqrt(deltaX*deltaX + deltaY*deltaY );
};

CGFloat angleBetweenPoints(CGPoint first, CGPoint second) {
    CGFloat height = second.y - first.y;
    CGFloat width = first.x - second.x;
    CGFloat rads = atan(height/width);
    return radiansToDegrees(rads);
    //degs = degrees(atan((top - bottom)/(right - left)))
}

CGFloat angleBetweenLines(CGPoint line1Start, CGPoint line1End, CGPoint line2Start, CGPoint line2End) {
    CGFloat a = line1End.x - line1Start.x;
    CGFloat b = line1End.y - line1Start.y;
    CGFloat c = line2End.x - line2Start.x;
    CGFloat d = line2End.y - line2Start.y;

    CGFloat rads = acos(((a*c) + (b*d)) / ((sqrt(a*a + b*b)) * (sqrt(c*c + d*d))));

    return radiansToDegrees(rads);
}

7874


1)distanceBetweenPoints関数

distanceBetweenPoints関数は、引数で渡されるfirstとsecondの2点間の距離を求める関数です。

これは三平方の定理(ピタゴラスの定理)、

 (斜辺の2乗)=(底辺の2乗)+(高さの2乗)

を使用しており、2点間のx座標の差分deltaXが直角三角形の底辺、y座標の差分deltaYが直角三角形の高さ、斜辺が2点間の距離に相当します。
(『Wikipedia/ピタゴラスの定理』参照)


2)angleBetweenPoints関数

angleBetweenPoints関数は、引数で渡されるfirstとsecondの2点間の角度を求める関数です。

これは逆正接(アークタンジェント。atan)を使用しており、2点間のx座標の差widthが直角三角形の底辺、y座標の差heightを直角三角形の高さとすると、

 (2点間の角度)= atan(高さ/底辺)

で求められます。
(『Wikipedia/三角関数』参照)

使用するにあたり、注意点が二つあります。

一つは、戻り値は点firstから点secondへのベクトルの角度ですが、変数の型にCGGeometry構造体を使用しているものの、UIKit座標系(左上原点)を前提としているということです。
(底辺widthは first.x - second.x で求めているのに対し高さheightは second.y - first.y で求めている)

したがって点firstの方が原点に近く、点secondがxとyともに正方向(つまり右下向きのベクトル)の場合、角度は負になります。

もう一つは戻り値がラジアンではなく度(degree)で表されているということです。


3)angleBetweenLines関数

angleBetweenLines関数は、引数で渡されるline1Startとline1Endを結ぶ線分と、line2Startとline2Endを結ぶ線分の、2つの線分間の角度を求める関数です。

これはベクトルの内積を利用しており、点line1Startから点line1Endへのベクトルをline1、点line2Startから点line2Endへのベクトルをline2とすると、

 line1・line2 = line1x * line2x + line1y * line2y = | line1 | | line2 | cos(line1とline2間の角度)

という式で表されるので、コードでは逆余弦(アークコサイン。acos)で角度を求めています。
(『KIT数学ナビゲーション/ベクトル/内積』『MaTのパソコンのページ/アルゴリズム解説/線分の長さと角度』参照)

angleBetweenPoints関数と同様に、UIKit座標系であることと、戻り値が度であることに注意してください。


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

CGPointUtils.hヘッダファイルのインポートと、PinchMeViewController.hヘッダファイルで宣言した2つのプロパティとラベルを消去するeraseLabelメソッドの実装と、3つのタッチイベント処理メソッドを追加します。
(太字が追加した部分)

#import "PinchMeViewController.h"
#import "CGPointUtils.h"

@implementation PinchMeViewController

@synthesize label;
@synthesize initialDistance;

- (void)eraseLabel {
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 {
    if ([touches count] == 2) {
        NSArray *twoTouches = [touches allObjects];
        UITouch *first = [twoTouches objectAtIndex:0];
        UITouch *second = [twoTouches objectAtIndex:1];
        initialDistance = distanceBetweenPoints([first locationInView:self.view],
            [second locationInView:self.view]);
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if ([touches count] == 2) {
        NSArray *twoTouches = [touches allObjects];
        UITouch *first = [twoTouches objectAtIndex:0];
        UITouch *second = [twoTouches objectAtIndex:1];
        CGFloat currentDistance = distanceBetweenPoints([first locationInView:self.view],
            [second locationInView:self.view]);

        if (initialDistance == 0)
            initialDistance = currentDistance;
        else if (currentDistance - initialDistance > kMinimumPinchDelta) {
            label.text = @"Outward Pinch";
            [self performSelector:@selector(eraseLabel) withObject:nil afterDelay:1.6f];
        }
        else if (initialDistance - currentDistance > kMinimumPinchDelta) {
            label.text = @"Inward Pinch";
            [self performSelector:@selector(eraseLabel) withObject:nil afterDelay:1.6f];
        }
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    initialDistance = 0;
}


@end

7875


1)CGPointUtils.hヘッダファイルのインポート

ピンチジェスチャを認識するため、2つのタッチポイント間の距離の計算にdistanceBetweenPoints関数を使用するので、CGPointUtils.hヘッダファイルをインポートします。


2)プロパティの実装

PinchMeViewController.hヘッダファイルで宣言した2つのプロパティ(label、initialDistance)を@synthesizeで実装します。


3)eraseLabel:メソッド

eraseLabel:メソッドはラベルのテキストを消去するメソッドで、touchesMoved:withEvent:メソッド内でピンチジェスチャを認識してラベルを表示した後に呼び出されます。

テキストの消去はtextプロパティで文字列を空に設定しているだけです。


4)不要なメソッドの削除

テンプレートで自動生成されるメソッドの内、initWithNibName:bundle:loadViewviewDidLoadメソッドは今回使用しないので削除します。


5)shouldAutorotateToInterfaceOrientation:メソッド

shouldAutorotateToInterfaceOrientation:のコメントアウトを解除してデフォルトのまま実装しています。


6)viewDidUnloadメソッド

viewDidUnloadにObjective-Cオブジェクトのプロパティlabelの所有権放棄を追加しています。


7)deallocメソッド

deallocにObjective-Cオブジェクトのプロパティlabelの解放を追加しています。


8)touchesBegan:withEvent:メソッド

touchesBegan:withEvent:メソッドはビューに一本以上の指が触れた時に呼び出されるメソッドで、ここではタッチポイント数が2つであることを確認し、2つのタッチポイント座標から初期間隔を求めて保持しています。

最初にピンチジェスチャの前提条件として、タッチポイント数が2つあるかどうかを確認します。

タッチポイント数はタッチオブジェクトのセットtouchesの要素数で表されているので、要素数をcountメソッドで取得します。

タッチポイント数が2の場合、ピンチジェスチャを判定するための初期間隔を求めます。

タッチオブジェクトのセットtouchesからallObjectsメソッドで全て(2つ)のオブジェクトを配列twoTouchesとして取得し、objectAtIndex:メソッドで配列内の各タッチオブジェクトを取得します。

そして各タッチオブジェクトからlocationInView:メソッドで座標を取得し、CGPointUtilsクラスのdistanceBetweenPoints関数で両タッチポイント間の距離initialDistanceを求め、設定します。

allObjects

- (NSArray *)allObjects

セットのメンバーに含まれている配列、またはセットにメンバーが無い場合は空の配列を返します。

配列内のオブジェクトの順序は定義されていません。


9)touchesMoved:withEvent:メソッド

touchesMoved:withEvent:メソッドはビューで一本以上の指が移動した時に呼び出されるメソッドで、ここではタッチポイント数が2つであることを確認し、移動した2つのタッチポイント座標から現在の間隔を求め、初期間隔との差からピンチイン/アウトジェスチャかどうかの判定を行います。

最初にtouchesBegan:withEvent:メソッドと同様に、タッチポイント数が2つあるかどうかの確認をし、タッチポイント数が2の場合、タッチオブジェクトのセットから各タッチオブジェクトを取得し、座標を取得、両タッチポイント間の距離currentDistanceを求めます。

その後、初期間隔と現在の間隔との差からピンチジェスチャの判定を行います。

一つ目の初期間隔initialDistanceが0かどうかの判定は、タッチの開始が必ずしも2本指で行われているとは限らない(タッチ開始が1本ずつで移動時に2本だった場合、初期間隔は0のままである)ためで、その場合は現在の間隔currentDistanceを初期間隔initialDistanceとして再設定します。

初期間隔が0でない場合は、初期間隔と現在の間隔の差を求め、ピンチジェスチャと認識するための定数kMinimumPinchDelta(100ピクセル)と比較を行います。

現在の間隔から初期間隔を引いた差が100ピクセルより大きければピンチアウト、初期間隔から現在の間隔を引いた差が100ピクセルより大きければピンチインとなります。

認識されたピンチの種類に応じて、textプロパティでラベルの文字列を設定して表示した後、performSelector:withObject:afterDelay:メソッドで1.6秒後にeraseLabelメソッドを呼び出すことによって文字列を消去しています。


10)touchesEnded:withEvent:メソッド

touchesEnded:withEvent:メソッドはビューから一本以上の指が離れた時に呼び出されるメソッドで、ここではピンチジェスチャの判定をリセットするため、初期間隔initialDistanceを0に設定しています。



参考文献

iOSイベント処理ガイド

Wikipedia/ピタゴラスの定理

Wikipedia/三角関数

KIT数学ナビゲーション/ベクトル/内積

MaTのパソコンのページ/アルゴリズム解説/線分の長さと角度

NSSet Class Reference

はじめての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 他

商品詳細を見る






コンパニオン20
0 Comments
Leave a comment
管理者にだけ表示を許可する
Top
0 Trackbacks
Top
Calendar
03 | 2017/04 | 05
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 - - - - - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

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

Wish List
WACOM


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

コンパニオン20
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