wake-up-neo.com

Wie kann ich hinter einer transparenten UIView auf eine Schaltfläche klicken?

Nehmen wir an, wir haben einen View-Controller mit einer Sub-View. Die Unteransicht nimmt die Mitte des Bildschirms mit 100 px-Rändern an allen Seiten ein. Wir fügen dann ein paar Kleinigkeiten hinzu, die Sie in dieser Unteransicht anklicken können. Wir verwenden die Unteransicht nur, um den neuen Rahmen zu nutzen (x = 0, y = 0 innerhalb der Unteransicht ist in der übergeordneten Ansicht tatsächlich 100.100). 

Stellen Sie sich dann vor, wir hätten etwas hinter der Unteransicht, z. B. ein Menü. Ich möchte, dass der Benutzer eines der "kleinen Dinge" in der Unteransicht auswählen kann, aber wenn dort nichts ist, möchte ich, dass Berührungen durchgehen (da der Hintergrund sowieso klar ist) zu den dahinter liegenden Schaltflächen. 

Wie kann ich das machen? Es sieht so aus, als würde TouchesBegan durchgehen, aber Buttons funktionieren nicht. 

161
Sean Clark Hess

Erstellen Sie eine benutzerdefinierte Ansicht für Ihren Container, und überschreiben Sie die pointInside: -Meldung, um false zurückzugeben, wenn sich der Punkt nicht in einer geeigneten untergeordneten Ansicht befindet.

Schnell:

class PassThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        for subview in subviews {
            if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
                return true
            }
        }
        return false
    }
}

Ziel c:

@interface PassthroughView : UIView
@end

@implementation PassthroughView
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    for (UIView *view in self.subviews) {
        if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
            return YES;
    }
    return NO;
}
@end

Wenn Sie diese Ansicht als Container verwenden, können alle untergeordneten Elemente Berührungen erhalten, die Ansicht selbst ist jedoch für Ereignisse transparent.

286
John Stephen

Ich benutze auch 

myView.userInteractionEnabled = NO;

Keine Notwendigkeit zur Unterklasse Funktioniert gut.

88
akw

Von Apple:

Die Ereignisweiterleitung ist eine von einigen Anwendungen verwendete Technik. Sie leiten Berührungsereignisse weiter, indem Sie die Ereignisbehandlungsmethoden eines anderen Responderobjekts aufrufen. Obwohl dies eine effektive Technik sein kann, sollten Sie sie mit Vorsicht anwenden. Die Klassen des UIKit-Frameworks sind nicht dafür ausgelegt, Berührungen zu erhalten, die nicht an sie gebunden sind ... Wenn Sie Berührungen an andere Responder in Ihrer Anwendung bedingt weiterleiten möchten, sollten alle diese Beantworter Instanzen Ihrer eigenen Unterklasse von UIView sein.

Apples Best Practice :

Senden Sie Ereignisse nicht explizit in die Responder-Kette (über nextResponder). Rufen Sie stattdessen die Superklassenimplementierung auf und überlassen Sie dem UIKit das Durchlaufen der Responder-Kette.

stattdessen können Sie überschreiben:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

in Ihrer UIView-Unterklasse und geben Sie NEIN zurück, wenn Sie möchten, dass diese Berührung in der Antworterkette nach oben gesendet wird (z. B. Ansichten hinter Ihrer Ansicht, die nichts enthalten).

18
jackslash

Eine weitaus einfachere Möglichkeit besteht darin, die Benutzerinteraktion im Interface Builder zu deaktivieren. "Wenn Sie ein Storyboard verwenden"

 enter image description here

7
Jeff

Aufbauend auf dem, was John gepostet hat, folgt ein Beispiel, bei dem Berührungsereignisse alle Unteransichten einer Ansicht mit Ausnahme von Schaltflächen durchlaufen können:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    // Allow buttons to receive press events.  All other views will get ignored
    for( id foundView in self.subviews )
    {
        if( [foundView isKindOfClass:[UIButton class]] )
        {
            UIButton *foundButton = foundView;

            if( foundButton.isEnabled  &&  !foundButton.hidden &&  [foundButton pointInside:[self convertPoint:point toView:foundButton] withEvent:event] )
                return YES;
        }        
    }
    return NO;
}
6
Tod Cunningham

In letzter Zeit habe ich eine Klasse geschrieben, die mir dabei helfen wird. Wenn Sie es als benutzerdefinierte Klasse für eine UIButton oder UIView verwenden, werden Berührungsereignisse übergeben, die auf einem transparenten Pixel ausgeführt wurden.

