40 votes

iPhone : Créez un composant réutilisable (contrôle) qui contient des éléments d'Interface Builder et du code.

Je veux créer un composant réutilisable (un contrôle personnalisé) pour l'iPhone. Il s'agit de plusieurs contrôles standards prédisposés sur une vue, puis d'un code associé. Mes objectifs sont les suivants :

  1. Je veux pouvoir utiliser Interface Builder pour mettre en page les sous-vues de mon contrôle personnalisé ;
  2. Je veux d'une manière ou d'une autre emballer le tout de sorte que je puisse ensuite assez facilement glisser et déposer le composant personnalisé résultant dans d'autres vues, sans avoir à recâbler manuellement un tas de prises et ainsi de suite. (Un peu de recâblage manuel, ça va, mais je ne veux pas en faire des tonnes et des tonnes).

Permettez-moi d'être plus concret, et de vous dire précisément ce que mon contrôle est censé faire. Dans mon application, j'ai parfois besoin d'appeler un service web pour valider les données que l'utilisateur a saisies. En attendant une réponse du service web, je veux afficher un spinner (un indicateur d'activité). Si le service web répond avec un code de réussite, je veux afficher une coche "réussite". Si le service Web répond par un code d'erreur, je veux afficher une icône d'erreur et un message d'erreur.

La méthode à usage unique est assez simple : je crée simplement une UIView qui contient une UIActivityIndicatorView, deux UIImages (une pour l'icône de réussite et une pour l'icône d'erreur), et une UILabel pour le message d'erreur. Voici une capture d'écran, avec les parties pertinentes marquées en rouge :

alt text

Je connecte ensuite les pièces aux prises, et je mets du code dans mon contrôleur.

Mais comment emballer ces éléments - le code et la petite collection de vues - afin de pouvoir les réutiliser ? Voici quelques solutions que j'ai trouvées et qui me permettent d'y arriver, mais qui ne sont pas si géniales :

  • Je peux faire glisser la collection de vues et de contrôles dans la section Objets personnalisés de la bibliothèque ; puis, plus tard, je peux les faire glisser à nouveau sur d'autres vues. Mais (a) cela oublie quelles images étaient associées aux deux UIImages, (b) il y a beaucoup de recâblage manuel de quatre ou cinq prises, et (c) surtout, cela n'apporte pas le code. (Il y a peut-être un moyen facile de câbler le code).
  • Je pense que je pourrais créer un IBPlugin ; je ne suis pas sûr que cela soit utile, et cela semble représenter beaucoup de travail. De plus, je ne sais pas vraiment si les IBPlugins fonctionnent pour le développement de l'iPhone.
  • Je me suis dit : "Hmm, il y a du code associé à cela - ça sent le contrôleur", alors j'ai essayé de créer un contrôleur personnalisé (par ex. WebServiceValidatorController ) avec le fichier XIB associé. Cela semble vraiment prometteur, mais à ce moment-là, je n'arrive pas à trouver comment, dans Interface Builder, faire glisser ce composant sur d'autres vues. Le site WebServiceValidatorController est un contrôleur et non une vue. Je peux donc le faire glisser dans une fenêtre de document, mais pas dans une vue.

J'ai l'impression que je rate quelque chose d'évident...

21voto

Bogatyr Points 12027

Voici comment je résous un problème similaire : je voulais.. :

  • créer des "classes de modules de widgets" réutilisables et autonomes mettant en œuvre une vue complexe construite à partir de plusieurs composants UIKit (et d'autres classes de modules de widgets imbriquées !). Les classes clientes de niveau supérieur de ces classes de widgets ne se soucient pas de savoir ce qu'est un UILabel, ce qu'est un UIImageView en interne dans le widget, les classes clientes ne se soucient que de concepts comme "afficher le score" ou "afficher le logo de l'équipe".

  • au sein d'une classe de module de widget, définissez l'interface utilisateur pour le widget et les prises de connexion à l'aide du constructeur d'interface.

  • pour les clients de niveau supérieur d'un widget, je voulais pouvoir placer le cadre du widget dans la vue du client dans interface builder sans avoir à concevoir des plugins personnalisés pour IB, etc.

Ces widgets ne sont pas des contrôleurs de premier niveau : il n'est donc pas logique qu'ils soient des sous-classes de UIViewController. Il y a aussi le conseil d'Apple de ne pas avoir plus d'un VC sur un écran visible à la fois. De plus, il y a tellement de conseils et d'opinions qui circulent comme "MVC ! MVC ! Vous devez séparer votre vue et votre contrôle ! MVC !" que les gens sont si fortement découragés de sous-classer UIView ou de placer la logique de l'application dans une sous-classe UIView.

Mais j'ai décidé de le faire quand même (sous-classe UIView). J'ai fait le tour de la question plusieurs fois (mais je suis encore assez nouveau dans le SDK/UIKit de l'iPhone), et je suis très sensible à la conception qui est laide, et qui peut causer des problèmes, et je ne vois franchement pas le problème de sous-classer UIView et d'ajouter des outlets et des actions. En fait, il y a de nombreux avantages à faire en sorte que votre widget réutilisable soit une sous-classe de UIView plutôt que d'être basé sur UIViewController :

  • Vous pouvez placer une instance directe de la classe widget dans le layout du constructeur d'interface d'une vue client, et le cadre UIView de la vue widget sera correctement initialisé lorsque la classe client sera chargée à partir du xib. Cela me semble beaucoup plus propre que de placer une "vue de remplacement" dans le client, puis d'instancier le module widget de manière programmatique, de définir le cadre du widget sur celui du remplacement, puis de remplacer la vue de remplacement par la vue du widget.

  • Vous pouvez créer un fichier xib propre et simple pour disposer les composants du widget dans le constructeur d'interface. Le propriétaire du fichier est défini comme étant votre classe de widget. Le fichier nib contient une UIView racine (vanille) où toute l'interface graphique est disposée, puis dans la méthode awakeFromNib : du widget, cette vue nib est ajoutée au widget lui-même en tant que sous-vue. Tout ajustement de la taille de l'image peut être géré ici, entièrement dans le code du widget, si cela est nécessaire. Par exemple :

    • (void) awakeFromNib { [[NSBundle mainBundle] loadNibNamed:@"MyWidgetView" owner:self options:nil]; [self addSubview:self.view]; }
  • Le widget initialise lui-même son interface utilisateur en utilisant la méthode loadNibNamed:owner:options de NSBundle dans sa propre méthode awakeFromNib, de sorte que l'intégration du widget dans les classes du client est simple : Il suffit de déposer un UIView dans la vue du client dans IB, de changer la classe de l'UIView en MyWidgetView et, dans le init ou le viewDidLoad du client, de définir les valeurs par défaut de l'affichage du widget en utilisant l'API du widget que vous écrivez vous-même (setScore : setTeamImage : etc.). )

Il me semble qu'il n'y a essentiellement aucune différence dans le code résultant entre la sous-classification d'un UIView de cette manière pour créer un "widget" réutilisable, et l'utilisation d'un UIViewController. Dans les deux cas, les fichiers .h contiennent les membres de la classe widget, les outlets, les déclarations d'action et les déclarations d'API spéciales. Dans les deux cas, le fichier .m contient des implémentations d'actions et des implémentations d'API spéciales. Et la vue EST séparée du contrôle - la vue est dans le fichier xib !

Donc, en résumé, voici ce que je fais pour emballer des widgets de vue complexes et réutilisables :

  • Faire de la classe du widget une sous-classe de UIView

  • Instanciez le widget où vous voulez, dans d'autres vues de votre application dans IB directement en faisant glisser un UIView de la bibliothèque d'outils, et en changeant la classe pour votre "MyWidgetClass".

  • Concevez l'interface utilisateur de votre widget en IB dans une nib à l'intérieur d'une UIView vanilla Root.

  • dans la méthode awakeFromNib : de la classe widget, charger l'interface utilisateur du widget à partir de xib et faire [self addSubview:self.theXibRootView]

  • Codez le widget comme vous le feriez pour un UIViewController : points de vente, actions, API spéciales.

J'aimerais connaître d'autres systèmes structurels permettant de créer des widgets réutilisables, et savoir quels sont leurs avantages par rapport à cette approche.

Si le widget doit signaler des événements à la classe du client, il suffit d'utiliser un protocole de délégué où le client définit le délégué du widget comme self, comme dans un UIViewController.

12voto

Michal Points 3353

J'ai créé des constructions similaires, sauf que je n'utilise pas le résultat dans IB, mais que j'instancie en utilisant le code. Je vais décrire comment cela fonctionne et, à la fin, je vous donnerai un indice sur la façon dont cela peut être utilisé pour accomplir ce que vous recherchez.

Je pars d'un fichier XIB vide où j'ajoute une vue personnalisée au niveau supérieur. Je configure cette vue personnalisée comme étant ma classe. En dessous dans la hiérarchie des vues, je crée et configure des sous-vues selon les besoins.

Je crée tous les IBOutlets dans ma classe de vue personnalisée, et je les connecte à cet endroit. Dans cet exercice, j'ignore complètement le "propriétaire du fichier".

Maintenant, lorsque j'ai besoin de créer la vue (généralement dans le contrôleur dans le cadre d'un while/for-loop pour en créer autant que nécessaire), j'utilise la fonctionnalité de NSBundle comme ceci :

- (void)viewDidLoad
{
    CGRect fooBarViewFrame = CGRectMake(0, 0, self.view.bounds.size.width, FOOBARVIEW_HEIGHT);
    for (MBSomeData *data in self.dataArray) {
        FooBarView *fooBarView = [self loadFooBarViewForData:data];
        fooBarView.frame = fooBarViewFrame;
        [self.view addSubview:fooBarView];

        fooBarViewFrame = CGRectOffset(fooBarViewFrame, 0, FOOBARVIEW_HEIGHT);
    }
}

- (FooBarView*)loadFooBarViewForData:(MBSomeData*)data
{
    NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"FooBarView" owner:self options:nil];
    FooBarView *fooBarView = [topLevelObjects objectAtIndex:0];
    fooBarView.barView.amountInEuro = data.amountInEuro;
    fooBarView.barView.gradientStartColor = data.color1;
    fooBarView.barView.gradientMidColor = data.color2;
    fooBarView.titleLabel.text = data.name;
    fooBarView.titleLabel.textColor = data.nameColor;
    return fooBarView;
}

Remarquez, comment j'ai mis le propriétaire de la plume à self - il n'y a pas de mal puisque je n'ai pas relié le "propriétaire du fichier" à quoi que ce soit. Le seul résultat valable du chargement de cette plume est son premier élément - ma vue.

Si vous souhaitez adapter cette approche pour créer des vues dans IB, c'est assez simple. Implémentez le chargement d'une vue secondaire dans une vue principale personnalisée. Définissez le cadre de la vue secondaire en fonction des limites de la vue principale, afin qu'elles aient la même taille. La vue principale deviendra le conteneur de votre vue personnalisée réelle, et aussi une interface pour le code externe - vous ouvrez seulement les propriétés nécessaires de ses sous-vues, le reste est encapsulé. Ensuite, il vous suffit de déposer la vue personnalisée dans IB, de la configurer pour qu'elle soit votre classe et de l'utiliser comme d'habitude.

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