198 votes

Passer de UIViewController à UIView ?

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 existait une référence inverse ?

0 votes

207voto

Phil M Points 1368

En utilisant l'exemple posté par Brock, je l'ai modifié de façon à ce qu'il s'agisse d'une catégorie de UIView au lieu de UIViewController et je l'ai rendu récursif de façon à ce que toute sous-vue puisse (si tout va bien) trouver le UIViewController parent.

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

@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
    UIResponder *responder = [self nextResponder];
    while (responder != nil) {
        if ([responder isKindOfClass:[UIViewController class]]) {
            return (UIViewController *)responder;
        }
        responder = [responder nextResponder];
    }
    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 la classe... copiez l'@interface dans le header, et l'@implementation dans le fichier .m. Ensuite, dans votre projet, #import "UIKitCategories.h" et utilisez-le dans le code de UIView :

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

48 votes

L'une des raisons pour lesquelles vous devez permettre à l'UIView de connaître son UIViewController est que vous avez des sous-classes d'UIView personnalisées qui doivent pousser une vue/dialogue modale.

0 votes

Génial, j'ai dû accéder à mon ViewController pour afficher un popup personnalisé qui est créé par une sous-vue.

2 votes

N'est-ce pas une mauvaise pratique pour une UIView de pousser une vue modale ? Je le fais en ce moment même, mais j'ai l'impression que ce n'est pas la bonne chose à faire

116voto

Brock Points 871

UIView est une sous-classe de UIResponder . UIResponder expose la méthode -nextResponder avec une implémentation qui renvoie nil . UIView remplace cette méthode, comme indiqué dans le document UIResponder (pour une raison quelconque, au lieu de dans UIView ) comme suit : si la vue a un contrôleur de vue, celui-ci est renvoyé par la fonction -nextResponder . S'il n'y a pas de contrôleur de vue, la méthode renvoie la vue supérieure.

Ajoutez ceci à votre projet et vous êtes prêt à rouler.

@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

Maintenant UIView a une méthode de travail pour retourner le contrôleur de vue.

6 votes

Cela ne fonctionnera que s'il n'y a rien dans la chaîne du répondeur entre le récepteur UIView et le UIViewController . La réponse de Phil M mettant en scène la récursion est la voie à suivre.

45voto

pgb Points 17316

Puisque c'est la réponse acceptée depuis longtemps, je pense que je dois la rectifier avec une meilleure réponse.

Quelques commentaires sur la nécessité :

  • Votre vue ne doit pas avoir besoin d'accéder directement au contrôleur de vue.
  • La vue doit au contraire être indépendante du contrôleur de vue, et être capable de travailler dans des contextes différents.
  • Si vous avez besoin que la vue s'interface d'une manière ou d'une autre avec le contrôleur de vue, la méthode recommandée, et ce que fait Apple dans Cocoa, est d'utiliser le modèle de délégué.

Voici un exemple de sa mise en œuvre :

@protocol MyViewDelegate < NSObject >

- (void)viewActionHappened;

@end

@interface MyView : UIView

@property (nonatomic, assign) MyViewDelegate delegate;

@end

@interface MyViewController < MyViewDelegate >

@end

La vue s'interface avec son délégué (comme UITableView par exemple) et il importe peu qu'elle soit mise en œuvre dans le contrôleur de vue ou dans toute autre classe que vous finissez par utiliser.

Ma réponse initiale suit : Je ne recommande pas cela, ni le reste des réponses où l'accès direct au contrôleur de vue est réalisé

Il n'y a pas de moyen intégré de le faire. Bien que vous puissiez le contourner en ajoutant un IBOutlet sur le UIView et de les connecter dans Interface Builder, ce n'est pas recommandé. La vue ne doit pas connaître le contrôleur de vue. Au lieu de cela, vous devriez faire comme @Phil M suggère et créer un protocole à utiliser comme délégué.

26 votes

C'est un très mauvais conseil. Vous ne devriez pas référencer un contrôleur de vue à partir d'une vue

0 votes

@Philippe Leybaert : Le fait de définir la propriété comme "assign" ne résoudrait-il pas la référence circulaire et les problèmes liés à cette approche ?

0 votes

@Philippe Leybaert, vous dites donc que l'application d'exemple Xcode BubbleLevel d'Apple est mauvaise ? LevelView possède une propriété d'affectation à son viewController .

34voto

de. Points 442

Je suggère une approche plus légère pour parcourir la chaîne complète des répondeurs 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

22voto

Randy Marsh Points 3229

En combinant plusieurs réponses déjà données, je suis en train de l'expédier aussi avec ma mise en œuvre :

@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

La catégorie fait partie de mon ARC-enabled bibliothèque statique que je livre sur toutes les applications que je crée. Elle a été testée plusieurs fois et je n'ai pas trouvé de problèmes ou de fuites.

P.S. : Vous n'avez pas besoin d'utiliser une catégorie comme je l'ai fait si la vue concernée est une sous-classe de la vôtre. Dans ce cas, mettez simplement la méthode dans votre sous-classe et vous êtes prêt à partir.

1 votes

C'est la meilleure réponse. Pas besoin de récursion, cette version est bien optimisée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X