iOS: inoltra tutti i tocchi attraverso una vista


141

Ho una vista sovrapposta a molte altre viste. Sto solo usando l'overaly per rilevare un certo numero di tocchi sullo schermo, ma a parte questo non voglio che la vista interrompa il comportamento di altre viste sottostanti, che sono viste di scorrimento, ecc. Come posso inoltrare tutti i tocchi attraverso questa vista overlay? È un sottocalco di UIView.


2
Hai risolto il tuo problema? Se sì, allora accetta la risposta in modo che sia più facile per gli altri trovare la soluzione perché sto affrontando lo stesso problema.
Khushbu Desai,

questa risposta funziona bene stackoverflow.com/a/4010809/4833705
Lance Samaria

Risposte:


121

Disabilitare l'interazione dell'utente era tutto ciò di cui avevo bisogno!

Objective-C:

myWebView.userInteractionEnabled = NO;

Swift:

myWebView.isUserInteractionEnabled = false

Questo ha funzionato nella mia situazione in cui avevo una visione d'insieme del pulsante. Ho disabilitato l'interazione sulla sottoview e il pulsante ha funzionato.
simple_code

2
In Swift 4,myWebView.isUserInteractionEnabled = false
Louis 'LYRO' Dupont,

117

Per passare i tocchi da una vista sovrapposta alle viste sottostanti, implementare il seguente metodo in UIView:

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

3
Ho lo stesso problema, ma quello che voglio è se sto ingrandendo / ruotando / muovendo la vista con un gesto, allora dovrebbe restituire sì e per il resto dovrebbe restituire no, come posso ottenere questo?
Minakshi,

@Minakshi è una domanda completamente diversa, fai una nuova domanda per quello.
Fattie,

ma tieni presente che questo semplice approccio NON ti consente di avere pulsanti e così via IN cima al livello in questione!
Fattie,

56

Questo è un vecchio thread, ma è venuto su una ricerca, quindi ho pensato di aggiungere il mio 2c. Ho una UIView di copertura con le sottoview e voglio solo intercettare i tocchi che colpiscono una delle sottoview, quindi ho modificato la risposta di PixelCloudSt a

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

1
È necessario creare una sottoclasse UIView personalizzata (o una sottoclasse di qualsiasi altra sottoclasse UIView come UIImageView o altro) e sovrascrivere questa funzione. In sostanza, la sottoclasse può avere un '@interface' completamente vuoto nel file .h e la funzione sopra è l'intero contenuto di '@implementation'.
fresidue

Grazie, questo è esattamente ciò di cui avevo bisogno per passare i tocchi attraverso lo spazio vuoto del mio UIView, per essere gestito dalla vista dietro. +100
Danny il

41

Versione migliorata della risposta @fresidue. È possibile utilizzare questa UIViewsottoclasse come vista trasparente che passa tocchi all'esterno della sua sottoview. Implementazione in 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

.

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

MANCIA:

Supponiamo che tu abbia un grande pannello "titolare", forse con una vista da tavolo dietro. Si crea il pannello "titolare" PassthroughView. Ora funzionerà, è possibile scorrere la tabella "attraverso" il "titolare".

Ma!

  1. Sulla parte superiore del pannello "titolare" ci sono alcune etichette o icone. Non dimenticare, ovviamente, quelli devono essere semplicemente contrassegnati come abilitati per l'interazione dell'utente OFF!

  2. Sulla parte superiore del pannello "titolare" sono presenti alcuni pulsanti. Non dimenticare, ovviamente, questi devono semplicemente essere contrassegnati come abilitati per l'interazione dell'utente ON!

  3. Nota che in qualche modo confuso, il "titolare" stesso - la vista che usi PassthroughViewsu - deve essere contrassegnato come abilitato per l'interazione dell'utente ON ! È ON !! (Altrimenti, il codice in PassthroughView semplicemente non verrà mai chiamato.)


1
Ho usato la soluzione fornita per Swift. Funziona su iOS 11 ma si blocca ogni volta su iOS 10. Mostra accesso errato. Qualche idea?
Soumen,

Mi hai salvato i giorni, ha funzionato per passare tocchi alle UIVview sottostanti.
AaoIi,

1
Non dimenticare di controllare anche$0.isUserInteractionEnabled
Sean Thielen,

7

Ho avuto un problema simile con un UIStackView (ma potrebbe essere qualsiasi altra vista). La mia configurazione era la seguente: Visualizza controller con containerView e StackView

