Vista la vista, come posso ottenere il suo viewController?


141

Ho un puntatore a UIView. Come posso accedervi UIViewController? [self superview]è un altro UIView, ma non il UIViewController, giusto?


Penso che questo thread abbia le risposte: arriva a UIViewController da UIView su iPhone?
Ushox,

Fondamentalmente, sto cercando di chiamare il mio viewController viewWillAppear mentre la mia vista viene respinta. La vista viene ignorata dalla vista stessa che sta rilevando un tocco e chiama [self removeFromSuperview]; ViewController non sta chiamando viewWillAppear / WillDisappear / DidAppear / DidDisappear stesso.
Mahboudz,

Volevo dire che sto cercando di chiamare viewWillDisappear poiché la mia vista viene respinta.
Mahboudz,

1
Possibile duplicato di Get to UIViewController da UIView?
Efren,

Risposte:


42

Sì, superviewè la vista che contiene la vista. Il tuo punto di vista non dovrebbe sapere quale sia esattamente il suo controller di vista, perché ciò infrangerebbe i principi MVC.

Il controller, d'altra parte, sa di quale vista è responsabile ( self.view = myView) e di solito questa vista delega i metodi / eventi per la gestione al controller.

In genere, anziché un puntatore alla vista, è necessario disporre di un puntatore al controller, che a sua volta può eseguire una logica di controllo o passare qualcosa alla sua vista.


24
Non sono sicuro che infrangerebbe i principi MVC. In ogni punto, una vista ha un solo controller di vista. Essere in grado di accedervi al fine di restituirgli un messaggio, dovrebbe essere una funzione automatica, non quella in cui devi lavorare per raggiungere (aggiungendo una proprietà per tenere traccia). Si potrebbe dire la stessa cosa delle opinioni: perché hai bisogno di sapere chi sei bambino? O se ci sono altre viste di pari livello. Eppure ci sono modi per ottenere quegli oggetti.
Mahboudz,

Hai un po 'ragione sulla vista, conoscendo il suo genitore, non è una decisione di progetto super chiara, ma è già stabilito di fare alcune azioni, usando direttamente una variabile membro di superview (controlla il tipo di genitore, rimuovi dal genitore, ecc.). Avendo lavorato di recente con PureMVC, sono diventato un po 'più schizzinoso riguardo l'astrazione del design :) Vorrei fare un parallelo tra le classi UIView e UIViewController di iPhone e le classi View e Mediator di PureMVC - la maggior parte delle volte, la classe View non ha bisogno di conoscere il suo gestore / interfaccia MVC (UIViewController / Mediator).
Dimitar Dimitrov,

9
Parola chiave: "più".
Glenn Maynard,

278

Dalla UIResponderdocumentazione per nextResponder:

La classe UIResponder non memorizza o imposta automaticamente il risponditore successivo, invece restituisce zero per impostazione predefinita. Le sottoclassi devono sovrascrivere questo metodo per impostare il risponditore successivo. UIView implementa questo metodo restituendo l'oggetto UIViewController che lo gestisce (se ne ha uno) o la sua superview (se non lo fa) ; UIViewController implementa il metodo restituendo la superview della sua vista; UIWindow restituisce l'oggetto applicazione e UIApplication restituisce zero.

Quindi, se si richiama una vista nextResponderfino a quando non è di tipo UIViewController, allora si ha il viewController padre di qualsiasi vista.

Si noti che potrebbe non avere ancora un controller della vista padre. Ma solo se la vista non fa parte della gerarchia di viste di una viewController.

Estensione Swift 3 e Swift 4.1 :

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder?.next
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

Estensione Swift 2:

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.nextResponder()
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

Categoria obiettivo C:

@interface UIView (mxcl)
- (UIViewController *)parentViewController;
@end

@implementation UIView (mxcl)
- (UIViewController *)parentViewController {
    UIResponder *responder = self;
    while ([responder isKindOfClass:[UIView class]])
        responder = [responder nextResponder];
    return (UIViewController *)responder;
}
@end

Questa macro evita l'inquinamento di categoria:

#define UIViewParentController(__view) ({ \
    UIResponder *__responder = __view; \
    while ([__responder isKindOfClass:[UIView class]]) \
        __responder = [__responder nextResponder]; \
    (UIViewController *)__responder; \
})

Solo una cosa: se ti preoccupi dell'inquinamento di categoria, definiscilo come una funzione statica piuttosto che una macro. Anche il brutale typecast è pericoloso, e per di più la macro potrebbe non essere corretta ma non ne sono sicuro.
Mojuba,

La macro funziona, io uso solo la versione macro personalmente. Inizio molti progetti e ho un'intestazione che inserisco ovunque con un sacco di queste funzioni di macro utility. Mi fa risparmiare tempo. Se non ti piacciono le macro, puoi adattarle a una funzione, ma le funzioni statiche sembrano noiose, dal momento che devi metterne una in ogni file che vuoi usare. Sembra invece che vorresti una funzione non statica dichiarata in un'intestazione e definita in un .m da qualche parte?
mxcl

Bello evitare alcuni delegati o notifiche. Grazie!
Ferran Maylinch,

Dovresti renderlo un'estensione di UIResponder;). Post molto edificante.
ScottyBlades,

32

Risposta di @andrey in una riga (testata in Swift 4.1 ):

extension UIResponder {
    public var parentViewController: UIViewController? {
        return next as? UIViewController ?? next?.parentViewController
    }
}

utilizzo:

 let vc: UIViewController = view.parentViewController

parentViewControllernon può essere definito publicse l'estensione è nello stesso file con cui UIViewè possibile impostarla fileprivate, si compila ma non funziona! 😐

Funziona benissimo. L'ho usato per l'azione del pulsante Indietro all'interno del file .xib dell'intestazione comune.
McDonal_11

