Ho un puntatore a UIView
. Come posso accedervi UIViewController
? [self superview]
è un altro UIView
, ma non il UIViewController
, giusto?
Ho un puntatore a UIView
. Come posso accedervi UIViewController
? [self superview]
è un altro UIView
, ma non il UIViewController
, giusto?
Risposte:
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.
Dalla UIResponder
documentazione 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 nextResponder
fino 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; \
})
UIResponder
;). Post molto edificante.
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
parentViewController
non può essere definito public
se l'estensione è nello stesso file con cui UIView
è possibile impostarla fileprivate
, si compila ma non funziona! 😐
Solo a scopo di debug, è possibile chiamare le _viewDelegate
viste 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._viewControllerForAncestor
attraverserà le superview fino a quando non trova la prima che appartiene a un controller di visualizzazione.
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
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)
}
Se non si ha familiarità con il codice e si desidera trovare ViewController corrispondente a una determinata vista, è possibile provare:
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.
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
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
}
}
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à.
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
}
}
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 :)
recursiveDescription
stampa solo la gerarchia di visualizzazione , non i controller di visualizzazione.