2011-03-25 11 views
71

Czy UIButton (lub jakakolwiek inna kontrola) może odbierać zdarzenia dotykowe, gdy ramka Uibuttonu znajduje się poza ramką rodzica? Gdy próbuję tego, mój UIButton nie wydaje się być w stanie odbierać żadnych zdarzeń. Jak to obejść?interakcja wykraczająca poza granice widoku

+2

Jest to również dobre i istotne (a może dupe): http://stackoverflow.com/a/31420820/8047 –

+0

Działa to doskonale dla mnie. https://developer.apple.com/library/ios/qa/qa2013/qa1812.html Najlepiej –

Odpowiedz

79

Tak. Można zastąpić metodę hitTest:withEvent:, aby zwrócić widok dla większego zbioru punktów niż ten widok. Zobacz UIView Class Reference.

Edit: 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; 
} 

Edit 2: (Po wyjaśnienie :) W celu zapewnienia, że ​​przycisk jest traktowane jako mieszczące się granicach jednostki dominującej, trzeba zastąpić pointInside:withEvent: w rodzic, aby dołączyć ramkę przycisku.

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 
{ 
    if (CGRectContainsPoint(self.view.bounds, point) || 
     CGRectContainsPoint(button.view.frame, point)) 
    { 
     return YES; 
    } 
    return NO; 
} 

Uwaga kod właśnie tam nadrzędne pointInside nie jest całkiem poprawne. Przywołanie wyjaśnia jak poniżej, to zrobić:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 
    { 
    if (CGRectContainsPoint(self.oversizeButton.frame, point)) 
     return YES; 

    return [super pointInside:point withEvent:event]; 
    } 

Zauważ, że chcesz zrobić to bardzo prawdopodobne ze self.oversizeButton jako IBOutlet w tym UIView podklasy; wtedy możesz po prostu przeciągnąć "sporny przycisk" do danego widoku specjalnego. (Lub, jeśli z jakiegoś powodu robiłeś to dużo w projekcie, miałbyś specjalną podklasę UIButton i mógłbyś przejrzeć swoją listę podrzędną dla tych klas.) Mam nadzieję, że to pomaga.

+0

Czy możesz mi pomóc w dalszej implementacji tego poprawnie z przykładowym kodem? Przeczytałem również odnośnik i z powodu następującej linii jestem zdezorientowany: "Punkty, które leżą poza granicami odbiorcy, nigdy nie są zgłaszane jako trafienia, nawet jeśli faktycznie znajdują się w jednym z subskrybentów odbiorcy. Subviews może rozciągać się wizualnie poza granice ich rodzica, jeśli właściwość clipsToBounds widoku rodzica jest ustawiona na NIE, jednak test trafień zawsze ignoruje punkty poza granicami widoku rodzica." – ThomasM

+0

To właśnie ta część sprawia, że ​​wydaje mi się, że nie jest to rozwiązanie, którego potrzebuję:" Jednak testowanie trafień zawsze ignoruje punkty poza granicami widoku rodzica ". Ale mogę się mylić, czasami znajduję odniesienia do jabłka naprawdę mylące .. – ThomasM

+0

Ach, rozumiem teraz.Musisz zastąpić rodzica 'pointInside: withEvent:', aby dołączyć ramkę przycisku.Zamówię przykładowy kod do mojej odpowiedzi powyżej. – jnic

21

@jnic pracuję na iOS SDK 5.0 i aby otrzymać kod działa prawo Musiałem to zrobić:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { 
if (CGRectContainsPoint(button.frame, point)) { 
    return YES; 
} 
return [super pointInside:point withEvent:event]; } 

Widok pojemnik w moim przypadku jest UIButton i wszystkie elementy podrzędne są także UIButtons, które mogą poruszać się poza granicami macierzystego UIButton.

Najlepszy

6

SWIFT:

Gdzie TargetView jest pogląd chcesz otrzymywać zdarzenia (które mogą być w całości lub częściowo znajduje się poza ramy swojego widzenia dominującej).

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) 
} 
13

W rodzica zobaczyć można zastąpić metodę testową obrażeń:

- (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 tym przypadku, jeśli temperatura spadnie w granicach swojej przycisku przekierować tam wezwanie; jeśli nie, przywróć pierwotną implementację.

2

W moim przypadku miałem podklasę UICollectionViewCell, która zawierała UIButton. Wyłączyłem clipsToBounds na komórce i przycisk był widoczny poza granicami komórki. Jednak przycisk nie był odbierany zdarzenia dotykowego.I był w stanie wykryć zdarzenia dotknięcie przycisku za pomocą Jacka odpowiedź: https://stackoverflow.com/a/30431157/3344977

Oto Swift wersja:

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) 
} 
0

Dla mnie (jak dla innych), odpowiedź była nie aby przesłonić Metoda hitTest, ale raczej metoda pointInside. Musiałem to zrobić tylko w dwóch miejscach.

Superview Jest to pogląd, że gdyby miał clipsToBounds ustawiona na wartość true, to by ten cały problem znika. Więc oto moja prosta UIView w Swift 3:

class PromiscuousView : UIView { 
    override func point(inside point: CGPoint, 
       with event: UIEvent?) -> Bool { 
     return true 
    } 
} 

podrzędny Twój przebieg mogą się różnić, ale w moim przypadku miałem również nadpisać metodę pointInside dla podrzędny, który został zdecydował dotyk był z miedza. Kopalnia 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]; 
    } 
} 

This answer (i kilka innych w tej samej kwestii) może pomóc wyjaśnić swoje zrozumienie.

1

Dlaczego tak się dzieje?

Dzieje się tak dlatego, że kiedy twoje wyskoki znajdują się poza granicami twojego podwładnego, wydarzenia dotykowe, które faktycznie mają miejsce w tym widoku, nie zostaną dostarczone do tego podglądu. Jednakże, WILL zostanie dostarczony do jego superview.

Bez względu na to, czy podobieństwa są obcięte wizualnie, czy też nie, wydarzenia dotykowe zawsze respektują prostokąt granic podglądu widoku docelowego. Innymi słowy, zdarzenia dotyku występujące w części widoku, która leży poza zasięgiem prostokąta widoku superwizora, nie są dostarczane do tego widoku. Link

Co należy zrobić?

Gdy twoje superview otrzyma wspomniane wyżej wydarzenie dotykowe, będziesz musiał wyraźnie powiedzieć UIKit, że mój podgląd powinien być tym, który otrzymał to zdarzenie dotykowe.

Co z kodem?

W swojej SuperView, wdrożyć func hitTest(_ point: CGPoint, with event: UIEvent?)

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 
     if isHidden || alpha == 0 || clipsToBounds { return nil } 
     // 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 nil 
    } 
+1

Dziękuję za to! Spędziłem wiele godzin nad kwestią wynikającą z tego. Naprawdę doceniam twoje poświęcenie czasu na opublikowanie tego. :) – ClayJ

Powiązane problemy