Отключить касание на фоне UIView, чтобы кнопки на более низких уровнях были доступны для просмотра

В принципе, вот моя иерархия взглядов (и я приговариваю, если это трудно читать... Я новичок здесь, поэтому предложения по отправке приветствуются)


- AppControls.xib
------- (UIView) ControlsView
----------------- (UIView) TopBar
----------------- -------------- btn1, btn2, btn3
----------------- UIView) BottomBar
----------------- -------------- slider1 btn1, btn2
--PageContent.xib
----------------- (UIView) ContentView
----------------- -------------- btn1, btn2, btn3
----------------- -------------- (UIImageView) FullPageImage


Моя ситуация заключается в том, что я хочу скрыть и показать элементы управления, когда вы нажимаете в любом месте на PageContent, который не является кнопкой, и у вас есть элементы управления, похожие на iPhone Video Player. Однако, когда элементы управления показаны, я все же хочу, чтобы вы могли нажимать кнопки на PageContent.

У меня все это работает, за исключением последнего бит. Когда элементы управления отображаются, фон элементов управления получает события касания вместо представления ниже. И отключить взаимодействие с пользователем в ControlsView отключает его всех своих детей.

Я попытался переопределить HitTest в моем подклассе ControlsView следующим образом, который я нашел в подобном сообщении:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *hitView = nil;
NSArray *subviews = [self subviews];
int subviewCount = [subviews count];
for (int subviewIndex = 0; !hitView && subviewIndex < subviewCount; subviewIndex++){
hitView = [[subviews objectAtIndex:subviewIndex] hitTest:point withEvent:event];
}   
return hitView;
}

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

Итак, мой вопрос вкратце: как я могу позволить всем представлениям представления иметь события касания, в то время как фон супер-просмотра не имеет значения, а кнопки на представлениях ниже могут получать события касания.

Спасибо!

Ответ 1

Ты близко. Не переопределяйте -hitTest:withEvent:. К тому времени, когда это вызвано, диспетчер событий уже решил, что ваше поддерево иерархии владеет событием и не будет искать в другом месте. Вместо этого переопределите -pointInside:withEvent:, который вызывается ранее в конвейере обработки событий. Это как система запрашивает "эй взгляд, делает ли ANYONE в вашей иерархии ответ на событие на данный момент?". Если вы скажете "НЕТ", обработка событий продолжается ниже вас в видимом стеке.

В documentation реализация по умолчанию просто проверяет, находится ли точка в границах представления вообще.

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

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView * view in [self subviews]) {
        if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
            return YES;
        }
    }
    return NO;
}

Ответ 2

Благодаря @Ben Zutto, решение Swift 3:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for view in self.subviews {
        if view.isUserInteractionEnabled, view.point(inside: self.convert(point, to: view), with: event) {
            return true
        }
    }

    return false
}

Ответ 3

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

Ответ 4

Небольшой вариант ответа Бена, связанный с детьми, которые выходят за пределы их родителя. Если clipChildren - ДА, то это не вернет YES для точек, находящихся за пределами основного элемента управления, но внутри некоторого дочернего элемента. если clipChildren НЕТ, это то же самое, что и у Бена.

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL clipChildren = YES;

    if (!clipChildren || [super pointInside:point withEvent:event]) {
        for (UIView * view in [self subviews]) {
            if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
                return YES;
            }
        }
    }
    return NO;
}