Ich habe einen Zeiger auf eine UIView
. Wie kann ich auf UIViewController
zugreifen? [self superview]
ist ein anderes UIView
, aber nicht das UIViewController
, richtig?
Ja, die Variable superview
ist die Ansicht, die Ihre Ansicht enthält. Ihre Ansicht sollte nicht genau wissen, welcher View-Controller genau ist, da dies die MVC-Prinzipien verletzen würde.
Der Controller hingegen weiß, für welche Ansicht er verantwortlich ist (self.view = myView
), und in der Regel delegiert er Methoden und Ereignisse zur Verarbeitung an den Controller.
Normalerweise sollten Sie anstelle eines Zeigers auf Ihre Ansicht einen Zeiger auf Ihren Controller haben, der wiederum Steuerlogik ausführen oder der Ansicht etwas übergeben kann.
Aus der UIResponder
-Dokumentation für nextResponder
:
Die UIResponder-Klasse speichert oder setzt den nächsten Responder nicht automatisch, sondern gibt standardmäßig null zurück. Unterklassen müssen diese Methode überschreiben, um den nächsten Responder festzulegen. UIView implementiert diese Methode, indem das UIViewController-Objekt zurückgegeben wird, von dem es verwaltet wird (falls vorhanden) oder dessen Superview (falls nicht) ; UIViewController implementiert die Methode, indem der Überblick der Ansicht zurückgegeben wird. UIWindow gibt das Anwendungsobjekt zurück und UIApplication gibt null zurück.
Wenn Sie also die nextResponder
einer Ansicht erneut anzeigen, bis sie vom Typ UIViewController
ist, haben Sie den übergeordneten viewController einer Ansicht.
Beachten Sie, dass not möglicherweise noch einen übergeordneten View-Controller hat. Dies gilt jedoch nur, wenn die Ansicht nicht Teil der Ansichtshierarchie einer ViewController-Ansicht ist.
Swift 3 und Swift 4.1 extension:
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
}
}
Swift 2 Erweiterung:
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
}
}
Ziel-C-Kategorie:
@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
Dieses Makro vermeidet Kategorienverschmutzung:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
Nur für Debugging-Zwecke können Sie _viewDelegate
für Ansichten aufrufen, um deren Ansichtscontroller abzurufen. Dies ist eine private API, daher für den App Store nicht sicher, aber für das Debuggen ist es nützlich.
Andere nützliche Methoden:
_viewControllerForAncestor
- Liefert den ersten Controller, der eine Ansicht in der Superview-Kette verwaltet. (danke n00neimp0rtant)_rootAncestorViewController
- Ruft den Vorfahrencontroller ab, dessen Hierarchie der Ansicht __.@andrey antworten in einer Zeile (getestet in Swift 4.1 ):
extension UIResponder {
public var parentViewController: UIViewController? {
return next as? UIViewController ?? next?.parentViewController
}
}
verwendungszweck:
let vc: UIViewController = view.parentViewController
Um einen Verweis auf UIViewController mit UIView zu erhalten, können Sie eine Erweiterung von UIResponder (die für UIView und UIViewController eine Superklasse ist) vornehmen, die es ermöglicht, die Responder-Kette zu durchlaufen und somit UIViewController zu erreichen (sonst Null).
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
Der schnelle und generische Weg 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)
}
Wenn Sie mit dem Code nicht vertraut sind und Sie ViewController entsprechend der angegebenen Ansicht finden möchten, können Sie Folgendes versuchen:
po (UIView *) 0x7fe523bd3000 po [(UIView *) 0x7fe523bd3000 nextResponder] po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder] ...
In den meisten Fällen erhalten Sie UIView, aber von Zeit zu Zeit wird es eine auf UIViewController basierte Klasse geben.
Ich denke, Sie können das Tippen an den View-Controller übertragen und damit umgehen. Dies ist ein akzeptablerer Ansatz. Beim Zugriff auf einen View-Controller aus seiner Sicht sollten Sie einen Verweis auf einen View-Controller beibehalten, da es keine andere Möglichkeit gibt. In diesem Thread kann es helfen: Zugriff auf den Ansichts-Controller von einer Ansicht aus
Ein bisschen spät, aber hier ist eine Erweiterung, mit der Sie einen Responder aller Art finden können, einschließlich 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
}
}
Leider ist dies nicht möglich, es sei denn, Sie unterteilen die Ansicht und geben ihr eine Instanzeneigenschaft oder ähnliches, in der die Ansichtssteuerungsreferenz gespeichert wird, sobald die Ansicht der Szene hinzugefügt wird.
In den meisten Fällen ist es sehr einfach, das ursprüngliche Problem dieses Beitrags zu umgehen, da die meisten Ansichtscontroller dem Programmierer, der für das Hinzufügen von Unteransichten zur ViewController-Ansicht verantwortlich war, bekannte Entitäten sind. ;-) Deshalb schätze ich, dass Apple dies tut hat nie die Mühe gemacht, diese Eigenschaft hinzuzufügen.
Mehr typsicherer Code für 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
}
}