Diese Lösung ist etwas besser als die akzeptierte Antwort, da Sie immer noch auf eine UIButton klicken können, die sich unter einer halbtransparenten UIView befindet, während der nicht transparente Teil der UIView immer noch auf Berührungsereignisse reagiert.

GIF

Wie Sie in der GIF sehen können, handelt es sich bei der Giraffe-Schaltfläche um ein einfaches Rechteck. Berührungsereignisse für transparente Bereiche werden an die gelbe Variable UIButton weitergeleitet.

Link zur Klasse

6
Segev

Die von mir gewählte Lösung funktionierte nicht vollständig für mich. Ich schätze, es lag daran, dass ich einen TabBarController in der Hierarchie hatte (wie in einem Kommentar darauf hingewiesen wird), dass es tatsächlich einige Bereiche der Benutzeroberfläche berührte, die jedoch mit meiner verwirrten Die Fähigkeit von tableView, Berührungsereignisse abzufangen, überschritt schließlich hitTest in der Ansicht, die ich Berührungen ignorieren und die Unteransichten dieser Ansicht behandeln lassen soll

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    UIView *view = [super hitTest:point withEvent:event];
    if (view == self) {
        return nil; //avoid delivering touch events to the container view (self)
    }
    else{
        return view; //the subviews will still receive touch events
    }
}
3
Emilio

Gemäß dem "iPhone Application Programming Guide":

Deaktivieren der Bereitstellung von Berührungsereignissen . Standardmäßig erhält eine Ansicht den Touch Ereignisse, Sie können jedoch die userInteractionEnabled-Eigenschaft auf NO .__ setzen. die Bereitstellung von Ereignissen deaktivieren. Eine Ansicht empfängt auch keine Ereignisse, wenn sie ausgeblendet ist oder wenn es transparent ist.

http://developer.Apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/EventHandling/EventHandling.html

Updated: Beispiel entfernt - Frage erneut lesen ...

Verfügen Sie über eine Gestenverarbeitung für die Ansichten, die möglicherweise die Taps verarbeiten, bevor die Schaltfläche es erhält? Funktioniert die Schaltfläche, wenn Sie keine transparente Ansicht darüber haben?

Irgendwelche Codebeispiele von nicht funktionierendem Code?

2
LK.

Ich habe dafür eine Kategorie erstellt.

ein wenig schwindelig und die Aussicht ist golden.

Die Kopfzeile

//UIView+PassthroughParent.h
@interface UIView (PassthroughParent)

- (BOOL) passthroughParent;
- (void) setPassthroughParent:(BOOL) passthroughParent;

@end

Die Implementierungsdatei

#import "UIView+PassthroughParent.h"

@implementation UIView (PassthroughParent)

+ (void)load{
    Swizz([UIView class], @selector(pointInside:withEvent:), @selector(passthroughPointInside:withEvent:));
}

- (BOOL)passthroughParent{
    NSNumber *passthrough = [self propertyValueForKey:@"passthroughParent"];
    if (passthrough) return passthrough.boolValue;
    return NO;
}
- (void)setPassthroughParent:(BOOL)passthroughParent{
    [self setPropertyValue:[NSNumber numberWithBool:passthroughParent] forKey:@"passthroughParent"];
}

- (BOOL)passthroughPointInside:(CGPoint)point withEvent:(UIEvent *)event{
    // Allow buttons to receive press events.  All other views will get ignored
    if (self.passthroughParent){
        if (self.alpha != 0 && !self.isHidden){
            for( id foundView in self.subviews )
            {
                if ([foundView alpha] != 0 && ![foundView isHidden] && [foundView pointInside:[self convertPoint:point toView:foundView] withEvent:event])
                    return YES;
            }
        }
        return NO;
    }
    else {
        return [self passthroughPointInside:point withEvent:event];// Swizzled
    }
}

@end

Sie müssen meine Swizz.h und Swizz.m hinzufügen

gefunden hier

Anschließend importieren Sie einfach UIView + PassthroughParent.h in Ihre {Project} -Prefix.pch-Datei, und jede Ansicht verfügt über diese Fähigkeit.

jede Ansicht wird Punkte enthalten, aber keine der Leerstellen.

Ich empfehle auch, einen klaren Hintergrund zu verwenden.

myView.passthroughParent = YES;
myView.backgroundColor = [UIColor clearColor];

EDIT 

Ich habe meine eigene Eigentumstasche erstellt, die zuvor nicht enthalten war.

Header-Datei

// NSObject+PropertyBag.h

#import <Foundation/Foundation.h>

@interface NSObject (PropertyBag)

