2016-06-15 24 views
14

Ci sono state domande simili prima e ho fatto riferimento a Capturing touches on a subview outside the frame of its superview using hitTest:withEvent: e Delivering touch events to a view outside the bounds of its parent view. Ma non sembrano rispondere al mio particolare problema.Ricezione di eventi di tocco fuori limite

Ho un controllo personalizzato con la seguente struttura:

+-------------------------------+ 
|   UIButton   | 
+-------------------------------+ 
|       | 
|       | 
|  UITableView  | 
|       | 
|       | 
+------------------------- + 

Il controllo personalizzato sostituisce il UIButton sottoclasse e aggiunge il UITableView come visualizzazione secondaria. L'idea è di avere l'intero controllo come un dropdown. Quando si preme UIButton, verrà visualizzato il menu a discesa UITableView e sarà possibile selezionare una scelta.

Questo tutto funziona bene con il hitTest sottoposto a override in UIButton come descritto in Q & di Apple un link qui sopra, se il controllo è un figlio diretto del più alto UIView. Tuttavia, se il controllo si trova in un'altra gerarchia di viste, UITableView non riceve gli eventi di tocco.

Quello che segue è il codice hitTest utilizzato:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { 
    // Convert the point to the target view's coordinate system. 
    // The target view isn't necessarily the immediate subview 
    let pointForTargetView = self.spinnerTableView.convertPoint(point, fromView:self) 

    if (CGRectContainsPoint(self.spinnerTableView.bounds, pointForTargetView)) { 
     // The target view may have its view hierarchy, 
     // so call its hitTest method to return the right hit-test view 
     return self.spinnerTableView.hitTest(pointForTargetView, withEvent:event); 
    } 
    return super.hitTest(point, withEvent: event) 
} 

Edit:

mi scuso per non essere stato in grado di guardare le risposte e verificare se si risolve il problema, a causa di qualche altri compiti che richiedono attenzione immediata. Li controllerò e ne accetterò uno che aiuti. Grazie per la vostra pazienza.

risposta

3

Stavo pensando al UIResponder, qualcosa come:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { 
     // Convert the point to the target view's coordinate system. 
     // The target view isn't necessarily the immediate subview 

     var responder: UIResponder = self 
     while responder.nextResponder() != nil { 
      responder = responder.nextResponder()! 
      if responder is UITableView { 
       // Got UITableView 
       break; 
      } 
     } 
     let pointForTargetView = responder.convertPoint(point, fromView:self) 
     if (CGRectContainsPoint(responder.bounds, pointForTargetView)) { 
      // The target view may have its view hierarchy, 
      // so call its hitTest method to return the right hit-test view 
      return self.responder.hitTest(pointForTargetView, withEvent:event); 
     } 
     return super.hitTest(point, withEvent: event) 
} 

Se questo metodo non funziona, provare a convertPoint con superview:

Quando si è sicuri che self.spinnerTableView non è nullo ed è è il vostro tavolo, fare:

let pointForTargetView = self.spinnerTableView.superView.convertPoint(point, fromView:self) 
+0

No, il problema con l'implementazione precedente era che il punto non era stato convertito correttamente nel sistema di coordinate 'UITableView'. Quindi anche nel caso precedente, 'CGRectContainsPoint' non identifica il punto in cui si trovano i limiti di' UITableView'. – Rajesh

+0

Ho aggiornato la mia risposta, spero che aiuterete. –

+0

Grazie, ma ho bisogno di un modo generico per farlo. Indipendentemente dal numero di livelli gerarchici in cui si trova il controllo. – Rajesh

1

è necessario sottoclasse UIView, e usarlo come vista di root del vostro UIViewController.

class HitTestBottomView: UIView { 

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

    if let hitTestView = self.getHitTestButtonFrom(self){ 
     let pointForHitTestView = hitTestView.convertPoint(point, fromView: self) 
     let pointForHitTestTableView = hitTestView.spinnerTableView.convertPoint(point, fromView: self) 

     if CGRectContainsPoint(hitTestView.bounds, pointForHitTestView) || CGRectContainsPoint(hitTestView.spinnerTableView.bounds, pointForHitTestTableView){ 
      return hitTestView.hitTest(pointForHitTestView, withEvent: event) 
     } 
    } 

    return super.hitTest(point, withEvent: event) 
    } 

    func getHitTestButtonFrom(view:UIView) ->HitTestButton?{ 
    if let testView = view as? HitTestButton { 
     return testView 
    } 

    for subView in view.subviews { 
     if let testView = self.getHitTestButtonFrom(subView) { 
      return testView 
     } 
    } 
    return nil 
    } 
} 
+0

L'idea era di usare il controllo come componente riutilizzabile all'interno di qualsiasi gerarchia di UIView. Quindi, utilizzarlo come la vista radice non è una soluzione preferita. – Rajesh

5

come avete dichiarato:

Tuttavia, se il controllo è in un'altra gerarchia vista, l'UITableView è non riceve gli eventi di tocco.

Anche se l'hai fatto funzionare, cosa succede se hai sottomesso questo controllo sotto un altro UIView ??

In questo modo abbiamo un carico di conversione dei punti per la gerarchia delle viste, il mio suggerimento è che, come è possibile vedere in this control, aggiunge la vista tabella nella stessa gerarchia in cui è presente il controllo stesso. (Ad esempio, si aggiunge la vista della tabella sul controller in cui è stato aggiunto questo controllo)

Una parte dal link:

-(void)textFieldDidBeginEditing:(UITextField *)textField 
{ 
    [self setupSuggestionList]; 
    [suggestionListView setHidden:NO]; 

    // Add list to the super view. 
    if(self.dataSourceDelegate && [self.dataSourceDelegate isKindOfClass:UIViewController.class]) 
    { 
     [((UIViewController *)self.dataSourceDelegate).view addSubview:suggestionListView]; 
    } 

    // Setup list as per the given direction 
    [self adjustListFrameForDirection:dropDownDirection]; 
} 

Speranza che aiuta!

+0

Ha senso. Non l'ho provato, però. – Rajesh

1

è necessario implementare

func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool 

per il vostro UIButton e verificare UITableView limiti. Perché hitTest controlla innanzitutto se questo punto di tocco si trova all'interno dei limiti della vista, quindi chiama hitTest per la visualizzazione. quindi devi implementare pointInside e restituire true per i tuoi limiti.

WWDC per advanced scroll views and touch handling techniques wwdc

+0

Questa è stata la prima cosa che ho fatto. Ma come spiegato, il problema è nella conversione del punto nel sistema di coordinate della vista tabella. – Rajesh

3

penso che si dovrebbe sovrascritto pointInside attuare:

class Control: UIButton { 

    var dropdown: Bool = false 

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool { 
     if dropdown { 
      return CGRectContainsPoint(CGRect(x: 0, y:0, width: self.bounds.width, self.bounds.height + spinnerTableView.frame.height), point) 
     } 
     return super.pointInside(point, withEvent: event) 
    } 
} 

come questo, hittest quando il tocco di fuori dai limiti di controllo, non tornerà nil di controllo delle chiamate superview.

Ma c'è ancora un problema, se il controllo si trova in un'altra gerarchia di viste, UITableView potrebbe non ricevere gli eventi di tocco.

Penso che questo problema sia normale, non è possibile decidere altro punto di vistaInside o no che sotto controllo, se il controllo superview hitTest restituisce nulla, il metodo di controllo hitTest non verrà chiamato. A meno che non venga sovrascritto tutto il metodo pointInside sotto il controllo, ma non è realistico.