Моя проблема: у меня есть супервизор, EditView
который занимает в основном весь фрейм приложения, и подпредставление, MenuView
которое занимает только нижние ~ 20%, а затем MenuView
содержит собственное подпредставление, ButtonView
которое фактически находится за пределами MenuView
границ (что-то вроде этого:) ButtonView.frame.origin.y = -100
.
(примечание: EditView
есть другие подпредставления, которые не являются частью MenuView
иерархии представлений пользователя, но могут повлиять на ответ.)
Вероятно, вы уже знаете проблему: когда ButtonView
находится в пределах MenuView
(или, более конкретно, когда мои касания находятся в MenuView
пределах), ButtonView
реагирует на события касания. Когда мои касания выходят за MenuView
пределы (но все еще в ButtonView
пределах), событие касания не принимается ButtonView
.
Пример:
- (E) является
EditView
родителем всех представлений - (M) -
MenuView
это подвид EditView - (B) -
ButtonView
это подвид MenuView
Диаграмма:
+------------------------------+
|E |
| |
| |
| |
| |
|+-----+ |
||B | |
|+-----+ |
|+----------------------------+|
||M ||
|| ||
|+----------------------------+|
+------------------------------+
Поскольку (B) находится за пределами кадра (M), касание в области (B) никогда не будет отправлено на (M) - фактически, (M) никогда не анализирует касание в этом случае, и касание отправляется на следующий объект в иерархии.
Цель: я полагаю, что переопределение hitTest:withEvent:
может решить эту проблему, но я не понимаю, как именно. В моем случае следует hitTest:withEvent:
переопределить в EditView
(моем «главном» супервизоре)? Или его следует переопределить в MenuView
прямом наблюдении за кнопкой, которая не получает касаний? Или я неправильно об этом думаю?
Если для этого требуется подробное объяснение, будет полезен хороший онлайн-ресурс - за исключением документации Apple UIView, в которой мне неясно.
Благодарность!
источник
Хорошо, я немного покопался и протестировал, вот как
hitTest:withEvent
работает - по крайней мере, на высоком уровне. Представьте этот сценарий:Диаграмма:
Поскольку (B) находится за пределами кадра (M), касание в области (B) никогда не будет отправлено на (M) - фактически, (M) никогда не анализирует касание в этом случае, и касание отправляется на следующий объект в иерархии.
Однако, если вы реализуете
hitTest:withEvent:
в (M), нажатия в любом месте приложения будут отправляться в (M) (или, по крайней мере, он знает о них). Вы можете написать код для обработки прикосновения в этом случае и вернуть объект, который должен получить прикосновение.Более конкретно: цель
hitTest:withEvent:
состоит в том, чтобы вернуть объект, который должен получить попадание. Итак, в (M) вы можете написать такой код:// need this to capture button taps since they are outside of self.frame - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { for (UIView *subview in self.subviews) { if (CGRectContainsPoint(subview.frame, point)) { return subview; } } // use this to pass the 'touch' onward in case no subviews trigger the touch return [super hitTest:point withEvent:event]; }
Я все еще новичок в этом методе и этой проблеме, поэтому, если есть более эффективные или правильные способы написания кода, прокомментируйте.
Я надеюсь, что это поможет любому, кто задаст этот вопрос позже. :)
источник
В Swift 5
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard !clipsToBounds && !isHidden && alpha > 0 else { return nil } for member in subviews.reversed() { let subPoint = member.convert(point, from: self) guard let result = member.hitTest(subPoint, with: event) else { continue } return result } return nil }
источник
Что бы я сделал, так это чтобы ButtonView и MenuView существовали на одном уровне в иерархии представлений, поместив их в контейнер, чей фрейм полностью соответствует им обоим. Таким образом, интерактивная область вырезанного элемента не будет игнорироваться из-за границ супервизора.
источник
Если у вас есть много других вложенных представлений внутри родительского представления, то, вероятно, большинство других интерактивных представлений не будут работать, если вы используете вышеуказанные решения, в этом случае вы можете использовать что-то вроде этого (в Swift 3.2):
class BoundingSubviewsViewExtension: UIView { @IBOutlet var targetView: UIView! override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { // Convert the point to the target view's coordinate system. // The target view isn't necessarily the immediate subview let pointForTargetView: CGPoint? = targetView?.convert(point, from: self) if (targetView?.bounds.contains(pointForTargetView!))! { // The target view may have its view hierarchy, // so call its hitTest method to return the right hit-test view return targetView?.hitTest(pointForTargetView ?? CGPoint.zero, with: event) } return super.hitTest(point, with: event) } }
источник
Если кому-то это нужно, вот быстрая альтернатива
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { if !self.clipsToBounds && !self.hidden && self.alpha > 0 { for subview in self.subviews.reverse() { let subPoint = subview.convertPoint(point, fromView:self); if let result = subview.hitTest(subPoint, withEvent:event) { return result; } } } return nil }
источник
Поместите следующие строки кода в свою иерархию представлений:
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { UIView* hitView = [super hitTest:point withEvent:event]; if (hitView != nil) { [self.superview bringSubviewToFront:self]; } return hitView; } - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event { CGRect rect = self.bounds; BOOL isInside = CGRectContainsPoint(rect, point); if(!isInside) { for (UIView *view in self.subviews) { isInside = CGRectContainsPoint(view.frame, point); if(isInside) break; } } return isInside; }
Для большей ясности в моем блоге было объяснено: «goaheadwithiphonetech» относительно «Пользовательское выноска: кнопка не активируется».
Надеюсь, это вам поможет ... !!!
источник