Mój problem: mam nadzór, EditView
który zajmuje w zasadzie całą ramkę aplikacji i podwidok, MenuView
który zajmuje tylko dolne ~ 20%, a następnie MenuView
zawiera własny podwidok, ButtonView
który faktycznie znajduje się poza MenuView
granicami aplikacji (coś takiego:) ButtonView.frame.origin.y = -100
.
(uwaga: EditView
ma inne widoki podrzędne, które nie są częścią MenuView
hierarchii widoków, ale mogą wpływać na odpowiedź).
Prawdopodobnie już znasz ten problem: kiedy ButtonView
znajduje się w granicach MenuView
(a dokładniej, kiedy moje dotknięcia są w MenuView
granicach), ButtonView
reaguje na zdarzenia dotykowe. Kiedy moje dotknięcia są poza MenuView
jego granicami (ale nadal w ButtonView
granicach), żadne zdarzenie dotykowe nie jest odbierane przez ButtonView
.
Przykład:
- (E) jest
EditView
rodzicem wszystkich poglądów - (M) jest
MenuView
podziałem widoku EditView - (B) jest
ButtonView
podziałem widoku MenuView
Diagram:
+------------------------------+
|E |
| |
| |
| |
| |
|+-----+ |
||B | |
|+-----+ |
|+----------------------------+|
||M ||
|| ||
|+----------------------------+|
+------------------------------+
Ponieważ (B) jest poza ramką (M), dotknięcie w regionie (B) nigdy nie zostanie wysłane do (M) - w rzeczywistości (M) nigdy nie analizuje dotyku w tym przypadku, a dotknięcie jest wysyłane do następny obiekt w hierarchii.
Cel: Rozumiem, że nadpisywanie hitTest:withEvent:
może rozwiązać ten problem, ale nie rozumiem dokładnie, jak. W moim przypadku powinno hitTest:withEvent:
być nadpisane w EditView
(mój nadzór „nadrzędny”)? A może powinno zostać pominięte w MenuView
bezpośrednim nadzorze nad przyciskiem, który nie jest dotknięty? A może źle o tym myślę?
Jeśli wymaga to długiego wyjaśnienia, pomocne byłoby dobre źródło informacji online - z wyjątkiem dokumentów Apple UIView, które nie wyjaśniły mi tego jasno.
Dzięki!
źródło
Ok, trochę wykopałem i przetestowałem, oto jak
hitTest:withEvent
działa - przynajmniej na wysokim poziomie. Wyobraź sobie ten scenariusz:Diagram:
Ponieważ (B) jest poza ramką (M), dotknięcie w regionie (B) nigdy nie zostanie wysłane do (M) - w rzeczywistości (M) nigdy nie analizuje dotyku w tym przypadku, a dotknięcie jest wysyłane do następny obiekt w hierarchii.
Jeśli jednak zaimplementujesz
hitTest:withEvent:
w (M), stuknięcia w dowolnym miejscu w aplikacji zostaną wysłane do (M) (a przynajmniej on o nich wie). Możesz napisać kod obsługujący dotyk w takim przypadku i zwrócić obiekt, który powinien otrzymać dotyk.Dokładniej: celem programu
hitTest:withEvent:
jest zwrócenie obiektu, który powinien otrzymać trafienie. Więc w (M) możesz napisać taki kod:// 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]; }
Wciąż jestem bardzo nowy w tej metodzie i tym problemie, więc jeśli istnieją bardziej wydajne lub poprawne sposoby pisania kodu, proszę o komentarz.
Mam nadzieję, że pomoże to każdemu, kto później odpowie na to pytanie. :)
źródło
W 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 }
źródło
Chciałbym, aby zarówno ButtonView, jak i MenuView istniały na tym samym poziomie w hierarchii widoku, umieszczając je oba w kontenerze, którego ramka całkowicie pasuje do nich obu. W ten sposób interaktywny obszar przyciętego elementu nie będzie ignorowany ze względu na granice superwiewu.
źródło
Jeśli masz wiele innych podglądów podrzędnych w widoku nadrzędnym, prawdopodobnie większość innych widoków interaktywnych nie zadziała, jeśli użyjesz powyższych rozwiązań, w takim przypadku możesz użyć czegoś takiego (w 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) } }
źródło
Jeśli ktoś tego potrzebuje, oto szybka alternatywa
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 }
źródło
Umieść poniższe wiersze kodu w hierarchii widoku:
- (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; }
Dla dokładniejszego wyjaśnienia zostało to wyjaśnione na moim blogu: „goaheadwithiphonetech” w odniesieniu do „Niestandardowe objaśnienie: problem z przyciskiem nie jest klikalny”.
Mam nadzieję, że to ci pomoże ... !!!
źródło