wake-up-neo.com

Von UIView zu UIViewController gelangen?

Gibt es eine integrierte Möglichkeit, von einem UIView zu seinem UIViewController zu gelangen? Ich weiß, dass Sie von UIViewController zu seinem UIView über [self view] Gelangen können, aber ich habe mich gefragt, ob es eine umgekehrte Referenz gibt.

179
bertrandom

Da dies seit langem die akzeptierte Antwort ist, glaube ich, dass ich es mit einer besseren Antwort korrigieren muss.

Einige Kommentare zur Notwendigkeit:

  • Ihre Ansicht muss nicht direkt auf den Ansichtscontroller zugreifen.
  • Die Ansicht sollte stattdessen unabhängig vom Ansichtscontroller sein und in verschiedenen Kontexten arbeiten können.
  • Wenn Sie möchten, dass die Ansicht in einer Weise mit dem Ansichts-Controller zusammenarbeitet, wird empfohlen, dass Apple in Cocoa das Delegate-Muster verwendet.

Ein Beispiel für die Implementierung lautet wie folgt:

@protocol MyViewDelegate < NSObject >

- (void)viewActionHappened;

@end

@interface MyView : UIView

@property (nonatomic, assign) MyViewDelegate delegate;

@end

@interface MyViewController < MyViewDelegate >

@end

Die Ansicht hat eine Schnittstelle zu ihrem Delegaten (wie zum Beispiel UITableView), und es ist ihm egal, ob sie im Ansichtscontroller oder in einer anderen Klasse implementiert ist, die Sie letztendlich verwenden.

Meine ursprüngliche Antwort lautet wie folgt: Ich empfehle dies nicht, auch nicht die restlichen Antworten, bei denen ein direkter Zugriff auf den View Controller möglich ist.

Es gibt keine eingebaute Möglichkeit, dies zu tun. Sie können dies umgehen, indem Sie ein IBOutlet in das UIView einfügen und diese in Interface Builder verbinden. Dies wird jedoch nicht empfohlen. Die Ansicht sollte nichts über den Ansichtscontroller wissen. Stattdessen sollten Sie wie in @Phil M vorgeschlagen vorgehen und ein Protokoll erstellen, das als Delegat verwendet wird.

45
pgb

Anhand des von Brock bereitgestellten Beispiels habe ich es so geändert, dass es eine Kategorie von UIView statt UIViewController ist, und es rekursiv gemacht, sodass jede Unteransicht (hoffentlich) den übergeordneten UIViewController finden kann.

@interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
- (id) traverseResponderChainForUIViewController;
@end

@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
    // convenience function for casting and to "mask" the recursive function
    return (UIViewController *)[self traverseResponderChainForUIViewController];
}

- (id) traverseResponderChainForUIViewController {
    id nextResponder = [self nextResponder];
    if ([nextResponder isKindOfClass:[UIViewController class]]) {
        return nextResponder;
    } else if ([nextResponder isKindOfClass:[UIView class]]) {
        return [nextResponder traverseResponderChainForUIViewController];
    } else {
        return nil;
    }
}
@end

Um diesen Code zu verwenden, fügen Sie ihn in eine neue Klassendatei ein (ich habe meine "UIKitCategories" genannt) und entfernen Sie die Klassendaten. Kopieren Sie das @Interface in den Header und die @Implementierung in die .m-Datei. Importieren Sie dann in Ihrem Projekt "UIKitCategories.h" und verwenden Sie im UIView-Code:

// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
200
Phil M

UIView ist eine Unterklasse von UIResponder. UIResponder legt die Methode -nextResponder mit einer Implementierung, die nil zurückgibt. UIView überschreibt diese Methode, wie in UIResponder dokumentiert (aus irgendeinem Grund anstelle von UIView), wie folgt: Wenn die Ansicht einen Ansichtscontroller hat, wird sie von -nextResponder. Wenn kein View-Controller vorhanden ist, gibt die Methode die Übersicht zurück.

Fügen Sie dies Ihrem Projekt hinzu und Sie können loslegen.

@interface UIView (APIFix)
- (UIViewController *)viewController;
@end

@implementation UIView (APIFix)

- (UIViewController *)viewController {
    if ([self.nextResponder isKindOfClass:UIViewController.class])
        return (UIViewController *)self.nextResponder;
    else
        return nil;
}
@end

Jetzt hat UIView eine Arbeitsmethode zum Zurückgeben des Ansichtscontrollers.