- (id) propertyValueForKey:(NSString*) key;
- (void) setPropertyValue:(id) value forKey:(NSString*) key;

@end

Implementierungsdatei

// NSObject+PropertyBag.m

#import "NSObject+PropertyBag.h"



@implementation NSObject (PropertyBag)

+ (void) load{
    [self loadPropertyBag];
}

+ (void) loadPropertyBag{
    @autoreleasepool {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Swizz([NSObject class], NSSelectorFromString(@"dealloc"), @selector(propertyBagDealloc));
        });
    }
}

__strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag
- (id) propertyValueForKey:(NSString*) key{
    return [[self propertyBag] valueForKey:key];
}
- (void) setPropertyValue:(id) value forKey:(NSString*) key{
    [[self propertyBag] setValue:value forKey:key];
}
- (NSMutableDictionary*) propertyBag{
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
    NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:@"%p",self]];
    if (propBag == nil){
        propBag = [NSMutableDictionary dictionary];
        [self setPropertyBag:propBag];
    }
    return propBag;
}
- (void) setPropertyBag:(NSDictionary*) propertyBag{
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
    [_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:@"%p",self]];
}

- (void)propertyBagDealloc{
    [self setPropertyBag:nil];
    [self propertyBagDealloc];//Swizzled
}

@end
2
The Lazy Coder

Swift 3

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.frame.contains(point) {
            return true
        }
    }
    return false
}
2
Barath

Soweit ich weiß, sollten Sie dazu in der Lage sein, die hitTest: -Methode zu überschreiben. Ich habe es ausprobiert, konnte aber nicht richtig funktionieren. 

Am Ende habe ich eine Reihe transparenter Ansichten um das berührbare Objekt erstellt, so dass sie es nicht verdeckten. Ein bisschen hack für mein Problem, das hat gut funktioniert.

1
Liam

Mit Tipps aus den anderen Antworten und der Dokumentation von Apple habe ich diese einfache Bibliothek zur Lösung Ihres Problems erstellt:
https://github.com/natrosoft/NATouchThroughView
Es macht es einfach, Ansichten in Interface Builder zu zeichnen, die Berührungen an eine zugrunde liegende Ansicht übergeben sollen.

Ich denke, das Swizzling-Verfahren ist übertrieben und in der Produktion sehr gefährlich, da Sie direkt mit der Basisimplementierung von Apple herumspielen und eine anwendungsweite Änderung vornehmen, die unbeabsichtigte Folgen haben kann.

Es gibt ein Demo-Projekt, und hoffentlich leistet das README gute Erklärungen, was zu tun ist. Um das OP zu adressieren, würden Sie die klare UIView, die die Schaltflächen enthält, in die Klasse NATouchThroughView im Interface Builder ändern. Dann finden Sie die klare UIView, die das Menü überdeckt, das Sie abtippen möchten. Ändern Sie diese UIView in die Klasse NARootTouchThroughView im Interface Builder. Es kann sogar die Root-UIView Ihres View-Controllers sein, wenn diese Berührungen zum darunterliegenden View-Controller durchgehen sollen. Schauen Sie sich das Demo-Projekt an, um zu sehen, wie es funktioniert. Es ist wirklich ziemlich einfach, sicher und nicht invasiv 

1
n8tr

Wenn Sie sich nicht die Mühe machen, UIView einer Kategorie oder Unterklasse zu verwenden, können Sie auch die Schaltfläche nach vorne bringen, sodass sie vor der transparenten Ansicht angezeigt wird. Je nach Ihrer Anwendung ist dies nicht immer möglich, aber es hat bei mir funktioniert. Sie können den Button jederzeit zurückbringen oder ausblenden.

0
Shaked Sayag

Stellen Sie eine backgroundColor Ihrer transparentView als UIColor(white:0.000, alpha:0.020) ein. Dann können Sie Berührungsereignisse in touchesBegan/touchesMoved-Methoden erhalten. Platzieren Sie den Code unten an einer Stelle, an der Ihre Ansicht aktiviert ist:

self.alpha = 1
self.backgroundColor = UIColor(white: 0.0, alpha: 0.02)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true
0
Agisight

Ich verwende das anstatt den Methodenpunkt zu überschreiben (innerhalb: CGPoint, mit: UIEvent)

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.point(inside: point, with: event) else { return nil }
        return self
    }
0
Wei

Versuche dies 

class PassthroughToWindowView: UIView {
        override func test(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            var view = super.hitTest(point, with: event)
            if view != self {
                return view
            }

            while !(view is PassthroughWindow) {
                view = view?.superview
            }
            return view
        }
    } 
0
Psajho Nub