2011-02-10 2 views
125

Хотя большинство документов для яблока очень хорошо написаны, я думаю, что «Event Handling Guide for iOS» является исключением. Мне трудно понять, что там описано.Обработка событий для iOS - как hitTest: withEvent: и pointInside: withEvent: связаны?

В документе говорится,

В хит-тестирования, окно вызывает hitTest:withEvent: на самой верхней точки зрения иерархии зрения; этот метод рекурсивно вызывает pointInside:withEvent: на каждом представлении в иерархии представлений, который возвращает ДА, идя вниз по иерархии, пока не найдет подпункт, в границах которого произошло касание. Это представление становится хитом-тестом.

Так это так только hitTest:withEvent: из самого верхней точки зрения, называются системой, которая называет pointInside:withEvent: всех подвиды, и если отдача от конкретного подвида ДА, то называет pointInside:withEvent: этого подтаблицу-х подклассы?

+2

Очень хороший учебник, который помог мне [ссылка] (http://smnh.me/ hit-testing-in-ios) – anneblue

ответ

155

Это довольно простой вопрос. Но я согласен с вами, что документ не так ясен, как другие документы, так что вот мой ответ.

Реализация hitTest:withEvent: в UIResponder делает следующее:

  • Он называет pointInside:withEvent: из self
  • Если возвращение NO, hitTest:withEvent: возвращается nil. конец истории.
  • Если возврат ДА, он отправляет hitTest:withEvent: сообщениям в его подзоны. начинается с верхнего представления верхнего уровня и продолжается до других представлений до тех пор, пока subview не возвращает объект nil, или все подпункты получают сообщение.
  • Если subview возвращает объект не nil в первый раз, первый hitTest:withEvent: возвращает этот объект. конец истории.
  • Если нет подтаблицы не возвращает значение не nil объекта, первый hitTest:withEvent: возвращает self

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

Однако вы можете переопределить hitTest:withEvent, чтобы сделать что-то по-другому. Во многих случаях переопределение pointInside:withEvent: проще и по-прежнему предоставляет достаточно возможностей для настройки обработки событий в приложении.

+0

Вы имеете в виду 'hitTest: withEvent:' из всех subviews выполняются в конце концов? – realstuff02

+2

Да. Просто переопределите 'hitTest: withEvent:' в ваших представлениях (и 'pointInside', если хотите), распечатайте журнал и вызовите' [super hitTest ...', чтобы узнать, чей вызов «hitTest: withEvent:» вызывается в каком порядке. – MHC

+0

Я просто сделал это и получил изображение ясно! Большое спасибо! – realstuff02

273

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

+----------------------------+ 
|A       | 
|+--------+ +------------+ | 
||B  | |C   | | 
||  | |+----------+| | 
|+--------+ ||D   || | 
|    |+----------+| | 
|    +------------+ | 
+----------------------------+ 

Допустим, вы кладете палец внутрь D. Вот что произойдет:

  1. hitTest:withEvent: вызывается A, самый верхний вид иерархии представлений.
  2. pointInside:withEvent: называется рекурсивно для каждого вида.
    1. pointInside:withEvent: вызывается A и возвращает YES
    2. pointInside:withEvent: вызывается B и возвращает NO
    3. pointInside:withEvent: вызывается C и возвращает YES
    4. pointInside:withEvent: вызывается D и возвращает YES
  3. По просмотрам, которые вернули YES, он будет смотреть вниз на иерархию, чтобы увидеть подпункт, где произошло касание. В этом случае от A, C и D, это будет D.
  4. D будет являться вид хит-испытателю
+0

Благодарим вас за ответ. То, что вы описали, также было в моем сознании, но @MHC говорит, что «hitTest: withEvent:» из B, C и D также вызывается. Что произойдет, если D является подзоном C, а не A? Я думаю, что я запутался ... – realstuff02

+2

В моем чертеже D является подпунктом C. – pgb

+1

Не будет ли '' '' '' '' '' '' '', так же как 'C' и' D'? –

21

Спасибо за ответы, они помогли мне решить ситуацию с видом на «Overlay».

+----------------------------+ 
|A +--------+    | 
| |B +------------------+ | 
| | |C   X | | 
| | +------------------+ | 
| |  |    | 
| +--------+    | 
|       | 
+----------------------------+ 

Предполагается, X - Прикосновение пользователя. pointInside:withEvent: по телефону BNO, поэтому 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; 
} 
  1. Мы не должны посылать событие прикосновения для скрытых или прозрачных взглядов или мнений с userInteractionEnabled набором для NO;
  2. Если контакт находится внутри self, self будет считаться потенциальным результатом.
  3. Проверьте рекурсивно все подзаголовки для попадания. Если есть, верните его.
  4. Else Самовозврат или ноль в зависимости от результата, начиная с шага 2.

Примечания, [self.subviewsreverseObjectEnumerator] необходимого следовать вид иерархии сверху наиболее донизу. И проверьте для clipsToBounds, чтобы убедиться в том, что вы не проверяете маскированные подзоны.

Использование:

  1. категории Импорт в вашем подклассы зрения.
  2. Заменить hitTest:withEvent: с этим
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 
    return [self overlapHitTest:point withEvent:event]; 
} 

Official Apple's Guide обеспечивает хорошие иллюстрации тоже.

Надеюсь, это поможет кому-то.

+0

Удивительно! Спасибо за четкую логику и отличный фрагмент кода, решил мой головорез! – Thompson

+0

@ Lion, Nice ответ. Также вы можете проверить равенство, чтобы очистить цвет на первом шаге. –

3

Это показывает, как этот фрагмент!

- (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; 
} 
+0

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

+0

@ DouglasHill благодаря вашей коррекции. С наилучшими пожеланиями – hippo

31

Я нахожу это Hit-Testing in 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; 
} 
+2

Отличный ответ, спасибо! – Jacob

0

Фрагмент из @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 
    } 
} 

Чтобы использовать его, просто переопределить Трассировка: точка: 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) 
} 
Смежные вопросы