112
Brock

Ich würde einen leichteren Ansatz vorschlagen, um die gesamte Antwortkette zu durchlaufen, ohne eine Kategorie in UIView hinzufügen zu müssen:

@implementation MyUIViewSubclass

- (UIViewController *)viewController {
    UIResponder *responder = self;
    while (![responder isKindOfClass:[UIViewController class]]) {
        responder = [responder nextResponder];
        if (nil == responder) {
            break;
        }
    }
    return (UIViewController *)responder;
}

@end
32
de.

Wenn ich mehrere bereits gegebene Antworten kombiniere, versende ich sie auch mit meiner Implementierung:

@implementation UIView (AppNameAdditions)

- (UIViewController *)appName_viewController {
    /// Finds the view's view controller.

    // Take the view controller class object here and avoid sending the same message iteratively unnecessarily.
    Class vcc = [UIViewController class];

    // Traverse responder chain. Return first found view controller, which will be the view's view controller.
    UIResponder *responder = self;
    while ((responder = [responder nextResponder]))
        if ([responder isKindOfClass: vcc])
            return (UIViewController *)responder;

    // If the view controller isn't found, return nil.
    return nil;
}

@end

Die Kategorie ist Teil meiner ARC-fähigen statischen Bibliothek, die ich mit jeder von mir erstellten Anwendung versende. Es wurde mehrmals getestet und ich habe keine Probleme oder Undichtigkeiten festgestellt.

P .: Sie müssen keine Kategorie verwenden, wie ich es getan habe, wenn die betroffene Ansicht eine Unterklasse von Ihnen ist. In letzterem Fall fügen Sie die Methode einfach in Ihre Unterklasse ein, und Sie können loslegen.

22

Auch wenn dies technisch gelöst werden kann, da pgb meiner Meinung nach empfiehlt, ist dies ein Konstruktionsfehler. Die Ansicht muss den Controller nicht kennen.

12
Ushox

Ich habe de answer geändert, damit ich jede Ansicht, Schaltfläche, Beschriftung usw. übergeben kann, um die übergeordnete UIViewController zu erhalten. Hier ist mein Code.

+(UIViewController *)viewController:(id)view {
    UIResponder *responder = view;
    while (![responder isKindOfClass:[UIViewController class]]) {
        responder = [responder nextResponder];
        if (nil == responder) {
            break;
        }
    }
    return (UIViewController *)responder;
}

Bearbeiten Swift 3 Version

class func viewController(_ view: UIView) -> UIViewController {
        var responder: UIResponder? = view
        while !(responder is UIViewController) {
            responder = responder?.next
            if nil == responder {
                break
            }
        }
        return (responder as? UIViewController)!
    }

Edit 2: - Swift Extention

extension UIView
{
    //Get Parent View Controller from any view
    func parentViewController() -> UIViewController {
        var responder: UIResponder? = self
        while !(responder is UIViewController) {
            responder = responder?.next
            if nil == responder {
                break
            }
        }
        return (responder as? UIViewController)!
    }
}
11
Varun Naharia

Vergessen Sie nicht, dass Sie für das Fenster, für das die Ansicht eine Unteransicht ist, auf den Stammansichts-Controller zugreifen können. Von dort aus, wenn Sie z. Verwenden Sie einen Navigationsansicht-Controller und möchten Sie eine neue Ansicht darauf schieben:

    [[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];

Sie müssen jedoch zuerst die rootViewController-Eigenschaft des Fensters ordnungsgemäß einrichten. Tun Sie dies, wenn Sie den Controller zum ersten Mal erstellen, z. In Ihrem App-Delegaten:

-(void) applicationDidFinishLaunching:(UIApplication *)application {
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    RootViewController *controller = [[YourRootViewController] alloc] init];
    [window setRootViewController: controller];
    navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
    [controller release];
    [window addSubview:[[self navigationController] view]];
    [window makeKeyAndVisible];
}
7
Gabriel

Ich bin auf eine Situation gestoßen, in der ich eine kleine Komponente wiederverwenden möchte, und habe Code in einer wiederverwendbaren Ansicht selbst hinzugefügt (es ist wirklich nicht viel mehr als eine Schaltfläche, die ein PopoverController öffnet).

Während dies im iPad gut funktioniert (das UIPopoverController zeigt sich von selbst, daher muss nicht auf ein UIViewController verwiesen werden), bedeutet derselbe Code, dass Sie plötzlich auf Ihr presentViewController verweisen UIViewController. Ein bisschen inkonsistent, oder?

