Wie erhalte ich bei einer Ansicht den viewController?


141

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 denke, dieser Thread hat die Antworten: Von UIView auf dem iPhone zu UIViewController gelangen?
Ushox

Grundsätzlich versuche ich, viewWillAppear meines viewControllers aufzurufen, da meine Ansicht verworfen wird. Die Ansicht wird von der Ansicht selbst verworfen, die ein Tippen erkennt und [self removeFromSuperview] aufruft. Der viewController ruft nicht viewWillAppear / WillDisappear / DidAppear / DidDisappear selbst auf.
Mahboudz

Ich meinte, ich versuche viewWillDisappear aufzurufen, da meine Ansicht verworfen wird.
Mahboudz

1
Mögliches Duplikat von Get to UIViewController von UIView?
Efren

Antworten:


42

Ja, das superviewist 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.


24
Ich bin mir nicht sicher, ob dies gegen die MVC-Prinzipien verstoßen würde. Zu jedem Zeitpunkt verfügt eine Ansicht nur über einen Ansichtscontroller. In der Lage zu sein, darauf zuzugreifen, um eine Nachricht an sie zurückzugeben, sollte eine automatische Funktion sein, nicht eine, an der Sie arbeiten müssen, um dies zu erreichen (indem Sie eine Eigenschaft hinzufügen, um den Überblick zu behalten). Über Ansichten könnte man dasselbe sagen: Warum müssen Sie wissen, wer Sie sind? Oder ob es andere Geschwisteransichten gibt. Es gibt jedoch Möglichkeiten, diese Objekte zu erhalten.
Mahboudz

Sie haben etwas Recht mit der Ansicht, wissen um die übergeordnete Ansicht, es ist keine sehr klare Entwurfsentscheidung, aber es ist bereits festgelegt, einige Aktionen auszuführen, indem Sie direkt eine Superview-Elementvariable verwenden (übergeordneten Typ überprüfen, vom übergeordneten Element entfernen usw.). Nachdem ich kürzlich mit PureMVC gearbeitet habe, bin ich etwas wählerischer in Bezug auf Designabstraktion geworden :) Ich würde parallel zwischen den UIView- und UIViewController-Klassen des iPhones und den View- und Mediator-Klassen von PureMVC arbeiten - die View-Klasse muss dies meistens nicht Kenntnisse über den MVC-Handler / die MVC-Schnittstelle (UIViewController / Mediator).
Dimitar Dimitrov

9
Schlüsselwort: "am meisten".
Glenn Maynard

278

Aus der UIResponderDokumentation 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, nextResponderbis 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; \
})

Nur eines: Wenn Sie sich Sorgen über die Verschmutzung von Kategorien machen, definieren Sie diese einfach als statische Funktion und nicht als Makro. Auch die brutale Typografie ist gefährlich, und außerdem ist das Makro möglicherweise nicht korrekt, aber nicht sicher.
Mojuba

Das Makro funktioniert, ich benutze die Makroversion nur persönlich. Ich starte viele Projekte und habe einen Header, den ich einfach überall mit einer Reihe dieser Makro-Dienstprogrammfunktionen ablege. Spart mir Zeit. Wenn Sie keine Makros mögen, können Sie sie in eine Funktion umwandeln, aber statische Funktionen scheinen mühsam zu sein, da Sie dann in jede Datei, die Sie verwenden möchten, eine einfügen müssen. Scheint, als ob Sie stattdessen eine nicht statische Funktion in einem Header deklarieren und in einer .m irgendwo definieren möchten?
mxcl

Schön, einige Delegierte oder Benachrichtigungen zu vermeiden. Vielen Dank!
Ferran Maylinch

Sie sollten es zu einer Erweiterung von machen UIResponder;). Sehr erbaulicher Beitrag.
ScottyBlades

31

@ 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

parentViewControllerkann nicht definiert werden, publicwenn sich die Erweiterung in derselben Datei befindet wie Sie, UIViewSie können sie festlegen fileprivate, sie wird kompiliert, funktioniert aber nicht! 😐

Es funktioniert gut. Ich habe dies für die Aktion "Zurück" in der .xib-Datei des allgemeinen Headers verwendet.
McDonal_11

23

Nur zu Debug-Zwecken können Sie _viewDelegateAnsichten 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.

Genau deshalb bin ich auf diese Frage gekommen. Anscheinend macht 'nextResponder' dasselbe, aber ich schätze die Einsicht, die diese Antwort bietet. Ich verstehe und mag MVC, aber das Debuggen ist ein anderes Tier!
mbm29414

4
Es scheint, dass dies nur in der Hauptansicht des View Controllers funktioniert, nicht in Unteransichten. _viewControllerForAncestorDurchläuft die Übersichten, bis die erste gefunden wird, die zu einem Ansichts-Controller gehört.
n00neimp0rtant

Danke @ n00neimp0rtant! Ich stimme dieser Antwort zu, damit die Leute Ihren Kommentar sehen.
Eyuelt

Aktualisieren Sie die Antwort mit weiteren Methoden.
Leo Natan

1
Dies ist beim Debuggen hilfreich.
Evanchin

10

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

4

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

2

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

  1. Führen Sie die App im Debug aus
  2. Navigieren Sie zum Bildschirm
  3. Starten Sie den Ansichtsinspektor
  4. Holen Sie sich die Ansicht, die Sie suchen möchten (oder eine untergeordnete Ansicht noch besser)
  5. Im rechten Bereich erhalten Sie die Adresse (z. B. 0x7fe523bd3000).
  6. Beginnen Sie in der Debug-Konsole mit dem Schreiben von Befehlen:
    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.


1

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


Wenn Sie eine Handvoll Ansichten haben und eine alle Ansichten schließt und Sie viewWillDisappear aufrufen müssen, ist es für diese Ansicht nicht einfacher, den Abgriff zu erkennen, als den Abgriff an den View Controller zu übergeben und den View Controller überprüfen zu lassen mit all den Ansichten, um zu sehen, welche angezapft wurde?
Mahboudz

0

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

0

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.


0

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

-1

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 :)


recursiveDescriptiondruckt nur die Ansichtshierarchie , nicht die Ansichtscontroller.
Alan Zeino

1
Daraus können Sie den Viewcontroller @AlanZeino
Akshay
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.