UIButton in einer Ansicht mit einem UITapGestureRecognizer


184

Ich habe Blick mit einem UITapGestureRecognizer. Wenn ich also auf die Ansicht tippe, wird über dieser Ansicht eine andere Ansicht angezeigt. Diese neue Ansicht verfügt über drei Schaltflächen. Wenn ich jetzt auf eine dieser Tasten drücke, wird die Tastenaktion nicht angezeigt, sondern nur die Tippgeste. Daher kann ich diese Schaltflächen nicht mehr verwenden. Was kann ich tun, um die Ereignisse auf diese Schaltflächen zu übertragen? Das Seltsame ist, dass die Schaltflächen immer noch hervorgehoben werden.

Ich kann den UITapGestureRecognizer nicht einfach entfernen, nachdem ich ihn erhalten habe. Denn damit kann auch die neue Ansicht entfernt werden. Bedeutet, ich möchte ein Verhalten wie die Vollbild-Videosteuerung .

Antworten:


256

Sie können Ihren Controller oder Ihre Ansicht (je nachdem, was den Gestenerkenner erstellt) als Delegierten des festlegen UITapGestureRecognizer. Dann können Sie im Delegaten implementieren -gestureRecognizer:shouldReceiveTouch:. In Ihrer Implementierung können Sie testen, ob die Berührung zu Ihrer neuen Unteransicht gehört, und in diesem Fall den Gestenerkenner anweisen, sie zu ignorieren. So etwas wie das Folgende:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    // test if our control subview is on-screen
    if (self.controlSubview.superview != nil) {
        if ([touch.view isDescendantOfView:self.controlSubview]) {
            // we touched our control surface
            return NO; // ignore the touch
        }
    }
    return YES; // handle the touch
}

In meiner Header-Datei ließ ich meine Ansicht UIGestoreRegognizerDelegate implementieren und fügte in meiner .mi Ihren Code oben hinzu. Der Hahn tritt nie in diese Methode ein, er geht direkt zu meinem Handler. Irgendwelche Ideen?
kmehta

1
@kmehta Sie haben höchstwahrscheinlich vergessen, die UIGestureRecognizer-Delegateneigenschaft festzulegen.
Bis

Kann diese Antwort verallgemeinert werden, um alle Fälle zu behandeln, in denen eine IBAction vorliegt?
Martin Wickman

@Martin IBAction wird bis zu kompiliert, voidsodass zur Laufzeit keine Informationen zurückbleiben, die zur Erkennung verwendet werden können. Sie können jedoch die Ansichtshierarchie durchgehen, nach Steuerelementen suchen UIControlund diese zurückgeben, NOwenn Sie Steuerelemente finden.
Lily Ballard

Danke dafür, es hilft mir. Wenn sich Ihre Schaltfläche in einer UIImageView befindet, stellen Sie sicher, dass userInteractionEnabled der imageView auf YES gesetzt ist.
James075

156

Als Folge von Caseys Folge von Kevin Ballards Antwort:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        if ([touch.view isKindOfClass:[UIControl class]]) {
            // we touched a button, slider, or other UIControl
            return NO; // ignore the touch
        }
    return YES; // handle the touch
}

Dadurch funktionieren im Grunde alle Benutzereingabetypen von Steuerelementen wie Schaltflächen, Schiebereglern usw.


1
Dies ist eine flexible Lösung, die begleitet werden kann, um bei setCancelsTouchesInView = NOder Interaktion mit Steuerelementen keine Tippgeste auszulösen. Sie können es jedoch besser schreiben:return ![touch.view isKindOfClass:[UIControl class]];
griga13

Swift 4+ Lösung: Rückkehr! (Touch.view ist UIControl)
Nteissler

103

Diese Antwort finden Sie hier: Link

Sie können auch verwenden

tapRecognizer.cancelsTouchesInView = NO;

Dies verhindert, dass der Hahnerkenner der einzige ist, der alle Hähne erfasst

UPDATE - Michael erwähnte den Link zu der Dokumentation, die diese Eigenschaft beschreibt: cancelsTouchesInView


8
Dies sollte die akzeptierte Antwort IMO sein. Auf diese Weise kann jede Unteransicht das Tippen für sich alleine handhaben und verhindert, dass die Ansicht, an die ein Tabrecognizer angehängt ist, das Tippen "hochhebt". Sie müssen keinen Delegaten implementieren, wenn Sie lediglich eine Ansicht anklickbar machen möchten (um den Ersthelfer zurückzutreten / die Tastatur in Textfeldern usw. auszublenden).
EeKay