Wie bereits erwähnt, ist es nicht der beste Ansatz, Logik in UIView zu haben. Es fühlte sich jedoch wirklich nutzlos an, die wenigen benötigten Codezeilen in einen separaten Controller zu packen.

So oder so, hier ist eine Swift Lösung, die jeder UIView eine neue Eigenschaft hinzufügt:

extension UIView {

    var viewController: UIViewController? {

        var responder: UIResponder? = self

        while responder != nil {

            if let responder = responder as? UIViewController {
                return responder
            }
            responder = responder?.nextResponder()
        }
        return nil
    }
}
6
Kevin R

Obwohl diese Antworten technisch korrekt sind, einschließlich Ushox, denke ich, dass die genehmigte Methode darin besteht, ein neues Protokoll zu implementieren oder ein vorhandenes wiederzuverwenden. Ein Protokoll isoliert den Beobachter von dem Beobachteten, ähnlich wie ein Postfach dazwischen. In der Tat ist es das, was Gabriel über den Aufruf der pushViewController-Methode tut. Die Ansicht "weiß", dass es das richtige Protokoll ist, Ihren navigationController höflich aufzufordern, eine Ansicht zu pushen, da der viewController dem navigationController-Protokoll entspricht. Sie können zwar Ihr eigenes Protokoll erstellen, es reicht jedoch aus, nur Gabriels Beispiel zu verwenden und das UINavigationController-Protokoll erneut zu verwenden.

6
PapaSmurf

Ich halte es nicht für eine "schlechte" Idee, herauszufinden, wer in manchen Fällen der View Controller ist. Was eine schlechte Idee sein könnte, ist, den Verweis auf diesen Controller zu speichern, da er sich ebenso ändern kann, wie sich die Übersichten ändern. In meinem Fall habe ich einen Getter, der die Antwortkette durchläuft.

//.h

@property (nonatomic, readonly) UIViewController * viewController;

//.m

- (UIViewController *)viewController
{
    for (UIResponder * nextResponder = self.nextResponder;
         nextResponder;
         nextResponder = nextResponder.nextResponder)
    {
        if ([nextResponder isKindOfClass:[UIViewController class]])
            return (UIViewController *)nextResponder;
    }

    // Not found
    NSLog(@"%@ doesn't seem to have a viewController". self);
    return nil;
}
5
Rivera

Die einfachste do while-Schleife zum Auffinden des viewControllers.

-(UIViewController*)viewController
{
    UIResponder *nextResponder =  self;

    do
    {
        nextResponder = [nextResponder nextResponder];

        if ([nextResponder isKindOfClass:[UIViewController class]])
            return (UIViewController*)nextResponder;

    } while (nextResponder != nil);

    return nil;
}

Vielleicht bin ich zu spät hier. Aber in dieser Situation mag ich keine Kategorie (Verschmutzung). Ich liebe diesen Weg:

#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
3
lee

Es ist sicherlich eine schlechte Idee und ein falsches Design, aber ich bin sicher, wir können alle eine Swift Lösung der besten Antwort von @Phil_M genießen:

static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
    func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
        if let nextResponder = responder.nextResponder() {
            if let nextResp = nextResponder as? UIViewController {
                return nextResp
            } else {
                return traverseResponderChainForUIViewController(nextResponder)
            }
        }
        return nil
    }

    return traverseResponderChainForUIViewController(responder)
}

Wenn Sie beabsichtigen, einfache Dinge zu tun, z. B. einen modalen Dialog anzuzeigen oder Daten zu verfolgen, ist die Verwendung eines Protokolls nicht gerechtfertigt. Ich persönlich speichere diese Funktion in einem Utility-Objekt. Sie können sie von jedem Ort aus verwenden, an dem das UIResponder-Protokoll implementiert ist:

if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}

Alle Gutschriften an @Phil_M

3
Paul Slm

Schnell 4

(prägnanter als die anderen Antworten)

fileprivate extension UIView {

  var firstViewController: UIViewController? {
    let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
    return firstViewController as? UIViewController
  }

}

Mein Anwendungsfall, für den ich zuerst auf die Ansicht zugreifen muss UIViewController: Ich habe ein Objekt, das sich um AVPlayer/AVPlayerViewController dreht, und ich möchte eine einfache show(in view: UIView) Methode, die AVPlayerViewController in view einbettet. Dazu muss ich auf views UIViewController zugreifen.

