[ios] Accéder à UIViewController depuis UIView?


10 Answers

En utilisant l'exemple publié par Brock, je l'ai modifié pour qu'il soit une catégorie de UIView à la place de UIViewController et qu'il soit récursif pour que n'importe quelle sous-vue puisse (espérons-le) trouver le parent UIViewController.

@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

Pour utiliser ce code, ajoutez-le dans un nouveau fichier de classe (j'ai nommé le mien "UIKitCategories") et supprimez les données de classe ... copiez l'@interface dans l'en-tête et la @implementation dans le fichier .m. Ensuite, dans votre projet, #import "UIKitCategories.h" et utilisez dans le code UIView:

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

Existe-t-il un moyen intégré de passer d'un UIView à son UIViewController ? Je sais que vous pouvez obtenir de UIViewController à son UIView via [self view] mais je me demandais s'il y a une référence inverse?




Peut-être que je suis en retard ici. Mais dans cette situation, je n'aime pas la catégorie (pollution). J'aime cette façon:

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



Version mise à jour pour swift 4: Merci pour @Phil_M et @ 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)
}



Je suis tombé sur une situation où j'ai un petit composant que je veux réutiliser, et j'ai ajouté du code dans une vue réutilisable (ce n'est vraiment pas beaucoup plus qu'un bouton qui ouvre un PopoverController ).

Bien que cela fonctionne bien dans l'iPad (le UIPopoverController présente donc sans référence à un UIViewController ), faire fonctionner le même code signifie faire référence soudainement à votre UIViewController depuis votre UIViewController . Kinda incompatible droit?

Comme mentionné précédemment, ce n'est pas la meilleure approche pour avoir de la logique dans votre UIView. Mais il était vraiment inutile d'enrouler les quelques lignes de code nécessaires dans un contrôleur séparé.

De toute façon, voici une solution rapide, qui ajoute une nouvelle propriété à n'importe quel UIView:

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



N'oubliez pas que vous pouvez accéder au contrôleur de vue racine pour la fenêtre dont la vue est une sous-vue. De là, si vous utilisez par exemple un contrôleur de vue de navigation et que vous voulez y insérer une nouvelle vue:

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

Cependant, vous devrez d'abord configurer correctement la propriété rootViewController de la fenêtre. Pour ce faire, créez d'abord le contrôleur, par exemple dans le délégué de votre application:

-(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];
}



C'est sûrement une mauvaise idée et un mauvais design, mais je suis sûr que nous pouvons tous profiter d'une solution Swift de la meilleure réponse proposée par @Phil_M:

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

Si votre intention est de faire des choses simples, comme afficher un dialogue modal ou suivre des données, cela ne justifie pas l'utilisation d'un protocole. Personnellement, je stocke cette fonction dans un objet utilitaire, vous pouvez l'utiliser à partir de tout ce qui implémente le protocole UIResponder comme:

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

Tout le crédit à @Phil_M




Il n'y a pas moyen.

Ce que je fais est de passer le pointeur UIViewController à l'UIView (ou un héritage approprié). Je suis désolé je ne peux pas aider avec l'approche de l'IB au problème parce que je ne crois pas en IB.

Pour répondre au premier intervenant: il faut parfois savoir qui vous a appelé parce que cela détermine ce que vous pouvez faire. Par exemple, avec une base de données, vous pouvez avoir un accès en lecture seule ou en lecture / écriture ...




Je ne pense pas que ce soit une mauvaise idée de savoir qui est le contrôleur de vue dans certains cas. Ce qui pourrait être une mauvaise idée est de sauvegarder la référence à ce contrôleur car elle pourrait changer au fur et à mesure que les vues d'ensemble changent. Dans mon cas, j'ai un getter qui traverse la chaîne répondeur.

//.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;
}



Je pense qu'il y a un cas où le observé doit informer l'observateur.

Je vois un problème similaire où UIView dans un UIViewController répond à une situation et il doit d'abord dire à son contrôleur de vue parent pour masquer le bouton de retour, puis à la fin dire au contrôleur de vue parent qu'il doit se décoller de la pile.

J'ai essayé ceci avec des délégués sans succès.

Je ne comprends pas pourquoi cela devrait être une mauvaise idée?




Je suggérerais une approche plus légère pour traverser la chaîne répondeur complète sans avoir à ajouter une catégorie sur UIView:

@implementation MyUIViewSubclass

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

@end



Même si cela peut techniquement être résolu comme le recommande pgb , à mon humble avis , c'est un défaut de conception. La vue ne devrait pas avoir besoin d'être au courant du contrôleur.




Related