4
Dies sollte definitiv die akzeptierte Antwort sein. Diese Antwort hat mich veranlasst, die Apple-Dokumentation richtig zu lesen , wodurch deutlich wird, dass der Gestenerkenner verhindert, dass Unteransichten die erkannten Ereignisse erhalten, sofern Sie dies nicht tun.
Michael van der Westhuizen

1
Das hat früher funktioniert, aber jetzt funktioniert es nicht mehr für mich. Vielleicht, weil ich eine scrollView unter der Schaltfläche habe? Ich musste den Delegierten wie in den obigen Antworten implementieren.
Xissburg

7
Nachdem ich ein wenig experimentiert hatte, bemerkte ich, dass dies nur funktioniert, wenn die Ansicht, die den Gestenerkenner enthält, ein Geschwister des UIControl ist, und nicht, wenn die Ansicht das übergeordnete Element des UIControl ist.
Xissburg

6
Funktioniert nicht wirklich wie beabsichtigt ... JA, es verhindert, dass Tap Tap Erkenner das Ereignis auf UIControl essen. Es wird jedoch immer noch die Erkennungsaktion ausgeführt, bei der gleichzeitig 2 Aktionen ausgeführt werden.
Tek Yin

71

Als Folge der Antwort von Kevin Ballard hatte ich das gleiche Problem und verwendete schließlich diesen Code:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if ([touch.view isKindOfClass:[UIButton class]]){
        return NO;
    }
    return YES;
}

Es hat den gleichen Effekt, funktioniert jedoch für jeden UIButton in jeder Ansichtstiefe (mein UIButton war mehrere Ansichten tief und der Delegierte von UIGestureRecognizer hatte keinen Verweis darauf.)


6
Ich will hier kein Schwanz sein, aber es ist wirklich JA und NEIN. Bitte verwenden Sie Kakao-Konventionen. TRUE / FALSE / NULL ist für die CoreFoundation-Schicht.
Steipete

Nach dem, was ich gelesen habe, wurden JA und NEIN tatsächlich zur besseren Lesbarkeit erstellt. Lesbarkeit ist subjektiv. Es ergibt sich aus den Namenskonventionen für Methoden und Eigenschaften, über die nicht jeder übermäßig besorgt ist. Wenn Sie die Namenskonvention strikt einhalten möchten, verwenden Sie JA und NEIN für alle NSWhatever. Ich werde jedoch sagen, dass Sie, wenn Sie Entwickler befragen, gemischte Meinungen darüber erhalten, welche davon besser lesbar ist [button setHidden: YES]; -oder- [button setHidden: TRUE];
Pimp Juice McJones

1
Ich denke, was ich damit sagen will, ist, dass es schwer zu leugnen ist, dass es in einigen Fällen subjektiv ist, wenn die Prämisse der Namenskonventionen Lesbarkeit ist. Wenn Sie ein Purist sind, werden Sie diese Aussage nicht verstehen.
Pimp Juice McJones

2
Es mag subjektiv erscheinen, aber nach einer Weile der Kakaoprogrammierung geraten Sie wirklich in den semantisch genaueren Codefluss ... das "WAHRE" scheint jetzt nach Jahren von Objective-C wirklich falsch zu sein, weil es nicht sehr "versteckt" ist Gut.
Kendall Helmstetter Gelner

Seit wann können / können Sie eine Tap-Geste als Touch Up Inside UITouch-Ereignis an einen UIButton übergeben (letzteres ist das Ereignis, das durch Tippen auf einen UIButton generiert wird)? Es erscheint doppelt und falsch , einen Tippgestenerkenner für eine Schaltfläche zu erstellen, der bereits 15 Berührungsereignisse mit einer Tippgeste zugeordnet sind. Ich würde gerne Code sehen, keine Vermutungen.
James Bush

10

In iOS 6.0 und höher verhindern Standardsteuerungsaktionen ein überlappendes Verhalten der Gestenerkennung. Die Standardaktion für eine Schaltfläche ist beispielsweise ein einziges Tippen. Wenn Sie einen einzelnen Tipp-Gestenerkenner an die übergeordnete Ansicht einer Schaltfläche angehängt haben und der Benutzer auf die Schaltfläche tippt, empfängt die Aktionsmethode der Schaltfläche das Berührungsereignis anstelle des Gestenerkenners. Dies gilt nur für die Gestenerkennung, die die Standardaktion für ein Steuerelement überlappt. Dazu gehören: .....

Aus Apples API-Dokument


8

Diese Antworten waren unvollständig. Ich musste mehrere Beiträge lesen, um diese boolesche Operation zu verwenden.