3
frouo

Dies beantwortet die Frage nicht direkt, sondern geht von der Absicht der Frage aus.

Wenn Sie eine Ansicht haben und in dieser Ansicht eine Methode für ein anderes Objekt aufrufen müssen, beispielsweise den Ansichtscontroller, können Sie stattdessen das NSNotificationCenter verwenden.

Erstellen Sie zuerst Ihre Benachrichtigungszeichenfolge in einer Headerdatei

#define SLCopyStringNotification @"ShaoloCopyStringNotification"

Rufen Sie aus Ihrer Sicht postNotificationName auf:

- (IBAction) copyString:(id)sender
{
    [[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}

Dann fügen Sie in Ihrem View Controller einen Beobachter hinzu. Ich mache das in viewDidLoad

- (void)viewDidLoad
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(copyString:)
                                                 name:SLCopyStringNotification
                                               object:nil];
}

Implementieren Sie nun (ebenfalls im selben View-Controller) Ihre Methode copyString: wie im @selector oben dargestellt.

- (IBAction) copyString:(id)sender
{
    CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
    UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
    [gpBoard setString:result.stringResult];
}

Ich sage nicht, dass dies der richtige Weg ist, es scheint nur sauberer zu sein, als die erste Antwortkette hochzufahren. Mit diesem Code habe ich einen UIMenuController in einem UITableView implementiert und das Ereignis wieder an den UIViewController übergeben, damit ich etwas mit den Daten anfangen kann.

3
Shaolo

Schnellere Lösung

extension UIView {
    var parentViewController: UIViewController? {
        for responder in sequence(first: self, next: { $0.next }) {
            if let viewController = responder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}
3
Igor Palaguta

Swift 4 Version

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
}

Anwendungsbeispiel

 if let parent = self.view.parentViewController{

 }
2
levin varghese

Auf Phils Antwort:

In der Reihe: id nextResponder = [self nextResponder]; Wenn self (UIView) keine Unteransicht der ViewController-Ansicht ist, können Sie, wenn Sie die Hierarchie von self (UIView) kennen, auch Folgendes verwenden: id nextResponder = [[self superview] nextResponder];...

1
sag

Aktualisierte Version für Swift 4: Danke für @Phil_M und @ paul-slm

static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
    func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
        if let nextResponder = responder.next {
            if let nextResp = nextResponder as? UIViewController {
                return nextResp
            } else {
                return traverseResponderChainForUIViewController(responder: nextResponder)
            }
        }
        return nil
    }

    return traverseResponderChainForUIViewController(responder: responder)
}
1
Dicle

Ich denke, es gibt einen Fall, in dem der Beobachter den Beobachter informieren muss.

Ich sehe ein ähnliches Problem, bei dem das UIView in einem UIViewController auf eine Situation reagiert und zuerst dem übergeordneten Ansichtscontroller mitteilen muss, dass die Zurück-Schaltfläche ausgeblendet werden soll, und dann nach Abschluss dem übergeordneten Ansichtscontroller mitteilen muss, dass er sich vom Stapel entfernen soll.

Ich habe dies mit Delegierten ohne Erfolg versucht.

Ich verstehe nicht, warum das eine schlechte Idee sein sollte?

0
user7865437

Eine andere einfache Möglichkeit besteht darin, eine eigene Ansichtsklasse zu haben und der Ansichtsklasse eine Eigenschaft des Ansichtscontrollers hinzuzufügen. Normalerweise erstellt der Ansichtscontroller die Ansicht, und hier kann der Controller sich auf die Eigenschaft einstellen. Grundsätzlich muss nicht nach dem Controller gesucht werden (mit ein bisschen Hacking), sondern der Controller muss sich selbst auf die Ansicht einstellen - dies ist einfach, aber sinnvoll, da der Controller die Ansicht "steuert".

0
jack

Wenn Sie dies nicht in den App Store hochladen möchten, können Sie auch eine private Methode von UIView verwenden.

@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end

// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
0
pixelomer

Meine Lösung würde wahrscheinlich als eine Art Schwindel betrachtet werden, aber ich hatte eine ähnliche Situation wie Mayoneez (ich wollte Ansichten als Reaktion auf eine Geste in einem EAGLView wechseln), und ich bekam den Sichtcontroller des EAGL auf diese Weise:

EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
0
gulchrider