iOS - Alle Berührungen durch eine Ansicht weiterleiten

141

Ich habe eine Ansicht über viele andere Ansichten gelegt. Ich verwende die Overaly nur, um eine bestimmte Anzahl von Berührungen auf dem Bildschirm zu erkennen, aber ansonsten möchte ich nicht, dass die Ansicht das Verhalten anderer Ansichten darunter stoppt, bei denen es sich um Bildlaufansichten usw. handelt. Wie kann ich alle Berührungen weiterleiten? diese Überlagerungsansicht? Es ist eine Subskala von UIView.

Sol
quelle
2
Hast du dein Problem gelöst? Wenn ja, dann akzeptieren Sie bitte die Antwort, damit andere die Lösung leichter finden können, da ich vor dem gleichen Problem stehe.
Khushbu Desai
Diese Antwort funktioniert gut stackoverflow.com/a/4010809/4833705
Lance Samaria

Antworten:

121

Das Deaktivieren der Benutzerinteraktion war alles, was ich brauchte!

Ziel c:

myWebView.userInteractionEnabled = NO;

Schnell:

myWebView.isUserInteractionEnabled = false
rmp251
quelle
Dies funktionierte in meiner Situation, in der ich eine Unteransicht der Schaltfläche hatte. Ich habe die Interaktion in der Unteransicht deaktiviert und die Schaltfläche hat funktioniert.
simple_code
2
In Swift 4,myWebView.isUserInteractionEnabled = false
Louis 'LYRO' Dupont
117

Implementieren Sie die folgende Methode in der UIView, um Berührungen von einer Überlagerungsansicht an die darunter liegenden Ansichten zu übergeben:

Ziel c:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"Passing all touches to the next view (if any), in the view stack.");
    return NO;
}

Swift 5:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    print("Passing all touches to the next view (if any), in the view stack.")
    return false
}
PixelCloudSt
quelle
3
Ich habe das gleiche Problem, aber ich möchte, wenn ich die Ansicht mit einer Geste zoome / drehe / bewege, sollte sie "Ja" und im Ruhezustand "Nein" zurückgeben. Wie kann ich dies erreichen?
Minakshi
@Minakshi das ist eine ganz andere Frage, stell eine neue Frage dafür.
Fattie
Beachten Sie jedoch, dass bei diesem einfachen Ansatz KEINE Schaltflächen usw. auf der betreffenden Ebene vorhanden sind.
Fattie
56

Dies ist ein alter Thread, aber er ist bei einer Suche aufgetaucht, also dachte ich, ich würde meinen 2c hinzufügen. Ich habe ein abdeckendes UIView mit Unteransichten und möchte nur die Berührungen abfangen, die eine der Unteransichten getroffen haben. Deshalb habe ich die Antwort von PixelCloudSt auf geändert

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView* subview in self.subviews ) {
        if ( [subview hitTest:[self convertPoint:point toView:subview] withEvent:event] != nil ) {
            return YES;
        }
    }
    return NO;
}
Fresidue
quelle
1
Sie müssen eine benutzerdefinierte UIView-Unterklasse (oder eine Unterklasse einer anderen UIView-Unterklasse wie UIImageView oder was auch immer) erstellen und diese Funktion überschreiben. Im Wesentlichen kann die Unterklasse ein vollständig leeres '@interface' in der .h-Datei haben, und die obige Funktion ist der gesamte Inhalt der '@implementation'.
Fresidue
Vielen Dank, genau das war es, was ich brauchte, um Berührungen durch den leeren Raum meines UIView zu führen, um von der Ansicht dahinter gehandhabt zu werden. +100
Danny
40

Verbesserte Version der @ fresidue-Antwort. Sie können diese UIViewUnterklasse als transparente Ansicht verwenden, die Berührungen außerhalb der Unteransicht übergibt. Implementierung in 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 pointInside:[self convertPoint:point toView:view] withEvent:event]) {
            return YES;
        }
    }
    return NO;
}

@end

.

und in Swift:

class PassthroughView: UIView {
  override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    return subviews.contains(where: {
      !$0.isHidden
      && $0.isUserInteractionEnabled
      && $0.point(inside: self.convert(point, to: $0), with: event)
    })
  }
}

TRINKGELD:

Angenommen, Sie haben eine große "Halter" -Panel, möglicherweise mit einer Tischansicht dahinter. Sie machen das "Halter" -PanelPassthroughView . Es wird jetzt funktionieren, Sie können die Tabelle "durch" den "Halter" scrollen.

