web-dev-qa-db-ja.com

hitTest:WithEventとサブビュー

2つのビューがありますが、1つのビューを(実質的に)大きくしたいと思います。 tapGestureをv1に配置すると、タップジェスチャはより大きなヒット領域で機能しますが、tapGestureをv2に配置すると、機能しません(実際には、元の境界内でなくても、tapGestureをまったく認識しません)。 TestView1のヒットテストメソッドをループすると、ポイントがフレームに含まれます。

#import "ViewController.h"

@interface TestView1 : UIView
@end

@implementation TestView1

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return nil;
}

@end

@interface TestView2 : UIView
@end

@implementation TestView2

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return nil;
}
@end

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    TestView1 *v1 = [[TestView1 alloc] initWithFrame:CGRectMake(50.f, 50.f, 100.f, 100.f)];
    [self.view addSubview:v1];

    TestView2 *v2 = [[TestView2 alloc] initWithFrame:CGRectMake(0.f, 0.f, 100.f, 100.f)];
    v2.backgroundColor = UIColor.yellowColor;
    [v1 addSubview:v2];

    UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
    [v2 addGestureRecognizer:gesture];
}

- (void) panGesture:(UIPanGestureRecognizer *)recognizer
{
    NSLog(@"tap");
}
@end
12
Andy Jacobs

ビュー階層をトラバースしていません。ドキュメントから:

このメソッドは、pointInside:withEvent:メッセージを各サブビューに送信してビュー階層をトラバースし、タッチイベントを受信するサブビューを決定します。 pointInside:withEvent:がYESを返す場合、サブビューの階層がトラバースされます。それ以外の場合、ビュー階層のブランチは無視されます。

pointInside:withEvent:を実装するだけで済みます。標準の実装ではhitTest:withEvent:の実装が呼び出され、階層をトラバースするという大変な作業がすべて行われるため、pointInside:withEvent:をオーバーライドしないでください。

次のように実装します。

@interface TestView1 : UIView
@end

@implementation TestView1

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    return (CGRectContainsPoint(frame, point));
}

@end

@interface TestView2 : UIView
@end

@implementation TestView2

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    return (CGRectContainsPoint(frame, point));
}

@end

さて、両方のビューが必要かどうかはあなた次第です。あなたはすでにv1のタッチ可能な領域を拡張することに成功しており、上記のコードを使用すると、v1のサブビューとしてv2でそれを行うことができます。

ただし、TestView1TestView2の実装は(元の投稿でも)まったく同じなので、なぜそれらを分離する必要があるのでしょうか。 v1とv2の両方を同じクラスのインスタンスにすることができます。 TestView、次のようにインスタンス化します。

TestView *v1 = [[TestView alloc] initWithFrame:CGRectMake(50.f, 50.f, 100.f, 100.f)];
[self.view addSubview:v1];
v1.clipsToBounds = YES;

TestView *v2 = [[TestView alloc] initWithFrame:CGRectMake(0.f, 0.f, 100.f, 100.f)];
v2.backgroundColor = UIColor.yellowColor;
[v1 addSubview:v2];
11
Sam

V2はタッチイベントを受信しません。 v1の周囲の領域をクリックすると、- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)eventselfが返されるためです。これは、宛先であるヒットテストビューである「v1」であることを宣言したことを意味します。すべてのタッチイベントの。
v1のタッチ可能な領域を拡張する正しい方法は、_TestView1_および_TestView2_に- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventを実装することです。

_- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                          self.frame.size.width + radius,
                          self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return YES;
    }
    return [super pointInside:point withEvent:event];
}
_

上記のコードは、v1の周囲の領域をクリックすると、「はい、あなたは私に触れました。誰がそれを処理できるかを確認します。多分それは私です、多分それは私のサブビューの1つです」と宣言します。したがって、ヒットテストは続行され、v1はそのサブビューv2が最上位のビューであることがわかります。したがって、v2はクリックイベントの宛先です。

V2が1つであることをv1がどのように知ることができるかを尋ねることができます。トリックを明らかにするための擬似コードは次のとおりです。

_@implementation UIView
//...
//...

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return CGRectContainsPoint(self.bounds, point); // Honestly tell others if the point is inside the bounds. That's the normal case.
}

// This method returns a hit-test view who or whose gesture recognizer is responsible for handling the events
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    for(UIView *aSubview in self.subviews)
    {
        // Ask each subview if the point falls in its area.
        if ([aSubview pointInside:[self convertPoint:point toView:aSubview]  point withEvent:event])
        {
            return [aSubview hitTest:[self convertPoint:point toView:aSubview] withEvent:event];
        }
    }

    // If no one can handle the event.
    return self;
}

//...
//...
@end
_

これらのコードは、簡単にするために、userInteractionEnablealphaなどを考慮していません。
_[super pointInside:point withEvent:event];_の- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventで_TestView1_を呼び出すと、ポイントがv2の領域にあるかどうかを尋ねられます。 v2の答えが「はい」で、サブビューがないため、v2は- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)eventに戻ります。

それがすべての話です。

5
liuyaodong

あなたがそれをしている方法は可能ですが、正しく行うのは難しいです。

私がお勧めするのは、UITapGestureRecognizerを共通のスーパービューに追加してから、タップの場所に応じてレシーバーとなるビューを決定することです。

- (void)onTap:(UITapGestureRecognizer*)tapRecognizer {
    CGPoint touchPosition = [tapRecognizer locationInView:self];

    CGRect view1Frame = self.view1.frame;
    view1Frame.width += 100;
    view1Frame.height += 100;

    if (CGRectContainsPoint(view1Frame, touchPosition)) {
        [self.view1 handleTap];
        return;
    }

    ...
}

ビューに共通のスーパービューがない場合は、それぞれをより大きな透明なビューに埋め込み、この大きなビューに認識機能を追加します。

2
Sulthan

メソッドを実装する必要があります:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return YES;
    }

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

その後:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return [super hitTest:point withEvent:event;
}

ドキュメントから

このメソッドは、pointInside:withEvent:メッセージを各サブビューに送信してビュー階層をトラバースし、タッチイベントを受信するサブビューを決定します。 pointInside:withEvent:がYESを返す場合、サブビューの階層がトラバースされます。それ以外の場合、ビュー階層のブランチは無視されます。このメソッドを自分で呼び出す必要はめったにありませんが、サブビューからタッチイベントを非表示にするためにオーバーライドすることができます。

このメソッドは、非表示になっている、ユーザーインタラクションを無効にしている、またはアルファレベルが0.01未満のビューオブジェクトを無視します。この方法では、ヒットを決定するときにビューのコンテンツは考慮されません。したがって、指定されたポイントがそのビューのコンテンツの透明な部分にある場合でも、ビューを返すことができます。

2
AntonPalich

あなたの質問から私が理解する限り、あなたはV1の上に大きなV2を追加しました。したがって、V2はV1の境界内でのみタッチ可能になります。そのため、V2の余分な領域ではジェスチャーが認識されません。

2
Vignesh