Обработка событий для iOS - как hitTest: withEvent: и pointInside: withEvent: связаны?
В то время как большинство документов Apple хорошо написаны, я думаю, что "" Руководство по обработке событий для iOS" является исключением. Мне трудно понять, что там описано.
В документе говорится:
При ударе-тестировании окно вызывает hitTest:withEvent:
в верхнем виде иерархии представлений; этот метод рекурсивно вызывает pointInside:withEvent:
для каждого представления в иерархии представлений, которая возвращает YES, идя вниз по иерархии, пока не найдет подпункт, в границах которого произошло касание. Это представление становится хитом-тестом.
Таким образом, только система hitTest:withEvent:
самого верхнего вида вызывается системой, которая вызывает pointInside:withEvent:
всех подзонов, а если возврат из определенного подвью - ДА, то вызывает pointInside:withEvent:
из подсайтов подвыражения?
Ответы
Ответ 1
Это довольно простой вопрос. Но я согласен с вами, что документ не так ясен, как другие документы, так что вот мой ответ.
Реализация hitTest:withEvent:
в UIResponder делает следующее:
- Он вызывает
pointInside:withEvent:
из self
- Если возврат НЕТ,
hitTest:withEvent:
возвращает nil
. конец истории.
- Если возврат равен YES, он отправляет сообщения
hitTest:withEvent:
в свои подпрограммы.
он начинается с подвью верхнего уровня и продолжается до других представлений до подсмотра
возвращает объект nil
, или все подпункты получают сообщение.
- Если subview возвращает объект не
nil
в первый раз, первый hitTest:withEvent:
возвращает этот объект. конец истории.
- Если subview не возвращает объект
nil
, первый hitTest:withEvent:
возвращает self
Этот процесс повторяется рекурсивно, поэтому обычно в конечном итоге возвращается представление листа иерархии представлений.
Однако вы можете переопределить hitTest:withEvent
, чтобы сделать что-то по-другому. Во многих случаях переопределение pointInside:withEvent:
проще и по-прежнему предоставляет достаточно возможностей для настройки обработки событий в вашем приложении.
Ответ 2
Я думаю, вы смешиваете подклассы с иерархией представлений. То, что говорит доктор, выглядит следующим образом. Скажем, у вас есть эта иерархия представлений. По иерархии я говорю не о иерархии классов, а о представлениях в иерархии представлений:
+----------------------------+
|A |
|+--------+ +------------+ |
||B | |C | |
|| | |+----------+| |
|+--------+ ||D || |
| |+----------+| |
| +------------+ |
+----------------------------+
Скажите, что вы положили палец внутрь D
. Вот что будет:
-
hitTest:withEvent:
вызывается в A
, самом верхнем вид иерархии представлений.
-
pointInside:withEvent:
называется рекурсивно для каждого вида.
-
pointInside:withEvent:
вызывается на A
и возвращает YES
-
pointInside:withEvent:
вызывается на B
и возвращает NO
-
pointInside:withEvent:
вызывается на C
и возвращает YES
-
pointInside:withEvent:
вызывается на D
и возвращает YES
- В представлениях, которые возвратили
YES
, он будет смотреть вниз на иерархию, чтобы увидеть subview, где произошло касание. В этом случае из A
, C
и D
это будет D
.
-
D
будет хитом-тестом
Ответ 3
Я нахожу этот Хит-тестирование в iOS очень полезным
![enter image description here]()
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
return nil;
}
Ответ 4
Спасибо за ответы, они помогли мне решить ситуацию с "наложением".
+----------------------------+
|A +--------+ |
| |B +------------------+ |
| | |C X | |
| | +------------------+ |
| | | |
| +--------+ |
| |
+----------------------------+
Предположим X
- прикосновение пользователя. pointInside:withEvent:
on B
возвращает NO
, поэтому hitTest:withEvent:
возвращает A
. Я написал категорию на UIView
для обработки проблемы, когда вам нужно получить касание сверху самого видимого вида.
- (UIView *)overlapHitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1
if (!self.userInteractionEnabled || [self isHidden] || self.alpha == 0)
return nil;
// 2
UIView *hitView = self;
if (![self pointInside:point withEvent:event]) {
if (self.clipsToBounds) return nil;
else hitView = nil;
}
// 3
for (UIView *subview in [self.subviewsreverseObjectEnumerator]) {
CGPoint insideSubview = [self convertPoint:point toView:subview];
UIView *sview = [subview overlapHitTest:insideSubview withEvent:event];
if (sview) return sview;
}
// 4
return hitView;
}
- Мы не должны отправлять события касания для скрытых или прозрачных представлений или представления с
userInteractionEnabled
, установленными на NO
;
- Если прикосновение находится внутри
self
, self
будет рассматриваться как потенциальный результат.
- Проверьте рекурсивно все подпункты для попадания. Если есть, верните его.
- Else возвращает self или nil в зависимости от результата с шага 2.
Примечание. [self.subviewsreverseObjectEnumerator]
необходимо следовать иерархии представлений сверху вниз. И проверьте clipsToBounds
, чтобы убедиться, что вы не проверяете маскированные подсмотры.
Использование:
- Импортировать категорию в подклассу.
- Замените
hitTest:withEvent:
на этот
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [self overlapHitTest:point withEvent:event];
}
Официальное руководство Apple содержит некоторые хорошие иллюстрации.
Надеюсь, это поможет кому-то.
Ответ 5
Он отображается как этот фрагмент!
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01)
{
return nil;
}
if (![self pointInside:point withEvent:event])
{
return nil;
}
__block UIView *hitView = self;
[self.subViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
CGPoint thePoint = [self convertPoint:point toView:obj];
UIView *theSubHitView = [obj hitTest:thePoint withEvent:event];
if (theSubHitView != nil)
{
hitView = theSubHitView;
*stop = YES;
}
}];
return hitView;
}
Ответ 6
Отрывок из @lion работает как шарм. Я поместил его в swift 2.1 и использовал его как расширение для UIView. Я отправляю его здесь, если кому-то это понадобится.
extension UIView {
func overlapHitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
// 1
if !self.userInteractionEnabled || self.hidden || self.alpha == 0 {
return nil
}
//2
var hitView: UIView? = self
if !self.pointInside(point, withEvent: event) {
if self.clipsToBounds {
return nil
} else {
hitView = nil
}
}
//3
for subview in self.subviews.reverse() {
let insideSubview = self.convertPoint(point, toView: subview)
if let sview = subview.overlapHitTest(insideSubview, withEvent: event) {
return sview
}
}
return hitView
}
}
Чтобы использовать его, просто переопределите hitTest: point: withEvent в вашем uiview следующим образом:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let uiview = super.hitTest(point, withEvent: event)
print("hittest",uiview)
return overlapHitTest(point, withEvent: event)
}