Aber!

  1. Oben auf dem "Halter" -Panel befinden sich einige Beschriftungen oder Symbole. Vergessen Sie nicht, natürlich müssen diese einfach als Benutzerinteraktion aktiviert AUS markiert werden!

  2. Oben auf dem "Halter" -Panel befinden sich einige Tasten. Vergessen Sie nicht, diese müssen natürlich einfach als Benutzerinteraktion aktiviert markiert sein!

  3. Beachten Sie, dass etwas verwirrend, die „Besitzer“ selbst - die Ansicht , die Sie verwenden PassthroughViewauf - markierte Interaktion mit dem Benutzer muss aktiviert sein ON ! Das ist AN !! (Andernfalls wird der Code in PassthroughView einfach nie aufgerufen.)

Timur Bernikovich
quelle
1
Ich habe die für Swift angegebene Lösung verwendet. Es funktioniert unter iOS 11, stürzt jedoch jedes Mal unter iOS 10 ab. Es zeigt schlechten Zugriff an. Irgendeine Idee?
Soumen
Du hast meine Tage gerettet, es hat geklappt, die folgenden UIViews zu berühren.
AaoIi
1
Vergessen Sie nicht, auch nach$0.isUserInteractionEnabled
Sean Thielen
7

Ich hatte ein ähnliches Problem mit einer UIStackView (könnte aber jede andere Ansicht sein). Meine Konfiguration war wie folgt: View Controller mit containerView und StackView

Es ist ein klassischer Fall, in dem ich einen Container habe, der im Hintergrund platziert werden musste, mit Schaltflächen an der Seite. Zu Layoutzwecken habe ich die Schaltflächen in eine UIStackView aufgenommen, aber jetzt berührt der mittlere (leere) Teil der stackView-Abschnitte :-(

Ich habe eine Unterklasse von UIStackView mit einer Eigenschaft erstellt, die die Unteransicht definiert, die berührt werden soll. Jetzt wird jede Berührung der seitlichen Schaltflächen (im Array * viewsWithActiveTouch * enthalten) den Schaltflächen zugewiesen, während jede Berührung der Stapelansicht an einer anderen Stelle als diesen Ansichten nicht abgefangen und daher an eine beliebige Stelle unterhalb des Stapels übergeben wird Aussicht.

/** Subclass of UIStackView that does not accept touches, except for specific subviews given in the viewsWithActiveTouch array */
class NoTouchStackView: UIStackView {

  var viewsWithActiveTouch: [UIView]?

  override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    if let activeViews = viewsWithActiveTouch {
        for view in activeViews {
           if CGRectContainsPoint(view.frame, point) {
                return view

           }
        }
     }
     return nil
   }
}
Frédéric Adda
quelle
6

Wenn die Ansicht, an die Sie die Berührungen weiterleiten möchten, keine Unteransicht / Übersicht ist, können Sie eine benutzerdefinierte Eigenschaft in Ihrer UIView-Unterklasse wie folgt einrichten:

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

Stellen Sie sicher, dass Sie es in Ihrer .m synthetisieren:

@synthesize forwardableTouchee;

Fügen Sie dann Folgendes in eine Ihrer UIResponder-Methoden ein, z.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    [self.forwardableTouchee touchesBegan:touches withEvent:event];

}

Wenn Sie Ihre UIView instanziieren, setzen Sie die Eigenschaft forwardableTouchee auf die Ansicht, an die die Ereignisse weitergeleitet werden sollen:

    SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
    view.forwardableTouchee = someOtherView;
Chris Ladd
quelle
4

In Swift 5

class ThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard let slideView = subviews.first else {
            return false
        }

        return slideView.hitTest(convert(point, to: slideView), with: event) != nil
    }
}
onmyway133
quelle
4

Ich musste Berührungen durch eine UIStackView führen. Ein UIView im Inneren war transparent, aber das UIStackView verbrauchte alle Berührungen. Das hat bei mir funktioniert:

class PassThrouStackView: UIStackView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if view == self {
            return nil
        }
        return view
    }
}

Alle arrangierten Unteransichten erhalten weiterhin Berührungen, aber Berührungen der UIStackView selbst gingen in die Ansicht unten über (für mich eine Kartenansicht).

gorkem
quelle
2

Sieht so aus, als ob es hier eine Menge Antworten gibt, es gibt niemanden, der schnell sauber ist, den ich brauchte. Also habe ich die Antwort von @fresidue hier genommen und sie in schnell konvertiert, da es das ist, was jetzt hauptsächlich Entwickler hier verwenden möchten.

Es hat mein Problem gelöst, bei dem ich eine transparente Symbolleiste mit Schaltfläche habe, aber die Symbolleiste für Benutzer unsichtbar sein soll und Berührungsereignisse durchlaufen werden sollen.

