Existe-t-il un moyen intégré pour passer de a UIView
à son UIViewController
? Je sais que vous pouvez passer de UIViewController
à son UIView
via [self view]
mais je me demandais s'il y avait une référence inverse?
Existe-t-il un moyen intégré pour passer de a UIView
à son UIViewController
? Je sais que vous pouvez passer de UIViewController
à son UIView
via [self view]
mais je me demandais s'il y avait une référence inverse?
Réponses:
Puisque c'est la réponse acceptée depuis longtemps, je sens que je dois la rectifier avec une meilleure réponse.
Quelques commentaires sur le besoin:
Voici un exemple de 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
fait, par exemple) et peu importe si elle est implémentée dans le contrôleur de vue ou dans toute autre classe que vous finissez par utiliser.
Ma réponse originale suit: Je ne recommande pas cela, ni le reste des réponses où un accès direct au contrôleur de vue est obtenu
Il n'y a pas de moyen intégré de le faire. Bien que vous puissiez le contourner en ajoutant un IBOutlet
sur UIView
et en les connectant dans Interface Builder, cela n'est pas recommandé. La vue ne doit pas connaître le contrôleur de vue. Au lieu de cela, vous devez faire comme @Phil M le suggère et créer un protocole à utiliser en tant que délégué.
En utilisant l'exemple posté par Brock, je l'ai modifié pour qu'il s'agisse d'une catégorie de UIView à la place de UIViewController et l'ai rendu récursif afin que toute 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 le @interface dans l'en-tête, et le @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];
UIView
est une sous-classe de UIResponder
. UIResponder
présente la méthode -nextResponder
avec une implémentation qui retourne nil
. UIView
remplace cette méthode, comme décrit dans UIResponder
(pour une raison quelconque au lieu de in UIView
) comme suit: si la vue a un contrôleur de vue, il est renvoyé par -nextResponder
. S'il n'y a pas de contrôleur de vue, la méthode retournera le superview.
Ajoutez ceci à votre projet et vous êtes prêt à démarrer.
@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
A maintenant UIView
une méthode de travail pour renvoyer le contrôleur de vue.
UIView
et le UIViewController
. La réponse de Phil M avec la récursivité est la voie à suivre.
Je suggérerais une approche plus légère pour parcourir la chaîne de répondeurs 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
En combinant plusieurs réponses déjà données, j'expédie également dessus 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 fonctionnalité ARC bibliothèque statique que j'expédie sur chaque application que je crée. Il a été testé plusieurs fois et je n'ai trouvé ni problème ni fuite.
PS: 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 dernier cas, mettez simplement la méthode dans votre sous-classe et vous êtes prêt à partir.
Même si cela peut techniquement être résolu comme le recommande pgb , à mon humble avis , il s'agit d'un défaut de conception. La vue ne devrait pas avoir besoin de connaître le contrôleur.
J'ai modifié de réponse afin que je puisse passer une vue, bouton, étiquette etc pour obtenir des parents est tout 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;
}
Modifier la version Swift 3
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)!
}
}
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. À partir de là, si vous utilisez par exemple un contrôleur de vue de navigation et que vous souhaitez 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. Faites ceci lorsque vous créez le contrôleur pour la première fois, par exemple dans votre délégué d'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];
}
[[self navigationController] view]
c'est la (sous) vue "principale" de la fenêtre, la rootViewController
propriété de la fenêtre doit être définie sur navigationController
laquelle contrôle la vue "principale" immédiatement.
Bien que ces réponses soient techniquement correctes, y compris Ushox, je pense que la manière approuvée est de mettre en œuvre un nouveau protocole ou de réutiliser un protocole existant. Un protocole isole l'observateur de l'observé, un peu comme mettre une fente de courrier entre eux. En effet, c'est ce que fait Gabriel via l'invocation de la méthode pushViewController; la vue "sait" que c'est un protocole approprié pour demander poliment à votre navigationController de pousser une vue, puisque le viewController est conforme au protocole navigationController. Bien que vous puissiez créer votre propre protocole, il suffit d'utiliser l'exemple de Gabriel et de réutiliser le protocole UINavigationController.
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
).
Bien que cela fonctionne correctement dans l'iPad (le se UIPopoverController
présente, il n'est donc pas nécessaire de faire référence à a UIViewController
), faire fonctionner le même code signifie soudainement référencer votre presentViewController
depuis votreUIViewController
. Un peu incohérent, non?
Comme mentionné précédemment, ce n'est pas la meilleure approche pour avoir de la logique dans votre UIView. Mais il semblait vraiment inutile d'envelopper les quelques lignes de code nécessaires dans un contrôleur séparé.
Quoi qu'il en soit, 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
}
}
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 tout comme les superviews changent. Dans mon cas, j'ai un getter qui traverse la chaîne de répondeurs.
//.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;
}
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;
}
(plus concis que les autres réponses)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
Mon cas d'utilisation pour lequel je dois d'abord accéder à la vue UIViewController
: j'ai un objet qui s'enroule autour AVPlayer
/ AVPlayerViewController
et je souhaite fournir une show(in view: UIView)
méthode simple qui s'intègre AVPlayerViewController
dans view
. Pour cela, j'ai besoin d'accéder view
aux fichiers UIViewController
.
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 que 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"
Dans votre vue, appelez postNotificationName:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
Ensuite, dans votre contrôleur de vue, vous ajoutez un observateur. Je fais cela dans 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 illustré 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 cela, cela 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 transmettre l'événement à l'UIViewController afin que je puisse faire quelque chose avec les données.
C'est sûrement une mauvaise idée et une mauvaise conception, 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 une boîte de dialogue modale 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
Peut-être que je suis en retard ici. Mais dans cette situation je n'aime pas la catégorie (pollution). J'adore cette façon:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
Solution plus rapide
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}
sequence
bit). Donc, si "swifty" signifie "plus fonctionnel", alors je suppose que c'est plus rapide.
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)
}
Version Swift 4
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{
}
return
mot - clé maintenant 🤓Solution 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
Solution 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
À la réponse de Phil:
En ligne: id nextResponder = [self nextResponder];
si self (UIView) n'est pas une sous-vue de la vue ViewController, si vous connaissez la hiérarchie de self (UIView), vous pouvez également utiliser: id nextResponder = [[self superview] nextResponder];
...
Ma solution serait probablement considérée comme un peu bidon, mais j'ai eu une situation similaire à mayoneez (je voulais changer de vue en réponse à un geste dans un EAGLView), et j'ai obtenu le contrôleur de vue de l'EAGL de cette façon:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];
.
ClassName *object
- avec un astérisque.
Je pense qu'il y a un cas où l'observé doit informer l'observateur.
Je vois un problème similaire où l'UIView dans un UIViewController répond à une situation et il doit d'abord dire à son contrôleur de vue parent de masquer le bouton Précédent, puis, une fois terminé, dire au contrôleur de vue parent qu'il doit se sortir de la pile.
J'ai essayé cela avec des délégués sans succès.
Je ne comprends pas pourquoi cela devrait être une mauvaise idée?
Un autre moyen simple consiste à avoir votre propre classe de vue et à ajouter une propriété du contrôleur de vue dans la classe de vue. Habituellement, le contrôleur de vue crée la vue et c'est là que le contrôleur peut se définir sur la propriété. Fondamentalement, c'est au lieu de chercher (avec un peu de piratage) le contrôleur, d'avoir le contrôleur pour se définir sur la vue - c'est simple mais logique car c'est le contrôleur qui "contrôle" la vue.
Si vous n'allez pas le télécharger sur l'App Store, vous pouvez également utiliser une méthode privée d'UIView.
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
var parentViewController: UIViewController? {
let s = sequence(first: self) { $0.next }
return s.compactMap { $0 as? UIViewController }.first
}
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 requis la classe de contrôleurs de vue.
USAGE:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
Il n'y a pas moyen.
Ce que je fais est de transmettre le pointeur UIViewController à l'UIView (ou un héritage approprié). Je suis désolé de ne pas pouvoir vous aider avec l'approche IB du problème car je ne crois pas en IB.
Pour répondre au premier commentateur: parfois, vous avez besoin de savoir qui vous a appelé car 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 ...