23

Solo a scopo di debug, è possibile chiamare le _viewDelegateviste per ottenere i loro controller di visualizzazione. Questa è un'API privata, quindi non è sicura per App Store, ma per il debug è utile.

Altri metodi utili:

  • _viewControllerForAncestor- ottenere il primo controller che gestisce una vista nella catena di superview. (grazie n00neimp0rtant)
  • _rootAncestorViewController - ottenere il controller antenato la cui gerarchia di visualizzazione è attualmente impostata nella finestra.

Questo è esattamente il motivo per cui sono arrivato a questa domanda. Apparentemente, "nextResponder" fa la stessa cosa, ma apprezzo l'intuizione fornita da questa risposta. Capisco e mi piace MVC, ma il debug è un animale diverso!
mbm29414

4
Sembra che funzioni solo sulla vista principale del controller di visualizzazione, non su tutte le sue visualizzazioni secondarie. _viewControllerForAncestorattraverserà le superview fino a quando non trova la prima che appartiene a un controller di visualizzazione.
n00neimp0rtant,

Grazie @ n00neimp0rtant! Sto votando questa risposta in modo che le persone vedano il tuo commento.
eyuelt,

Aggiorna risposta con più metodi.
Leo Natan,

1
Questo è utile durante il debug.
evanchin

10

Per ottenere un riferimento a UIViewController con UIView, è possibile creare l'estensione di UIResponder (che è la super classe per UIView e UIViewController), che consente di risalire attraverso la catena di responder e raggiungere così UIViewController (altrimenti restituire zero).

extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.nextResponder() is UIViewController {
            return self.nextResponder() as? UIViewController
        } else {
            if self.nextResponder() != nil {
                return (self.nextResponder()!).getParentViewController()
            }
            else {return nil}
        }
    }
}

//Swift 3
extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.next is UIViewController {
            return self.next as? UIViewController
        } else {
            if self.next != nil {
                return (self.next!).getParentViewController()
            }
            else {return nil}
        }
    }
}

let vc = UIViewController()
let view = UIView()
vc.view.addSubview(view)
view.getParentViewController() //provide reference to vc

4

Il modo rapido e generico in Swift 3:

extension UIResponder {
    func parentController<T: UIViewController>(of type: T.Type) -> T? {
        guard let next = self.next else {
            return nil
        }
        return (next as? T) ?? next.parentController(of: T.self)
    }
}

//Use:
class MyView: UIView {
    ...
    let parentController = self.parentController(of: MyViewController.self)
}

2

Se non si ha familiarità con il codice e si desidera trovare ViewController corrispondente a una determinata vista, è possibile provare:

  1. Esegui l'app nel debug
  2. Passa allo schermo
  3. Avvia la finestra di ispezione
  4. Prendi la vista che vuoi trovare (o una vista figlio ancora migliore)
  5. Dal riquadro di destra ottenere l'indirizzo (ad es. 0x7fe523bd3000)
  6. Nella console di debug inizia a scrivere i comandi:
    po (UIView *) 0x7fe523bd3000
    po [(UIView *) 0x7fe523bd3000 nextResponder]
    po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder]
    po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

Nella maggior parte dei casi otterrai UIView, ma di tanto in tanto ci sarà una classe basata su UIViewController.


1

Penso che puoi propagare il tocco al controller di visualizzazione e lasciarlo gestire. Questo è un approccio più accettabile. Per quanto riguarda l'accesso a un controller di visualizzazione dalla sua visualizzazione, è necessario mantenere un riferimento a un controller di visualizzazione, poiché non esiste un altro modo. Vedi questa discussione, potrebbe essere d'aiuto: Accesso al controller di visualizzazione da una vista


Se hai una manciata di viste e una chiude tutte le viste e devi chiamare viewWillDisappear, non sarebbe più facile per quella vista rilevare il tocco piuttosto che passare il tocco al controller della vista e far controllare il controller della vista con tutti i punti di vista per vedere quale è stato toccato?
Mahboudz,

0

Altro tipo di codice sicuro per Swift 3.0

extension UIResponder {
    func owningViewController() -> UIViewController? {
        var nextResponser = self
        while let next = nextResponser.next {
            nextResponser = next
            if let vc = nextResponser as? UIViewController {
                return vc
            }
        }
        return nil
    }
}

0

Purtroppo, questo è impossibile a meno che non si esegua la sottoclasse della vista e si fornisca una proprietà di istanza o simile che memorizzi il riferimento del controller di vista al suo interno una volta che la vista viene aggiunta alla scena ...

Nella maggior parte dei casi, è molto semplice aggirare il problema originale di questo post poiché la maggior parte dei controller di visualizzazione sono entità ben note al programmatore che era responsabile dell'aggiunta di eventuali subview alla visualizzazione di ViewController ;-) Ecco perché suppongo che Apple non si è mai preso la briga di aggiungere quella proprietà.


0

Un po 'in ritardo, ma ecco un'estensione che ti consente di trovare un risponditore di qualsiasi tipo, incluso ViewController.

extension NSObject{
func findNext(type: AnyClass) -> Any{
    var resp = self as! UIResponder

    while !resp.isKind(of: type.self) && resp.next != nil
    {
        resp = resp.next!
    }

    return resp
  }                       
}

-1

Se si imposta un punto di interruzione, è possibile incollarlo nel debugger per stampare la gerarchia della vista:

po [[UIWindow keyWindow] recursiveDescription]

Dovresti essere in grado di trovare il genitore della tua vista da qualche parte in quel casino :)


recursiveDescriptionstampa solo la gerarchia di visualizzazione , non i controller di visualizzazione.
Alan Zeino

1
da ciò è possibile identificare il viewcontroller @AlanZeino
Akshay il
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.