Czy możliwe jest, aby UIButton (lub jakakolwiek inna kontrolka w tym zakresie) odbierała zdarzenia dotyku, gdy ramka UIButton znajduje się poza ramką swojego rodzica? Ponieważ kiedy próbuję tego, mój UIButton nie wydaje się być w stanie odbierać żadnych zdarzeń. Jak mam to obejść?
94
Odpowiedzi:
Tak. Możesz nadpisać
hitTest:withEvent:
metodę, aby zwrócić widok dla większego zestawu punktów niż zawiera ten widok. Zobacz odwołanie do klasy UIView .Edycja: Przykład:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { CGFloat radius = 100.0; CGRect frame = CGRectMake(-radius, -radius, self.frame.size.width + radius, self.frame.size.height + radius); if (CGRectContainsPoint(frame, point)) { return self; } return nil; }
Edycja 2: (Po wyjaśnieniu :) Aby upewnić się, że przycisk jest traktowany jako znajdujący się w granicach rodzica, musisz nadpisać
pointInside:withEvent:
w rodzicu, aby uwzględnić ramkę przycisku.- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { if (CGRectContainsPoint(self.view.bounds, point) || CGRectContainsPoint(button.view.frame, point)) { return YES; } return NO; }
Zwróć uwagę, że kod właśnie tam zastępujący pointInside nie jest całkiem poprawny. Jak Summon wyjaśnia poniżej, zrób to:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { if ( CGRectContainsPoint(self.oversizeButton.frame, point) ) return YES; return [super pointInside:point withEvent:event]; }
Zwróć uwagę, że najprawdopodobniej zrobiłbyś to
self.oversizeButton
jako IBOutlet w tej podklasie UIView; następnie możesz po prostu przeciągnąć dany „przycisk oversize” do odpowiedniego widoku specjalnego. (Lub, jeśli z jakiegoś powodu robiłeś to często w projekcie, miałbyś specjalną podklasę UIButton i mógłbyś przejrzeć listę podrzędnych tych klas.) Mam nadzieję, że to pomoże.źródło
pointInside:withEvent:
aby uwzględnić ramkę przycisku. Dodam przykładowy kod do mojej odpowiedzi powyżej.hitTest:withEvent:
ipointInside:withEvent:
obszar, który mnie nie wzywał, kiedy naciskam przycisk znajdujący się poza granicami rodziców. Co może być nie tak?@jnic, pracuję na iOS SDK 5.0 i aby twój kod działał poprawnie, musiałem zrobić to:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { if (CGRectContainsPoint(button.frame, point)) { return YES; } return [super pointInside:point withEvent:event]; }
Widok kontenera w moim przypadku to UIButton, a wszystkie elementy podrzędne są również UIButtonami, które mogą poruszać się poza granice nadrzędnego UIButton.
Najlepsza
źródło
W widoku nadrzędnym możesz zmienić metodę testu trafień:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { CGPoint translatedPoint = [_myButton convertPoint:point fromView:self]; if (CGRectContainsPoint(_myButton.bounds, translatedPoint)) { return [_myButton hitTest:translatedPoint withEvent:event]; } return [super hitTest:point withEvent:event]; }
W takim przypadku, jeśli punkt mieści się w granicach twojego przycisku, przekierowujesz tam połączenie; jeśli nie, wróć do pierwotnej implementacji.
źródło
W moim przypadku miałem
UICollectionViewCell
podklasę zawierającą plikUIButton
. WyłączyłemclipsToBounds
na komórce i przycisk był widoczny poza granicami komórki. Jednak przycisk nie odbierał zdarzeń dotykowych. Udało mi się wykryć zdarzenia dotyku na przycisku, korzystając z odpowiedzi Jacka: https://stackoverflow.com/a/30431157/3344977Oto wersja Swift:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { let translatedPoint = button.convertPoint(point, fromView: self) if (CGRectContainsPoint(button.bounds, translatedPoint)) { print("Your button was pressed") return button.hitTest(translatedPoint, withEvent: event) } return super.hitTest(point, withEvent: event) }
Swift 4:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let translatedPoint = button.convert(point, from: self) if (button.bounds.contains(translatedPoint)) { print("Your button was pressed") return button.hitTest(translatedPoint, with: event) } return super.hitTest(point, with: event) }
źródło
Dlaczego tak się dzieje?
Dzieje się tak, ponieważ gdy widok podrzędny znajduje się poza granicami podglądu, zdarzenia dotykowe, które faktycznie mają miejsce w tym podglądzie, nie zostaną do niego dostarczone. Jednakże BĘDZIE być dostarczony do SuperView.
Co musisz zrobić?
Kiedy Twój nadzór otrzyma wspomniane powyżej zdarzenie dotyku, musisz wyraźnie powiedzieć UIKit, że moja subview powinna otrzymywać to zdarzenie dotykowe.
A co z kodem?
W swoim nadzorze, zaimplementuj
func hitTest(_ point: CGPoint, with event: UIEvent?)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if isHidden || alpha == 0 || clipsToBounds { return super.hitTest(point, with: event) } // convert the point into subview's coordinate system let subviewPoint = self.convert(point, to: subview) // if the converted point lies in subview's bound, tell UIKit that subview should be the one that receives this event if !subview.isHidden && subview.bounds.contains(subviewPoint) { return subview } return super.hitTest(point, with: event) }
Fascynująca gotchya: musisz przejść do „najwyższego zbyt małego superwizji”
Musisz wejść „w górę” do „najwyższego” widoku, którego widok problemu znajduje się na zewnątrz.
Typowy przykład:
Powiedzmy, że masz ekran S z widokiem kontenera C. Widok kontrolera widoku kontenera to V. (Przypomnij sobie, że V będzie znajdować się w C i będzie identycznego rozmiaru). V ma widok podrzędny (może przycisk) B. B to problem widok, który faktycznie znajduje się poza V.
Ale pamiętaj, że B jest również poza C.
W tym przykładzie trzeba zastosować rozwiązanie
override hitTest
w rzeczywistości do C, a nie V . Jeśli zastosujesz to do V - to nic nie robi.źródło
Szybki:
Gdzie targetView jest widokiem, w którym chcesz odebrać zdarzenie (które może być całkowicie lub częściowo umieszczone poza ramką jego widoku nadrzędnego).
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { let pointForTargetView: CGPoint = targetView.convertPoint(point, fromView: self) if CGRectContainsPoint(targetView.bounds, pointForTargetView) { return closeButton } return super.hitTest(point, withEvent: event) }
źródło
Dla mnie (podobnie jak dla innych) odpowiedzią nie było przesłonięcie
hitTest
metody, ale raczejpointInside
metodę. Musiałem to zrobić tylko w dwóch miejscach.Nadzór To pogląd, że gdyby był
clipsToBounds
ustawiony na prawdę, zniknąłby cały ten problem. Oto moje prosteUIView
w Swift 3:class PromiscuousView : UIView { override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return true } }
Podgląd podrzędny Twój przebieg może się różnić, ale w moim przypadku musiałem również nadpisać
pointInside
metodę dla podglądu, który zdecydował, że dotyk jest poza zakresem. Mój jest w Objective-C, więc:- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { WEPopoverController *controller = (id) self.delegate; if (self.takeHitsOutside) { return YES; } else { return [super pointInside:point withEvent:event]; } }
Ta odpowiedź (i kilka innych na to samo pytanie) może pomóc w zrozumieniu.
źródło
aktualizacja swift 4.1
Aby uzyskać zdarzenia dotykowe w tym samym UIView, wystarczy dodać następującą metodę nadpisania, która zidentyfikuje określony widok dotknięty w UIView, który jest umieszczony poza granicami
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let pointforTargetview:CGPoint = self.convert(point, to: btnAngle) if btnAngle.bounds.contains(pointforTargetview) { return self } return super.hitTest(point, with: event) }
źródło