iOS - transférer toutes les touches à travers une vue

141

J'ai une vue superposée à de nombreuses autres vues. J'utilise uniquement le overaly pour détecter un certain nombre de touches sur l'écran, mais à part cela, je ne veux pas que la vue arrête le comportement des autres vues en dessous, qui sont des vues de défilement, etc. Comment puis-je transférer toutes les touches à travers cette vue en superposition? C'est une sous-valeur de UIView.

sol
la source
2
Avez-vous résolu votre problème? Si oui, veuillez accepter la réponse afin qu'il soit plus facile pour les autres de trouver la solution car je suis confronté au même problème.
Khushbu Desai
cette réponse fonctionne bien stackoverflow.com/a/4010809/4833705
Lance Samaria

Réponses:

121

La désactivation de l'interaction utilisateur était tout ce dont j'avais besoin!

Objectif c:

myWebView.userInteractionEnabled = NO;

Rapide:

myWebView.isUserInteractionEnabled = false
rmp251
la source
Cela a fonctionné dans ma situation où j'avais une sous-vue du bouton. J'ai désactivé l'interaction sur la sous-vue et le bouton a fonctionné.
simple_code
2
In Swift 4,myWebView.isUserInteractionEnabled = false
Louis 'LYRO' Dupont
117

Pour passer des touches d'une vue de superposition aux vues en dessous, implémentez la méthode suivante dans l'UIView:

Objectif 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
la source
3
J'ai le même problème, mais ce que je veux, c'est que si je zoome / tourne / déplace la vue avec un geste, il devrait retourner oui et pour l'événement de repos, il devrait renvoyer non, comment puis-je y parvenir?
Minakshi
@Minakshi c'est une question totalement différente, posez une nouvelle question pour cela.
Fattie
mais sachez que cette approche simple NE VOUS PERMET PAS D'AVOIR des boutons et ainsi de suite AU-DESSUS du calque en question!
Fattie
56

C'est un vieux fil de discussion, mais il est apparu lors d'une recherche, alors j'ai pensé ajouter mon 2c. J'ai un UIView de couverture avec des sous-vues, et je veux uniquement intercepter les touches qui ont frappé l'une des sous-vues, j'ai donc modifié la réponse de PixelCloudSt à

-(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
la source
1
Vous devez créer une sous-classe UIView personnalisée (ou une sous-classe de toute autre sous-classe UIView telle que UIImageView ou autre) et remplacer cette fonction. En substance, la sous-classe peut avoir un '@interface' entièrement vide dans le fichier .h, et la fonction ci-dessus être le contenu entier de '@implementation'.
fresidue
Merci, c'est exactement ce dont j'avais besoin pour passer des touches à travers l'espace vide de mon UIView, pour être manipulé par la vue derrière. +100
Danny
41

Version améliorée de la réponse @fresidue. Vous pouvez utiliser cette UIViewsous - classe comme vue transparente en passant des touches en dehors de sa sous-vue. Mise en œuvre en Objective-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

.

et dans 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)
    })
  }
}

POINTE:

Disons alors que vous avez un grand panneau "support", peut-être avec une vue de table derrière. Vous faites le panneau "titulaire" PassthroughView. Cela fonctionnera maintenant, vous pouvez faire défiler le tableau "à travers" le "titulaire".

Mais!

  1. En haut du panneau "support", vous avez des étiquettes ou des icônes. N'oubliez pas, bien sûr, ceux-ci doivent simplement être marqués Interaction de l'utilisateur activée OFF!

  2. En haut du panneau "support", vous avez quelques boutons. N'oubliez pas, bien sûr, ceux-ci doivent simplement être marqués sur l'interaction de l'utilisateur activée!

  3. Notez que quelque peu prêter à confusion, le « titulaire » lui - même - la vue que vous utilisez PassthroughViewsur - doit être marquée interaction utilisateur activée ON ! C'est ON !! (Sinon, le code de PassthroughView ne sera tout simplement jamais appelé.)

Timur Bernikovitch
la source
1
J'ai utilisé la solution donnée pour Swift. Il fonctionne sur iOS 11 mais se bloque à chaque fois sur iOS 10. Affichage d'un accès incorrect. Une idée?
Soumen
Vous avez sauvé mes jours, cela a fonctionné pour passer des touches aux UIViews ci-dessous.
AaoIi
1
N'oubliez pas de vérifier également$0.isUserInteractionEnabled
Sean Thielen
7

J'ai eu un problème similaire avec un UIStackView (mais pourrait être n'importe quelle autre vue). Ma configuration était la suivante: Contrôleur de vue avec containerView et StackView

C'est un cas classique où j'ai un conteneur qui devait être placé en arrière-plan, avec des boutons sur le côté. À des fins de mise en page, j'ai inclus les boutons dans un UIStackView, mais maintenant la partie centrale (vide) de la stackView intercepte les touches :-(

