webentwicklung-frage-antwort-db.com.de

Interaktion jenseits der Grenzen von UIView

Kann ein UIButton (oder ein anderes Steuerelement) Berührungsereignisse empfangen, wenn der Rahmen des UIButton außerhalb des Rahmens des übergeordneten Elements liegt? Wenn ich das versuche, scheint mein UIButton keine Ereignisse empfangen zu können. Wie kann ich das umgehen?

84
ThomasM

Ja. Sie können die Methode hitTest:withEvent: Überschreiben, um eine Ansicht für eine größere Menge von Punkten zurückzugeben, als diese Ansicht enthält. Siehe IView-Klassenreferenz .

Bearbeiten: Beispiel:

- (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: (Nach Klärung :) Um sicherzustellen, dass die Schaltfläche innerhalb der Grenzen des übergeordneten Elements liegt, müssen Sie pointInside:withEvent: im übergeordneten Element, um den Rahmen der Schaltfläche einzuschließen.

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

Beachten Sie , dass der Code zum Überschreiben von pointInside nicht ganz korrekt ist. Wie Summon unten erklärt, mache dies:

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

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

Beachten Sie, dass Sie dies höchstwahrscheinlich mit self.oversizeButton Als IBOutlet in dieser UIView-Unterklasse tun würden. dann können Sie einfach die betreffende "Oversize-Schaltfläche" auf die betreffende spezielle Ansicht ziehen. (Wenn Sie dies aus irgendeinem Grund häufig in einem Projekt tun würden, hätten Sie eine spezielle UIButton-Unterklasse, und Sie könnten Ihre Unteransichtsliste nach diesen Klassen durchsuchen.) Ich hoffe, es hilft.

91
jnic

@jnic, ich arbeite an iOS SDK 5.0 und damit Ihr Code richtig funktioniert, musste ich Folgendes tun:

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

Die Containeransicht in meinem Fall ist ein UIButton, und alle untergeordneten Elemente sind auch UIButtons, die außerhalb der Grenzen des übergeordneten UIButton verschoben werden können.

Beste

23
Summon

In der übergeordneten Ansicht können Sie die Treffertestmethode überschreiben:

- (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];

}

In diesem Fall leiten Sie den Anruf dort weiter, wenn der Punkt innerhalb der Grenzen Ihrer Schaltfläche liegt. Wenn nicht, kehren Sie zur ursprünglichen Implementierung zurück.

19
Ja͢ck

In meinem Fall hatte ich eine Unterklasse UICollectionViewCell, die eine UIButton enthielt. Ich habe clipsToBounds in der Zelle deaktiviert und die Schaltfläche war außerhalb der Zellengrenzen sichtbar. Die Schaltfläche empfing jedoch keine Berührungsereignisse. Ich konnte die Berührungsereignisse auf der Schaltfläche anhand von Jacks Antwort erkennen: https://stackoverflow.com/a/30431157/3344977

Hier ist eine Swift Version:

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)
}
11
user3344977

Schnell:

Wobei targetView die Ansicht ist, in der Sie das Ereignis empfangen möchten (die sich ganz oder teilweise außerhalb des Rahmens der übergeordneten Ansicht befinden kann).

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)
}
8
Luke Bartolomeo

Warum passiert das?

Dies liegt daran, dass Berührungsereignisse, die tatsächlich in dieser Unteransicht auftreten, nicht an diese Unteransicht übermittelt werden, wenn Ihre Unteransicht außerhalb der Grenzen Ihrer Übersicht liegt. Es [~ # ~] wird jedoch [~ # ~] an seine Übersicht geliefert.

Unabhängig davon, ob Teilansichten visuell beschnitten werden oder nicht, wird bei Berührungsereignissen immer das Begrenzungsrechteck der Zielansicht berücksichtigt. Mit anderen Worten, Berührungsereignisse, die in einem Teil einer Ansicht auftreten, der außerhalb des Begrenzungsrechtecks ​​der Ansicht liegt, werden dieser Ansicht nicht übermittelt. Link

Was müssen Sie tun?

Wenn Ihre Übersicht das oben erwähnte Berührungsereignis empfängt, müssen Sie UIKit explizit mitteilen, dass meine Unteransicht diejenige sein soll, die dieses Berührungsereignis empfängt.

Was ist mit dem Code?

Implementiere in deinem Überblick 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)
    }

.

Faszinierende Gotchya: Sie müssen zum "höchsten zu kleinen Überblick" gehen

Sie müssen "nach oben" zur "höchsten" Ansicht gehen, in der sich die Problemansicht außerhalb befindet.

Typisches Beispiel:

Angenommen, Sie haben einen Bildschirm S mit einer Containeransicht C. Die Ansicht des Containeransicht-Controllers ist V. (Rückruf V befindet sich innerhalb von C und hat die gleiche Größe.) V hat eine Unteransicht (möglicherweise eine Schaltfläche) B. B ist das Problem Ansicht, die sich tatsächlich außerhalb von V befindet.

Beachte aber, dass B auch außerhalb von C liegt

In diesem Beispiel müssen Sie die Lösung override hitTest Tatsächlich auf C und nicht auf V anwenden. Wenn Sie es auf V anwenden, geschieht nichts.

8
Scott Zhu

Für mich (wie für andere) war die Antwort nicht , die hitTest -Methode zu überschreiben, sondern die pointInside Methode. Ich musste das nur an zwei Stellen tun.

The Superview Dies ist die Ansicht, dass das ganze Problem verschwinden würde, wenn clipsToBounds auf true gesetzt wäre. Also hier ist mein einfaches UIView in Swift 3:

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

Die Unteransicht Ihr Kilometerstand kann variieren, aber in meinem Fall musste ich auch die pointInside -Methode für die Unteransicht überschreiben, die die entschieden hatte Berührung war außerhalb der Grenzen. Meins ist in Objective-C, also:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    WEPopoverController *controller = (id) self.delegate;
    if (self.takeHitsOutside) {
        return YES;
    } else {
        return [super pointInside:point withEvent:event];
    }
}

Diese Antwort (und einige andere zu derselben Frage) können Ihnen dabei helfen, Ihr Verständnis zu klären.

3
Dan Rosenstark

Swift 4.1 aktualisiert

Um Berührungsereignisse in derselben UIView zu erhalten, müssen Sie nur die folgende Überschreibungsmethode hinzufügen, damit die in UIView getippte Ansicht identifiziert wird, die außerhalb der Grenzen liegt

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)
}
2
Ankit Kushwah