È un caso classico in cui ho un contenitore che doveva essere posizionato in background, con pulsanti laterali. Ai fini del layout, ho incluso i pulsanti in un UIStackView, ma ora la parte centrale (vuota) degli stack intercetta i tocchi :-(

Quello che ho fatto è creare una sottoclasse di UIStackView con una proprietà che definisce il subView che dovrebbe essere toccabile. Ora, qualsiasi tocco sui pulsanti laterali (incluso nell'array * viewsWithActiveTouch *) verrà dato ai pulsanti, mentre qualsiasi tocco sullo stackview in qualsiasi luogo diverso da queste viste non verrà intercettato, e quindi passato a tutto ciò che è sotto lo stack Visualizza.

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

6

Se la vista a cui vuoi inoltrare i tocchi non è una sottoview / superview, puoi impostare una proprietà personalizzata nella sottoclasse UIView in questo modo:

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

Assicurati di sintetizzarlo nel tuo .m:

@synthesize forwardableTouchee;

E quindi includere quanto segue in uno qualsiasi dei metodi UIResponder come:

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

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

}

Ovunque installi la tua UIView, imposta la proprietà forwardableTouchee su qualunque vista desideri che gli eventi vengano inoltrati a:

    SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
    view.forwardableTouchee = someOtherView;

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

4

Avevo bisogno di passare i tocchi attraverso un UIStackView. Un UIView all'interno era trasparente, ma UIStackView ha consumato tutti i tocchi. Questo ha funzionato per me:

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

Tutte le sottovoci organizzate ricevono ancora tocchi, ma i tocchi su UIStackView stesso passano alla vista di seguito (per me una mapView).


2

Sembra che tu abbia anche un sacco di risposte qui, non c'è nessuno pulito in breve tempo di cui avevo bisogno. Quindi ho preso la risposta da @fresidue qui e l'ho convertita in rapido poiché è quello che ora gli sviluppatori vogliono usare qui.

Ha risolto il mio problema in cui ho una barra degli strumenti trasparente con pulsante, ma voglio che la barra degli strumenti sia invisibile all'utente e gli eventi di tocco dovrebbero passare.

isUserInteractionEnabled = false come alcuni affermati non è un'opzione basata sul mio test.

 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
}

1

Ho avuto un paio di etichette all'interno di StackView e non ho avuto molto successo con le soluzioni sopra, invece ho risolto il mio problema usando il codice seguente:

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

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

}

Quindi potresti fare qualcosa del tipo:

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

0

Prova qualcosa del genere ...

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

Il codice sopra, nel tuo metodo touchesBegan, ad esempio, passerebbe i tocchi a tutte le visualizzazioni secondarie della vista.


6
Il problema con questo approccio AFAIK è che il tentativo di inoltrare tocchi alle classi del framework UIKit molto spesso non avrà alcun effetto. Secondo la documentazione di Apple, le classi del framework UIKit non sono progettate per ricevere tocchi che hanno la proprietà view impostata su un'altra vista. Quindi questo approccio funziona principalmente per sottoclassi personalizzate di UIView.
maciejs,

0

La situazione che stavo cercando di fare era costruire un pannello di controllo usando i controlli all'interno di UIStackView nidificato. Alcuni dei controlli avevano gli altri di UITextField con quelli di UIButton. Inoltre, c'erano etichette per identificare i controlli. Quello che volevo fare era mettere un grande pulsante "invisibile" dietro il pannello di controllo in modo che se un utente toccasse un'area al di fuori di un pulsante o campo di testo, potessi quindi catturarlo e agire - principalmente scartare qualsiasi tastiera se un testo campo era attivo (resignFirstResponder). Tuttavia, toccando un'etichetta o un'altra area vuota nel pannello di controllo non passerebbe attraverso le cose. Le discussioni di cui sopra sono state utili per trovare la mia risposta di seguito.

Fondamentalmente, ho sotto-classificato UIStackView e ho sovrascritto la routine "point (inside: with) per cercare il tipo di controlli che avevano bisogno del tocco e" ignorare "cose ​​come etichette che volevo ignorare. Controlla anche all'interno di UIStackView in modo che le cose possano rientrare nella struttura del pannello di controllo.

Il codice è forse un po 'più dettagliato di quanto dovrebbe essere. Ma è stato utile nel debug e, si spera, fornisce maggiore chiarezza in ciò che la routine sta facendo. Assicurati solo in Interface Builder di modificare la classe di UIStackView in 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
}

}


-1

Come suggerito da @PixelCloudStv se si desidera passare toccati da una vista all'altra ma con un controllo aggiuntivo su questo processo - sottoclasse UIView

//intestazione

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//implementazione

#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

Dopo in InterfaceBuilder basta impostare la classe di View su TouchView e impostare rect attivo con il tuo rect. Inoltre puoi cambiare e implementare altra logica.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.