Ich habe einen Zeiger auf a UIView
. Wie greife ich darauf zu UIViewController
? [self superview]
ist ein anderer UIView
, aber nicht der UIViewController
, richtig?
Ich habe einen Zeiger auf a UIView
. Wie greife ich darauf zu UIViewController
? [self superview]
ist ein anderer UIView
, aber nicht der UIViewController
, richtig?
Antworten:
Ja, das superview
ist die Ansicht, die Ihre Ansicht enthält. Ihre Ansicht sollte nicht genau wissen, um welchen Ansichts-Controller es sich handelt, da dies gegen die MVC-Prinzipien verstoßen würde.
Der Controller hingegen weiß, für welche Ansicht er verantwortlich ist ( self.view = myView
), und normalerweise delegiert diese Ansicht Methoden / Ereignisse für die Verarbeitung an den Controller.
In der Regel sollten Sie anstelle eines Zeigers auf Ihre Ansicht einen Zeiger auf Ihren Controller haben, der wiederum entweder eine Steuerlogik ausführen oder etwas an seine Ansicht ü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 nil zurück. Unterklassen müssen diese Methode überschreiben, um den nächsten Responder festzulegen. UIView implementiert diese Methode, indem es das UIViewController-Objekt zurückgibt, das es verwaltet (falls vorhanden) oder die Übersicht (falls nicht) . UIViewController implementiert die Methode, indem es die Übersicht seiner Ansicht zurückgibt. UIWindow gibt das Anwendungsobjekt zurück und UIApplication gibt null zurück.
Wenn Sie also eine Ansicht so lange rekursieren, nextResponder
bis sie vom Typ ist UIViewController
, haben Sie den übergeordneten viewController einer Ansicht.
Beachten Sie, dass möglicherweise noch kein übergeordneter Ansichtscontroller vorhanden ist. Aber nur, wenn die Ansicht nicht Teil der Ansichtshierarchie einer viewController-Ansicht ist.
Swift 3 und Swift 4.1 Erweiterung:
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 Verschmutzung durch Kategorien:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
UIResponder
;). Sehr erbaulicher Beitrag.
@ andrey Antwort in einer Zeile (getestet in Swift 4.1 ):
extension UIResponder {
public var parentViewController: UIViewController? {
return next as? UIViewController ?? next?.parentViewController
}
}
Verwendung:
let vc: UIViewController = view.parentViewController
parentViewController
kann nicht definiert werden, public
wenn sich die Erweiterung in derselben Datei befindet wie Sie, UIView
Sie können sie festlegen fileprivate
, sie wird kompiliert, funktioniert aber nicht! 😐
Nur zu Debug-Zwecken können Sie _viewDelegate
Ansichten aufrufen , um deren Ansichts-Controller abzurufen. Dies ist eine private API, daher nicht sicher für den App Store, aber zum Debuggen ist sie nützlich.
Andere nützliche Methoden:
_viewControllerForAncestor
- Holen Sie sich den ersten Controller, der eine Ansicht in der Übersichtskette verwaltet. (danke n00neimp0rtant)_rootAncestorViewController
- Holen Sie sich den Ahnen-Controller, dessen Ansichtshierarchie derzeit im Fenster festgelegt ist._viewControllerForAncestor
Durchläuft die Übersichten, bis die erste gefunden wird, die zu einem Ansichts-Controller gehört.
Um einen Verweis auf UIViewController mit UIView zu erhalten, können Sie UIResponder (eine Superklasse für UIView und UIViewController) erweitern, die es ermöglicht, die Responderkette zu durchlaufen und so UIViewController zu erreichen (andernfalls wird Null zurückgegeben).
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 ViewController finden möchten, der der angegebenen Ansicht entspricht, 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 gibt es eine UIViewController-basierte Klasse.
Ich denke, Sie können den Tipp an den View Controller weitergeben und ihn damit umgehen lassen. Dies ist ein akzeptablerer Ansatz. Für den Zugriff auf einen Ansichtscontroller aus seiner Ansicht sollten Sie einen Verweis auf einen Ansichtscontroller beibehalten, da es keinen anderen Weg gibt. In diesem Thread kann es hilfreich sein: Zugriff auf den View Controller von einer Ansicht aus
Mehr typsicheren 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
}
}
Leider ist dies nur möglich, wenn Sie die Ansicht in Unterklassen unterteilen und ihr eine Instanzeigenschaft oder ähnliches bereitstellen, in der die Referenz des Ansichtscontrollers gespeichert wird, sobald die Ansicht zur Szene hinzugefügt wird ...
In den meisten Fällen ist es sehr einfach, das ursprüngliche Problem dieses Beitrags zu umgehen, da die meisten View-Controller dem Programmierer, der für das Hinzufügen von Unteransichten zur ViewController-Ansicht verantwortlich war, bekannte Einheiten sind ;-) Deshalb denke ich, dass Apple nie die Mühe gemacht, diese Eigenschaft hinzuzufügen.
Ein bisschen spät, aber hier ist eine Erweiterung, mit der Sie einen Responder jeglichen Typs 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
}
}
Wenn Sie einen Haltepunkt festlegen, können Sie diesen in den Debugger einfügen, um die Ansichtshierarchie zu drucken:
po [[UIWindow keyWindow] recursiveDescription]
Sie sollten in der Lage sein, die Eltern Ihrer Ansicht irgendwo in diesem Durcheinander zu finden :)
recursiveDescription
druckt nur die Ansichtshierarchie , nicht die Ansichtscontroller.