Fügen Sie dies in Ihre * .h-Datei ein

@interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>

Fügen Sie dies in Ihre * .m-Datei ein

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {

    NSLog(@"went here ...");

    if ([touch.view isKindOfClass:[UIControl class]])
    {
        // we touched a button, slider, or other UIControl
        return NO; // ignore the touch
    }
    return YES; // handle the touch
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.



    //tap gestrure
    UITapGestureRecognizer *tapGestRecog = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenTappedOnce)];
    [tapGestRecog setNumberOfTapsRequired:1];
    [self.view addGestureRecognizer:tapGestRecog];


// This line is very important. if You don't add it then your boolean operation will never get called
tapGestRecog.delegate = self;

}


-(IBAction) screenTappedOnce
{
    NSLog(@"screenTappedOnce ...");

}

7

Habe von hier aus einen anderen Weg gefunden . Es erkennt die Berührung, ob in jeder Taste oder nicht.

(1) pointInside: withEvent: (2) locationInView:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
       shouldReceiveTouch:(UITouch *)touch {
    // Don't recognize taps in the buttons
    return (![self.button1 pointInside:[touch locationInView:self.button1] withEvent:nil] &&
            ![self.button2 pointInside:[touch locationInView:self.button2] withEvent:nil] &&
            ![self.button3 pointInside:[touch locationInView:self.button3] withEvent:nil]);
}

Ich habe alle anderen Lösungen dafür ausprobiert, aber der Ansatz -pointInside: withEvent: war der einzige, der für mich funktioniert hat.
Kenny Wyland

3

Hier ist die Swift-Version von Lily Ballards Antwort , die für mich funktioniert hat:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if (scrollView.superview != nil) {
        if ((touch.view?.isDescendantOfView(scrollView)) != nil) { return false }
    }
    return true
}

3

Swift 5

Schaltfläche in der Übersicht mit Tapgesture

 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let _ = touch.view as? UIButton { return false }
    return true
}

In meinem Fall die Implementierung von hitTest für mich funktioniert. Ich hatte Sammlungsansicht mit Knopf

Diese Methode durchläuft die Ansichtshierarchie, indem sie die point(inside:with:)Methode jeder Unteransicht aufruft, um zu bestimmen, welche Unteransicht ein Berührungsereignis erhalten soll. Wenn point(inside:with:)true zurückgegeben wird, wird die Hierarchie der Unteransicht auf ähnliche Weise durchlaufen, bis die vorderste Ansicht mit dem angegebenen Punkt gefunden wird.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard isUserInteractionEnabled else { return nil }

    guard !isHidden else { return nil }

    guard alpha >= 0.01 else { return nil }

    guard self.point(inside: point, with: event) else { return nil }

    for eachImageCell in collectionView.visibleCells {
        for eachImageButton in eachImageCell.subviews {
            if let crossButton = eachImageButton as? UIButton {
                if crossButton.point(inside: convert(point, to: crossButton), with: event) {
                    return crossButton
                }
            }
        }
    }
    return super.hitTest(point, with: event)
}

1

Sie können den UITapGestureRecognizer daran hindern, andere Ereignisse abzubrechen (z. B. das Tippen auf Ihre Schaltfläche), indem Sie den folgenden Booleschen Wert festlegen:

    [tapRecognizer setCancelsTouchesInView:NO];

1

Wenn Ihr Szenario so ist:

Sie haben eine einfache Ansicht und einige UIButtons, UITextField-Steuerelemente, die dieser Ansicht als Unteransichten hinzugefügt wurden. Jetzt möchten Sie die Tastatur schließen, wenn Sie eine andere Stelle in der Ansicht als die Steuerelemente berühren (Unteransichten, die Sie hinzugefügt haben).

Dann ist die Lösung:

Fügen Sie Ihrem XYZViewController.m die folgende Methode hinzu (die Ihre Ansicht hat)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.view endEditing:YES];
}

Zuerst habe ich es mit der Antwort von @cdasher versucht , aber in meinem Fall erhalte ich sogar beim Klicken auf die Schaltfläche touch.view als meine "Ansicht" in ViewController, aber nicht als mein "UIButton" -Steuerelement. Kann jemand sagen, warum die Ansichtseigenschaft der Berührung nicht die ursprüngliche Ansicht
zurückgibt,

1
Funktioniert nicht, wenn Sie eine Bildlaufansicht oder eine andere Ansicht haben, die die Berührungsereignisse beansprucht.
lkraider

1

Wenn Sie die Antwort von cdasher optimieren, erhalten Sie

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch 
{
    return ![touch.view isKindOfClass:[UIControl class]];
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.