wake-up-neo.net

Woher bekomme ich den viewController?

Ich habe einen Zeiger auf eine UIView. Wie kann ich auf UIViewController zugreifen? [self superview] ist ein anderes UIView, aber nicht das UIViewController, richtig?

115
mahboudz

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.

38

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; \
})
238
mxcl

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 __.
19
Leo Natan

@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
16

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
9
Andrey

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)
}
4
iTSangar

Wenn Sie mit dem Code nicht vertraut sind und Sie ViewController entsprechend der angegebenen Ansicht finden möchten, können Sie Folgendes versuchen:

  1. App in Debug ausführen
  2. Navigieren Sie zum Bildschirm
  3. Starten Sie den Inspektor für die Ansicht
  4. Schnappen Sie sich die gewünschte Ansicht (oder eine untergeordnete Ansicht).
  5. Rufen Sie im rechten Fenster die Adresse ab (z. B. 0x7fe523bd3000).
  6. In der Debug-Konsole beginnen Sie, Befehle zu schreiben:
 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.

1
m4js7er

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

0
Nava Carmon

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
  }                       
}
0
Hackbarth

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.

0
Morten Carlsen

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
    }
}
0
Denis Krivitski