Arriver à UIViewController depuis UIView?

y a-t-il une façon intégrée 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 avait une référence inversée?

169
demandé sur vikingosegundo 2009-08-27 15:22:45

24 réponses

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

Quelques observations sur la nécessité de:

  • votre vue ne devrait pas avoir besoin d'accéder directement au contrôleur de vue.
  • la vue doit être indépendante du contrôleur de vue et pouvoir fonctionner dans des contextes différents.
  • si vous avez besoin d'afficher à l'interface avec le contrôleur de vue, la manière recommandée, et ce que Apple fait à travers Cocoa est d'utiliser le modèle délégué.

un exemple de la façon de le mettre en œuvre est le suivant:

@protocol MyViewDelegate < NSObject >

- (void)viewActionHappened;

@end

@interface MyView : UIView

@property (nonatomic, assign) MyViewDelegate delegate;

@end

@interface MyViewController < MyViewDelegate >

@end

la vue se connecte avec son délégué (comme UITableView le fait, par exemple) et il ne se soucie pas si elle est implémentée dans le contrôleur de la vue ou dans toute autre classe que vous finissez par utiliser.

ma réponse originale est la suivante: Je ne recommande pas ce, Ni le reste des réponses où l'accès direct au contrôleur de vue est atteint

il n'y a pas de manière intégrée pour le faire. Alors que vous pouvez le contourner en ajoutant un IBOutlet sur le UIView et les connecter dans Interface Builder, ce n'est pas recommandé. La vue ne doit pas connaître le contrôleur de vue. Vous devriez plutôt faire comme @Phil M le suggère et créer un protocole à utiliser en tant que délégué.

41
répondu pgb 2013-06-18 17:32:28

en utilisant L'exemple posté par Brock, Je l'ai modifié pour qu'il soit une catégorie D'UIView à la place D'UIViewController et l'ai fait récursif pour que n'importe quel subview puisse (avec un peu de chance) trouver le UIViewController parent.

@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 (je l'ai appelé mine" UIKitCategories") et supprimez les données de classe... copiez l'interface @dans l'en-tête, et l'implémentation @dans le .m de fichier. Puis dans votre projet, # importer " UIKitCategories.h" et utilisation dans le code uivi:

// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
198
répondu Phil M 2016-03-26 07:19:33

UIView est une sous classe de UIResponder . UIResponder décrit la méthode -nextResponder avec une implémentation qui retourne nil . UIView remplace cette méthode, comme documenté dans UIResponder (pour une raison quelconque au lieu de UIView ) comme suit: si la vue a un contrôleur de vue, il est retourné par -nextResponder . S'il n'y a pas de contrôleur de vue, la méthode retournera la superview.

Ajouter à 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.

111
répondu Brock 2013-04-01 16:27:14

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

@implementation MyUIViewSubclass

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

@end
30
répondu de. 2012-01-26 22:56:51

combinant plusieurs réponses déjà données, je l'expédie aussi bien 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 ma bibliothèque statique activée par ARC que j'expédie sur chaque application que je crée. Il a été testé plusieurs fois et je n'ai trouvé aucun problème ou fuite.

P. S.: vous n'avez pas besoin d'utiliser une catégorie comme je l'ai fait si la vue concernée est une de vos sous-classes. Dans ce dernier cas, il suffit de mettre le méthode dans votre sous-classe, et vous êtes bon pour aller.

22
répondu Randy Marsh 2013-02-28 14:33:26

même si cela peut techniquement être résolu comme pgb recommande, IMHO, il s'agit d'un défaut de conception. Il n'est pas nécessaire que la vue soit au courant du contrôleur.

12
répondu Ushox 2009-08-27 15:56:18

j'ai modifié de réponse si je peux passer n'importe quel affichage, bouton, label, etc. c'est parent UIViewController . Voici mon code.

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

Edit 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: - Extension Swift

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)!
    }
}
10
répondu Varun Naharia 2017-12-12 13:01:05

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

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

vous aurez besoin de configurer la propriété rootViewController de la fenêtre correctement en premier, cependant. Faites ceci lorsque vous créez le contrôleur pour la première fois, par exemple dans votre délégué app:

-(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
répondu Gabriel 2011-04-01 03:30:31

alors que ces réponses sont techniquement correctes, y compris Ushox, je pense que la approuvé façon est de mettre en œuvre un nouveau protocole ou de réutiliser un existant. Un protocole isole l'observateur de l'observé, un peu comme mettre une fente pour le courrier entre eux. En effet, C'est ce que Gabriel fait via l'invocation de la méthode pushViewController; la vue "sait" qu'il est correct de demander poliment à votre navigationController de pousser une vue, puisque le viewController conforme au protocole navigationController. Alors que vous pouvez créer votre propre protocole, utilisez simplement L'exemple de Gabriel et réutilisez le protocole UINavigationController.

6
répondu PapaSmurf 2011-04-05 21:00:14

Je ne pense pas que ce soit une" mauvaise " idée de découvrir qui est le contrôleur de vue pour certains cas. Ce qui pourrait être une mauvaise idée est de sauvegarder la référence de ce controller car il pourrait changer juste comme les superviews changent. Dans mon cas, j'ai un getter qui traverse la chaîne des intervenants.

//.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
répondu Rivera 2012-05-24 00:10:10

la boucle la plus simple pour trouver le viewController.

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

    do
    {
        nextResponder = [nextResponder nextResponder];

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

    } while (nextResponder != nil);

    return nil;
}
5
répondu Mohd Iftekhar Qurashi 2014-04-07 06:31:34

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 elle-même(ce n'est vraiment pas beaucoup plus qu'un bouton qui ouvre un PopoverController ).