isUserInteractionEnabled = false, wie einige angegeben haben, ist keine Option, die auf meinen Tests basiert.

 override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.hitTest(convert(point, to: subview), with: event) != nil {
            return true
        }
    }
    return false
}
Renetik
quelle
1

Ich hatte einige Labels in StackView und hatte mit den obigen Lösungen keinen großen Erfolg. Stattdessen löste ich mein Problem mit dem folgenden Code:

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

Unterklasse UIStackView:

class PassThrouStackView:UIStackView{
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        for subview in self.arrangedSubviews {
            let convertedPoint = convert(point, to: subview)
            let labelPoint = subview.point(inside: convertedPoint, with: event)
            if (labelPoint){
                return subview
            }

        }
        return nil
    }

}

Dann könnten Sie so etwas tun wie:

class ResponsiveLabel:UILabel{
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Respond to touch
    }
}
Amir.n3t
quelle
0

Versuchen Sie so etwas ...

for (UIView *view in subviews)
  [view touchesBegan:touches withEvent:event];

Der obige Code, beispielsweise in Ihrer touchBegan-Methode, übergibt die Berührungen an alle Unteransichten der Ansicht.

Jordanien
quelle
6
Das Problem bei diesem Ansatz AFAIK ist, dass der Versuch, Berührungen an die Klassen des UIKit-Frameworks weiterzuleiten, meistens keine Auswirkungen hat. Laut Apples Dokumentation sind UIKit-Framework-Klassen nicht dafür ausgelegt, Berührungen zu empfangen, bei denen die Eigenschaft view auf eine andere Ansicht festgelegt ist. Daher funktioniert dieser Ansatz hauptsächlich für benutzerdefinierte Unterklassen von UIView.
Maciejs
0

Die Situation, die ich versuchte, bestand darin, ein Control Panel mit Steuerelementen in verschachtelten UIStackViews zu erstellen. Einige der Steuerelemente hatten UITextFields andere mit UIButton. Es gab auch Etiketten zur Identifizierung der Kontrollen. Was ich tun wollte, war, eine große „unsichtbare“ Schaltfläche hinter das Bedienfeld zu setzen, damit ich, wenn ein Benutzer auf einen Bereich außerhalb einer Schaltfläche oder eines Textfelds tippte, diese abfangen und Maßnahmen ergreifen konnte - in erster Linie jede Tastatur, wenn ein Text vorhanden war Feld war aktiv (resignFirstResponder). Wenn Sie jedoch auf ein Etikett oder einen anderen leeren Bereich im Bedienfeld tippen, werden die Dinge nicht durchgelassen. Die obigen Diskussionen waren hilfreich, um meine Antwort unten zu finden.

Grundsätzlich habe ich UIStackView unterklassifiziert und die Routine „point (inside: with)“ überschrieben, um nach der Art der Steuerelemente zu suchen, die berührt werden müssen, und Dinge wie Beschriftungen, die ich ignorieren wollte, zu „ignorieren“. Es wird auch nach UIStackViews gesucht, damit die Dinge in die Systemsteuerungsstruktur zurückkehren können.

Der Code ist vielleicht etwas ausführlicher als er sein sollte. Aber es war beim Debuggen hilfreich und bietet hoffentlich mehr Klarheit darüber, was die Routine tut. Stellen Sie im Interface Builder sicher, dass Sie die Klasse der UIStackViews in PassThruStack ändern.

class PassThruStack: UIStackView {

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {

    for view in self.subviews {
        if !view.isHidden {
            let isStack = view is UIStackView
            let isButton = view is UIButton
            let isText = view is UITextField
            if isStack || isButton || isText {
                let pointInside = view.point(inside: self.convert(point, to: view), with: event)
                if pointInside {
                    return true
                }
            }
        }
    }
    return false
}

}}

anorskdev
quelle
-1

Wie von @PixelCloudStv vorgeschlagen, wenn Sie Berührungen von einer Ansicht in eine andere werfen möchten, aber zusätzliche Kontrolle über diesen Prozess haben möchten - Unterklasse UIView

//Header

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//Implementierung

#import "TouchView.h"

@implementation TouchView

#pragma mark - Ovverride

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL moveTouch = YES;
    if (CGRectContainsPoint(self.activeRect, point)) {
        moveTouch = NO;
    }
    return moveTouch;
}

@end

Nachdem Sie in interfaceBuilder die View-Klasse auf TouchView gesetzt haben, setzen Sie mit Ihrem Rect das aktive Rect. Sie können auch andere Logik ändern und implementieren.

gbk
quelle