Ce que j'ai fait, c'est créer une sous-classe d'UIStackView avec une propriété définissant la sous-vue qui devrait être touchable. Désormais, tout contact sur les boutons latéraux (inclus dans le tableau * viewsWithActiveTouch *) sera donné aux boutons, tandis que tout contact sur la vue de la pile ailleurs que ces vues ne sera pas intercepté, et donc transmis à tout ce qui se trouve sous la pile. vue.

/** 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
la source
6

Si la vue à laquelle vous souhaitez transférer les touches ne se trouve pas être une sous-vue / supervision, vous pouvez configurer une propriété personnalisée dans votre sous-classe UIView comme suit:

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

Assurez-vous de le synthétiser dans votre .m:

@synthesize forwardableTouchee;

Et puis incluez les éléments suivants dans l'une de vos méthodes UIResponder telles que:

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

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

}

Partout où vous instanciez votre UIView, définissez la propriété forwardableTouchee sur la vue vers laquelle vous souhaitez que les événements soient transférés:

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

Dans 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
la source
4

J'avais besoin de passer des touches à travers un UIStackView. Un UIView à l'intérieur était transparent, mais l'UIStackView consommait toutes les touches. Cela a fonctionné pour moi:

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

Toutes les sous-vues arrangées reçoivent toujours des touches, mais les touches sur l'UIStackView lui-même sont passées à la vue ci-dessous (pour moi, une mapView).

gorkem
la source
2

On dirait que même toi, c'est pas mal de réponses ici, il n'y a personne propre en vitesse dont j'avais besoin. J'ai donc pris la réponse de @fresidue ici et l'ai convertie en swift car c'est ce que la plupart des développeurs veulent utiliser ici.

Cela a résolu mon problème où j'ai une barre d'outils transparente avec un bouton, mais je veux que la barre d'outils soit invisible pour l'utilisateur et que les événements tactiles doivent passer.

isUserInteractionEnabled = false comme certains l'ont indiqué n'est pas une option basée sur mes tests.

 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
la source
1

J'avais quelques étiquettes dans StackView et je n'ai pas eu beaucoup de succès avec les solutions ci-dessus, à la place j'ai résolu mon problème en utilisant le code ci-dessous:

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

Sous-classer 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
    }

}

Ensuite, vous pouvez faire quelque chose comme:

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

Essayez quelque chose comme ça ...

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

Le code ci-dessus, dans votre méthode touchesBegan par exemple, transmettrait les touches à toutes les sous-vues de vue.

Jordan
la source
6
Le problème avec cette approche AFAIK est que tenter de transmettre des touches aux classes du framework UIKit n'aura le plus souvent aucun effet. Selon la documentation d'Apple, les classes du framework UIKit ne sont pas conçues pour recevoir des touches dont la propriété view est définie sur une autre vue. Cette approche fonctionne donc principalement pour les sous-classes personnalisées d'UIView.
maciejs
0

La situation que j'essayais de faire était de construire un panneau de contrôle en utilisant des contrôles à l'intérieur de UIStackView imbriqués. Certains des contrôles avaient UITextField d'autres avec UIButton. En outre, il y avait des étiquettes pour identifier les contrôles. Ce que je voulais faire, c'était mettre un gros bouton «invisible» derrière le panneau de configuration afin que si un utilisateur tapait sur une zone en dehors d'un bouton ou d'un champ de texte, je puisse ensuite l'attraper et agir - principalement ignorer tout clavier s'il s'agissait d'un texte Le champ était actif (resignFirstResponder). Cependant, taper sur une étiquette ou une autre zone vide dans le panneau de configuration ne ferait pas passer les choses. Les discussions ci-dessus ont été utiles pour trouver ma réponse ci-dessous.

Fondamentalement, j'ai sous-classé UIStackView et j'ai écrasé la routine «point (inside: with) pour rechercher le type de contrôles qui nécessitaient le toucher et« ignorer »des choses comme les étiquettes que je voulais ignorer. Il vérifie également l'intérieur de UIStackView afin que les choses puissent rentrer dans la structure du panneau de contrôle.

Le code est peut-être un peu plus détaillé qu'il ne devrait l'être. Mais cela a été utile pour le débogage et, espérons-le, fournit plus de clarté sur ce que fait la routine. Assurez-vous simplement dans Interface Builder de changer la classe de UIStackView en PassThruStack.

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
la source
-1

Comme suggéré par @PixelCloudStv si vous voulez passer touché d'une vue à une autre mais avec un contrôle supplémentaire sur ce processus - sous-classe UIView

//entête

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//la mise en oeuvre

#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

Après dans interfaceBuilder, définissez simplement la classe de View sur TouchView et définissez le rect actif avec votre rect. Vous pouvez également changer et mettre en œuvre une autre logique.

gbk
la source