HitTest: WithEvent и Subviews

У меня есть 2 вида, но я хочу сделать 1 вид (фактически) больше. если я поместил свой tapGesture на v1, жест выделения будет работать с большей зоной удара но если я поместил свой tapGesture на v2, он не сработает (на самом деле он вообще не распознает tapGesture, даже не в исходных границах), хотя я прохожу через мой метод testView1 hittest и точки попадают в рамку.

#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

Ответ 1

Вы не просматриваете иерархию представлений. Из документации:

Этот метод пересекает иерархию представлений, отправив сообщение pointInside: withEvent: для каждого подсмотра, чтобы определить, какой субвью должен получить событие касания. Если pointInside: withEvent: возвращает YES, то проходит иерархия subviews; в противном случае его ветвь иерархии представлений игнорируется.

Вам нужно только реализовать 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, а с помощью кода выше вы можете сделать это с v2 как подвид v1.

Однако реализации TestView1 и TestView2 абсолютно одинаковы (даже в вашем исходном сообщении), так зачем вам их разделять? Вы можете сделать 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];

Ответ 2

Ваш v2 не получит никаких событий касания. потому что, когда вы нажимаете область вокруг вашего v1, она возвращает self в своем - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event, что означает, что вы заявили, что это "v1" - это тестовое представление, которое является местом назначения всех событий касания. < ш > Правильный способ расширения v1 touchable ares - реализовать - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event в TestView1 и 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);

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

Приведенный выше код означает, что когда вы нажимаете область вокруг своего v1, она заявляет: "Да, вы коснулись меня, и я проверю, кто с этим справится. Возможно, это я, возможно, это одно из моих подзаголовков". Таким образом, тест-тест продолжается, и v1 найдет, что его subview v2 является самым верхним видом, таким образом, v2 является местом назначения вашего события click.

Вы можете спросить, как узнать v1, что v2 является тем. Вот псевдо-код, чтобы выявить трюк:

@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 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

Этот код для простоты не принял userInteractionEnable, alpha и другие вещи. Когда вы вызываете [super pointInside:point withEvent:event]; в свой TestView1 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event, он спрашивает, попадает ли точка в область v2. Если ответ v2 да, и потому, что у него нет подзонов, значит, v2 вернется в свой - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event.

Вот и вся история.

Ответ 3

Насколько я понимаю из вашего вопроса, вы добавили больше V2 поверх V1. поэтому V2 будет осязаемым только в пределах V1. Таким образом, ваш жест не распознается в дополнительной области V2.

Ответ 4

Вам необходимо реализовать метод:

- (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: сообщение для каждого подсмотра, чтобы определить, какие subview должен получить событие касания. Если pointInside: withEvent: возвращает YES, затем проходит иерархия subviews; в противном случае его ветвь иерархии представлений игнорируется. Вам редко приходится называть это метод, но вы можете переопределить его, чтобы скрыть события касания от подвиды.

Этот метод игнорирует скрытые объекты вида, которые отключены пользовательские взаимодействия или уровень альфа менее 0,01. Этот метод не учитывает содержание просмотров при определении удара. Таким образом, представление все равно можно вернуть, даже если указанная точка находится в прозрачная часть этого представления.

Ответ 5

То, как вы это делаете, возможно, но сложно сделать правильно.

Я рекомендую добавить 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;
    }

    ...
}

Если представления не имеют общего супервизора, просто вставьте каждый из них в более крупный прозрачный вид и добавьте распознаватель к этому большему виду.