alors que cela fonctionne très bien dans l'iPad (le UIPopoverController se présente, donc n'a pas besoin de référence à un UIViewController ), obtenir le même code à travailler signifie soudainement se référer à votre presentViewController de votre UIViewController . Un peu incohérent droit?

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

dans tous les cas, voici une solution swift, qui ajoute une nouvelle propriété à tout 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
    }
}
4
répondu Kevin R 2016-08-05 12:13:06

cela ne répond pas directement à la question, mais fait plutôt une hypothèse sur l'intention de la question.

si vous avez une vue et dans cette vue vous devez appeler une méthode sur un autre objet, comme par exemple le contrôleur de vue, vous pouvez utiliser le NSNotificationCenter à la place.

créez D'abord votre chaîne de notification dans un fichier d'en-tête

#define SLCopyStringNotification @"ShaoloCopyStringNotification"

À votre avis d'appel postNotificationName:

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

puis dans votre contrôleur de vue vous ajoutez un observateur. Je le fais ce dans le viewDidLoad

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

maintenant (également dans le même contrôleur de vue) implémentez votre méthode copyString: comme décrit dans le @selector ci-dessus.

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

Je ne dis pas que c'est la bonne façon de faire ça, ça semble juste plus propre que de remonter la chaîne des premiers intervenants. J'ai utilisé ce code pour implémenter un UIMenuController sur un UITableView et faire remonter l'événement à la UIViewController pour que je puisse faire quelque chose avec les données.

3
répondu Shaolo 2013-04-15 05:38:45

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 rapide 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 des données de suivi, cela ne justifie pas l'utilisation d'un protocole. Je stocke Personnellement 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) {}

tous les crédits to @Phil_M

3
répondu Paul Slm 2016-03-26 07:27:15

à la réponse de Phil:

dans la ligne: id nextResponder = [self nextResponder]; si self(UIView) n'est pas un sous-View de la vue de ViewController, si vous connaissez la hiérarchie de self(UIView) vous pouvez utiliser aussi: id nextResponder = [[self superview] nextResponder]; ...

1
répondu sag 2013-06-04 14:35:30

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; \
})
1
répondu lee 2018-01-03 09:07:33

ma solution serait probablement considérée comme une sorte de faux, mais j'avais une situation similaire à mayoneez (je voulais changer de point de vue en réponse à un geste dans un EAGLView), et j'ai obtenu le contrôleur de point de vue EAGL de cette façon:

EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
0
répondu gulchrider 2011-08-07 19:43:30

je pense qu'il y a un cas où les besoins observés à informer l'observateur.

je vois un problème similaire lorsque L'UIView dans un Controller UIViewController répond à une situation et qu'il doit d'abord dire à son contrôleur de vue parent de cacher le bouton back et ensuite, une fois terminé, dire au contrôleur de vue parent qu'il doit se détacher de la pile.

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

I ne comprends pas pourquoi ce devrait être une mauvaise idée?

0
répondu user7865437 2011-12-08 08:16:28

une autre façon facile est d'avoir votre propre classe de vue et d'ajouter une propriété du contrôleur de vue dans la classe de vue. Habituellement le controller view crée la vue et c'est là que le controller peut se mettre à la propriété. Au fond, c'est la même chose au lieu de chercher (avec un peu de piratage) le contrôleur, en lui demandant de se positionner sur la vue - c'est simple mais logique car c'est le contrôleur qui "contrôle" la vue.

0
répondu jack 2012-10-25 01:08:16

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)
}
0
répondu Dicle Yilmaz 2018-03-04 20:37:25

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
}

}

exemple d'utilisation

 if let parent = self.view.parentViewController{

    }
0
répondu levin varghese 2018-06-22 05:12:56

solution plus rapide

extension UIView {
    var parentViewController: UIViewController? {
        for responder in sequence(first: self, next: { "151900920".next }) {
            if let viewController = responder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}
0
répondu Igor Palaguta 2018-08-09 11:14:24

si votre rootViewController est UINavigationViewController, qui a été configuré dans la classe AppDelegate, alors

    + (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];

for (UIViewController *v in arrVc)
{
    if ([v isKindOfClass:c])
    {
        return v;
    }
}

return nil;}

où c Classe de contrôleur de vue requise.

USAGE:

     RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
-1
répondu Ansari Awais 2015-02-06 12:30:33

il n'y a pas moyen.

ce que je fais c'est passer le pointeur UIViewController à L'UIView (ou un héritage approprié). Je suis désolé de ne pas pouvoir aider L'IB à aborder le problème parce que je ne crois pas en L'IB.

pour répondre au premier commentateur: parfois, vous avez besoin de savoir qui vous a appelé parce qu'il détermine ce que vous pouvez faire. Par exemple, avec une base de données, vous pouvez avoir accès en lecture seule ou en lecture/écriture ...

-5
répondu John Smith 2009-08-27 11:32:49