Help us understand the problem. What is going on with this article?

[iOS] touchイベントを制御する

More than 5 years have passed since last update.

タッチイベントの種類

タッチイベントにはいくつかのイベントがあります。
タッチイベントは「UIResponder」クラスで定義されている以下のメソッドをオーバーライドすることで検知できます。(UIView、UIViewControllerはUIResponderクラスのサブクラスなのでこれらもメソッドを持っています)

メソッド 説明
touchesBegan:withEvent: タッチ開始時
touchesMoved:withEvent: タッチしたまま指を移動
touchesEnded:withEvent: タッチした指が画面から離れる
touchesCancelled:withEvent: タッチがキャンセルされたとき(例えば電話の着信など)

ちなみに、いくつかのクラスではデフォルトオフになっているため、setUserInteractionEnabled:アクセサメソッドにYESを渡す必要があります。

タッチされたviewを調べる

タッチされたviewを調べるには、touchオブジェクトのviewを参照することで確認できます。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    NSLog(@"%@", touch.view);
}

また、event#touchesForView:メソッドに、タッチされたか知りたいviewを指定することで、該当のビューがタッチされたかを確認することができます。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    if ([event touchesForView:aView]) {
        //NULLじゃなかったら、`aView`がタッチされている
    }
}

タッチされたlayerを調べる

viewに比べ、layerは若干めんどくさい処理が必要になります。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];

    //タッチされた位置を取得
    CGPoint touchpoint = [touch locationInView:self.view];

    //得られた位置にあるlayerを取得
    CALayer *layer = [self.view.layer hitTest:touchpoint];
}

UIViewのタッチイベントをキャプチャするタイミング

hitTest:withEvent:メソッドは画面上どこをタップされても呼び出されます。
HTML的に考えると違和感があるのだけど、メソッド名を見れば自明。
さらにこのメソッドの中でpointInside:withEvent:メソッドが呼ばれ、ここで実際に自身がタッチされたかをチェックし、BOOLを返すようになっています。

例えばこんな感じ↓

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent)event
{
    CGRect rect = self.bounds;

    // タッチ位置が自身の`bounds`に含まれるかチェック
    return CGRectContainsPoint(rect, point);
}

ヒットエリアを拡大する

ちなみにこの判定のメソッドをオーバーライドし、その値を調整することで実際のヒットエリアを変えることができます。(仮にすべてにYESを返すと必ずタッチされたことになる)

ボタンのタップ反応エリアの拡大方法を参考にさせて頂いています。

UIButtonのサブクラスで、以下をオーバーライドし、実装します。

ExpandHitArea.m
- (BOOL)pointInside:(CGPoint)point
          withEvent:(UIEvent *)event
{
    const CGFloat insetLeft   = 15.0f;
    const CGFloat insetRight  = 15.0f;
    const CGFloat insetTop    = 15.0f;
    const CGFloat insetBottom = 15.0f;

    // ヒット確認用のCGRectを新規に作る
    CGRect rect = self.bounds;

    // 指定したinset分、領域を広げる
    // (位置をオフセットし、幅・高さを足し込む)
    rect.origin.x -= insetLeft;
    rect.origin.y -= insetTop;
    rect.size.width  += (insetLeft + insetRight);
    rect.size.height += (insetTop + insetBottom);

    // 拡張したエリアとの当たり判定を返す
    return CGRectContainsPoint(rect, point);
}

hitTest:withEvent:メソッドをオーバーライドしてタッチ対象を判別する

以下のように、hitTest:withEvent:メソッド内でSubviewを使って判定を行い、returnするUIViewを変えることでtargetを変えることができます。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGRect rect = self.subView.bounds;

    // subviewの位置がタッチされていたらsubviewを返す
    if (CGRectContainsPoint(rect, point)) {
        return self.subView;
    }

    return [super hitTest:point withEvent:event];
}

returnで返すUViewを固定にすれば、必ずそれがタッチされたことに、なんてこともできます。


[2014.07.10 追記] ハマったこと

親のviewのframeからはみ出した要素はタップに反応しない

これ、地味にハマりました。
HTML脳だと見えているものは基本、クリックなどに反応してくれるのでしばらく答えにたどり着けずにいました。

要はaView.clipsToBounds = YES;にしているとクリッピングされて見えなくなる範囲は、見ていても反応しない、ということです。
自分でイチから作っている場合はすぐに気付けそうですが、人が作ったものに手を入れている場合は予期せぬ構造になっていて「なんで見えているのに反応しないんだー」ってことになるので注意が必要です。
(これに気づくのに数時間要した・・orz)

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away