Il y a 9 ans -
Temps de lecture 18 minutes
Article Programmez « Démarrer avec iOS » (2/2)
Introduction
Avec le précédent article, nous avons étudié les bases d’un projet iOS et vu comment mettre en place un écran contenant une tableView avec des cellules personnalisées, tout en récupérant des données depuis un serveur distant. Dans ce nouvel article, nous irons plus loin en ajoutant quelques écrans et en présentant des outils qui facilitent la vie des développeurs iOS.
Pour commencer, si vous n’avez pas lu la première partie de cet article, il est possible d’en récupérer le code source complet depuis cet adresse : https://github.com/xebia-france/programmez-MovieApp/releases/tag/1.0.
CocoaPods
Il existe des milliers de composants et bibliothèques de qualité sur internet. Ces derniers permettent souvent un gain de temps conséquent lors du développement d’une application. Cependant, les récupérer, gérer les différentes versions, les mises à jour ou encore les configurer peut faire perdre un temps précieux ! Afin de pallier ce problème, nous vous conseillons d’utiliser le gestionnaire de dépendances appelé CocoaPods.
Afin de l’utiliser, il suffit de spécifier les dépendances utilisées dans un simple fichier texte pour que CocoaPods résolve les dépendances entre les bibliothèques, récupère les sources, et crée et/ou mette à jour un workspace Xcode afin de compiler votre projet en toute tranquillité.
L’installation de CocoaPods est très facile. Pour cela, il faut s’assurer d’avoir Ruby MRI 2.0.0 ou 1.8.7 et Xcode command line tools installés sur votre machine. Ensuite, il suffit d’exécuter la commande suivante : [sudo] gem install cocoapods. Il est possible d’utiliser la même commande afin de mettre à jour CocoaPods.
Une fois CocoaPods installé, il est maintenant temps de créer le fichier Podfile qui va lister l’ensemble des dépendances que nous souhaitons utiliser (il est possible de rechercher des dépendances sur http://cocoapods.org). Pour cela, créez un fichier nommé ‘Podfile’ dans le même répertoire que votre projet puis ajoutez une ligne par dépendance. Par exemple, si votre projet utilise la librairie XBToolKit, il suffit d’ajouter la ligne suivante : pod ‘XBToolkit’, ‘~> 0.0.11’ (nous verrons par la suite ce qu’est XBToolKit).
Enfin, pour récupérer les sources et configurer automatiquement votre projet, il suffit d’exécuter la commande suivante dans le dossier de votre projet : pod install. Si, par la suite, de nouvelles dépendances sont ajoutées au fichier Podfile, il est possible d’exécuter de nouveau « pod install » afin d’installer les dépendances manquantes. Il est également possible de mettre à jour l’ensemble des dépendances en exécutant « pod update ».
Avec CocoaPods, vous pouvez réaliser des configurations très avancées. L’ensemble des possibilités se trouve sur http://docs.cocoapods.org/index.html.
Une application plus robuste
Dans le chapitre précédent, nous avons créé MADataSource qui nous a permis de récupérer et stocker la liste des films au box-office. Bien que cette approche fonctionne pour une petite application avec un nombre limité d’interactions avec un serveur, elle ne peut être considérée comme appropriée pour des cas plus complexes où une communication plus profonde avec les API serveur s’impose.
Un autre élément sur lequel nous allons travailler est la robustesse de notre code : la précédente version de notre application stockait les données à l’intérieur d’un NSDictionary. NSDictionary est une structure de données suffisamment puissante mais pas assez fiable si nous travaillons en équipe et que le modèle de données est complexe.
Pour ces raisons notamment, nous allons maintenant introduire un composant open-source, nommé XBToolkit, qui nous permettra de traiter à la fois les requêtes réseau et les modèles de données d’une manière simple et robuste. XBToolkit nous donne la possibilité de récupérer un fichier JSON à partir d’un Web Service et de le transformer en objets métier, en utilisant un modèle de données que nous allons fournir à ses fonctions. XBToolkit se base sur des bibliothèques open-source largement adoptées, comme par exemple AFNetworking.
Pour installer XBToolkit, nous devons suivre les étapes décrites dans l’introduction de cet article.
Notre fichier Podfile devra contenir les lignes suivantes :
platform : iOS , '6 .0 ' pod 'XBToolkit', '0 .0.11 '
Nous pouvons maintenant clore le fichier ouvrir le Terminal et écrire :
cd <Mon Dossier MovieApp> pod install
Une fois l’exécution terminée, un autre fichier, nommé MovieApp.xcworkspace, apparaît dans notre dossier. Ce fichier sera désormais notre nouveau projet que nous allons ouvrir à chaque fois que nous voudrons travailler sur MovieApp. Comme le suffixe l’indique, MovieApp.xcworkspace est un espace de travail, c’est-à-dire est un espace comprenant des références multiples vers d’autres projets. Dans notre cas, MovieApp.xcworkspace comprend notre ancien MovieApp.xcodeproj ainsi qu’un nouveau projet contenant toutes les sources de nos bibliothèques externes, comme XBToolkit.
Création du modèle de données
Comme mentionné précédemment, afin de produire une implémentation robuste, la liste des films que nous téléchargeons à partir du service Web sera transformée en une liste d’instances d’objets métier.
Dans Xcode, nous pouvons créer ces objets dans nos projets en cliquant sur :
« File -> New -> File -> Objective-C class -> Next ».
Dans le champ de texte « Class », nous écrirons « MAMovie » et dans « Subclass of », nous saisirons « NSObject » .
Afin que XBToolkit puisse convertir le fichier JSON en instances de classe MAMovie, nous devons préciser que notre objet doit être mappé en utilisant la fonctionnalité de « Mapping » de la bibliothèque. Pour ce faire, nous allons importer XBMappingProvider dans MAMovie.h :
#import "XBMappingProvider.h"
et nous allons spécifier que MAMovie implémente ce protocole en ajoutant à côté de NSObject <XBMappingProvider>. La ligne de l’interface devra donc être comme suit :
@interface MAMovie : NSObject <XBMappingProvider>
En dessous de @interface nous pouvons maintenant ajouter toutes les propriétés qui seront remplies automatiquement avec les valeurs JSON :
@property (nonatomic, strong) NSString *internalBaseClassIdentifier; @property (nonatomic, strong) NSString *synopsis; @property (nonatomic, strong) NSArray *abridgedCast; @property (nonatomic, strong) NSString *criticsConsensus; @property (nonatomic, strong) NSString *mpaaRating; @property (nonatomic, strong) NSString *title; @property (nonatomic, strong) NSNumber *runtime; @property (nonatomic, strong) NSNumber *year; @property (nonatomic, strong) MAPosters *posters; @property (nonatomic, strong) NSDictionary *ratings; @property (nonatomic, strong) NSArray *genres; @property (nonatomic, strong) NSDictionary *links;
À l’intérieur de @implementation dans MAMovie.m, nous devons maintenant ajouter le code suivant qui instancie une configuration vide pour le mapping de MAMovie :
+ (DCParserConfiguration *)mappings { DCParserConfiguration *config = [[DCParserConfiguration alloc] init]; return config; }
Une fois que la classe qui contiendra les données de films est créée, nous allons faire de même pour les posters. Dans Xcode, nous allons ajouter une nouvelle classe Objective-C, du nom de MAPosters, sous-classe de NSObject. Le code de la classe sera très similaire à celui que nous avons écrit dans MAMovie.
#import <Foundation/Foundation.h> #import "XBMappingProvider.h" @interface MAPosters : NSObject<XBMappingProvider> @property (nonatomic, strong) NSString *original; @property (nonatomic, strong) NSString *detailed; @property (nonatomic, strong) NSString *thumbnail; @property (nonatomic, strong) NSString *profile; @end
#import <Foundation/Foundation.h> #import "MAPosters.h" @implementation MAPosters + (DCParserConfiguration *)mappings { DCParserConfiguration *config = [[DCParserConfiguration alloc] init]; return config; } @end
Revenons à MAMovie.h et ajoutons une autre importation de MAPosters.h (# import « MAPosters.h») pour corriger l’erreur de compilation.
Qu’avons-nous fait ici ? Le code que nous venons d’écrire nous permet de convertir automatiquement toutes les valeurs de « posters » figurant dans le JSON en instances d’objets MAPosters.
Création d’un client d’API
Nous continuons nos améliorations en supprimant MADataSource.h et MADataSource.m qui seront remplacés par un code plus propre. Retirez toutes les références à MADataSource de notre projet et, en particulier, de MATableViewController.h et MATableViewController.m.
Notre prochaine étape consistera à créer un client d’API, qui est une classe qui s’occupera de toutes les interactions de notre application avec le serveur. Notre client d’API sera un singleton, c’est à dire une classe à laquelle on peut accéder à partir de différentes parties de notre code sans être instanciée plusieurs fois. Par exemple, dans le développement iOS, UIApplication est un singleton dont l’instance peut être consultée partout avec la syntaxe [UIApplication sharedApplication].
Créons donc notre client d’API, en ajoutant une nouvelle classe nommée MAMovieAppAPIClient, sous-classe de XBHttpClient.
MAMovieAppAPIClient.h sera comme suit.
#import "XBHttpClient.h" typedef void(^MABoxOfficeCallbackBlock)(NSArray *array); @interface MAMovieAppAPIClient : XBHttpClient + (MAMovieAppAPIClient *)sharedClient; - (void)downloadBoxOfficeWithCallback:(MABoxOfficeCallbackBlock)callback; @end
L’en-tête de la classe expose simplement quelques méthodes publiques que nous allons mettre en œuvre dans MAMovieAppAPIClient.m.
Ouvrons MAMovieAppAPIClient.m et, au dessus de la ligne @implementation, ajoutons des chaînes de caractères statiques et des importations qui seront utilisées plus tard.
#import "XBHttpJsonDataLoader.h" #import "XBJsonToArrayDataMapper.h" #import "XBReloadableArrayDataSource.h" #import "MAMovie.h" static NSString *kAPI_KEY = @"YOUR_API_KEY"; static NSString *kBoxOfficePath = @"lists/movies/box_office.json"; static NSString *kMAMovieAppAPIBaseURLString = @"http://api.rottentomatoes.com/api/public/v1.0/";
En dessous de @implementation, ajoutons le code suivant :
+ (instancetype)sharedClient { static MAMovieAppAPIClient *_sharedClient = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedClient = [[self alloc] initWithBaseUrl:kMAMovieAppAPIBaseURLString]; }); return _sharedClient; } - (id)initWithBaseUrl:(NSString *)url { self = [super initWithBaseUrl:url]; if (!self) { return nil; } return self; }
Vu que notre client d’API est un singleton, la fonction sharedClient sera le point d’entrée unique de notre exemple. Les lignes que nous venons d’écrire, et en particulier la fonction dispatch_once, précisent que le code à l’intérieur ne doit être effectué qu’une seule fois, de sorte qu’une seule instance du client API puisse être créée.
Ajoutons la méthode « rottentTomatoesUrlForPath » permettant de générer une URL compatible avec les Rotten Tomatoes services Web.
- (NSString *)rottenTomatoesUrlForPath:(NSString *)relativePath { // Retrieve the current user country code NSLocale *locale = [NSLocale currentLocale]; NSString *countryCode = [locale objectForKey: NSLocaleCountryCode]; // Create the params dictionary NSDictionary *params = @{@"country": countryCode, @"limit": @20}; // Create a new mutable string starting with our base URI NSMutableString *urlString = [NSMutableString stringWithString:@""]; // Append the relative path and our api key [urlString appendString:relativePath]; [urlString appendFormat:@"?apikey=%@",kAPI_KEY]; // Append all params in the dictionary as key = obj [params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [urlString appendFormat:@"&%@=%@", key, obj]; }]; NSLog(@"Loading data from : '%@'", urlString); return urlString; }
Et enfin, ajoutons la méthode downloadBoxOfficeWithCallback, comme suit :
- (void)downloadBoxOfficeWithCallback:(MABoxOfficeCallbackBlock)callback { // Instantiate a dataLoader from your HTTP client and a given resourcePath: XBHttpJsonDataLoader *boxOfficeDataLoader = [XBHttpJsonDataLoader dataLoaderWithHttpClient:self resourcePath:[self rottenTomatoesUrlForPath:kBoxOfficePath]]; // Instantiate a dataMapper, allowing the response to be deserialized to a given class (e.g. MAMovie): XBJsonToArrayDataMapper *boxOfficeDataMapper = [XBJsonToArrayDataMapper mapperWithRootKeyPath:@"movies" typeClass:[MAMovie class]]; // Create the data source from the dataLoader and the dataMapper: XBReloadableArrayDataSource *dataSource = [XBReloadableArrayDataSource dataSourceWithDataLoader:boxOfficeDataLoader dataMapper:boxOfficeDataMapper]; [dataSource loadDataWithCallback:^{ if (callback) { callback(dataSource.array); } }]; }
downloadBoxOfficeWithCallback est une méthode simple mais puissante qui, avec un nombre de lignes succinct, prend en charge les étapes suivantes :
- Création d’une instance s’occupant de gérer le téléchargement du box-office depuis le Web Service en utilisant XBHttpJsonDataLoader.
- Mapping des films au box-office vers notre objet MAMovie.
- Création d’un dataSource qui relie téléchargement et mapping.
- Lancer le téléchargement et déclencher la méthode de callback.
Grâce à cette implémentation, il est vraiment facile d’étendre les fonctionnalités du client d’API. Si nous souhaitons récupérer des données supplémentaires depuis les services Web de Rotten Tomatoes, il suffit de créer une méthode similaire à downloadBoxOfficeWithCallback à l’intérieur de notre classe de client d’API.
Affichage des données
Maintenant que nous avons créé le client API, nous devons rétablir les fonctionnalités de notre application.
Revenons à MATableViewController.m et :
- importons « MAMovieAppAPIClient.h », « MAMovie.h » et « UIImageView+AFNetworking.h »,
- ajoutons les propriétés privés «movies» entre @interface et @end, comme suit :
@property (nonatomic, strong) NSArray *movies;
Dans viewDidLoad, nous allons ajouter les lignes suivantes.
[[MAMovieAppAPIClient sharedClient] downloadBoxOfficeWithCallback:^(NSArray *array) { self.movies = array; [self.tableView reloadData]; }];
Ce code exécute la méthode de downloadBoxOffice de notre client d’API singleton et, une fois l’opération terminée, s’occupe de stocker les résultats à l’intérieur de la propriété movies et de recharger la tableView.
Dans la méthode
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
nous allons remplacer l’implémentation existante par return [self.movies count];
Enfin, nous remplaçons l’implémentation de cellForRowAtIndexPath par la suivante :
static NSString *CellIdentifier = @"MovieCell"; MAMovieCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; // Retrieve the MAMovie object MAMovie *movie = self.movies[indexPath.row]; cell.titleLabel.text = movie.title; cell.subtitleLabel.text = [movie.year stringValue]; // The syntax movie.posters.thumbnail allows to retrieve the thumbnail string from the object MAPosters contained in MAMovie [cell.thumbnail setImageWithURL:[NSURL URLWithString:movie.posters.thumbnail] placeholderImage:nil]; return cell;
Avec ces dernières modifications, nous nous sommes finalement débarrassés de NSDictionary et, grâce aux objets métier, nous pouvons maintenant bénéficier d’un meilleur contrôle sur les types de données des propriétés, avoir un retour immédiat sur les erreurs grâce aux contrôles de compilation et – last but not least – enfin bénéficier de l’autocomplétion de code !
Nouveau View Controller et transition
À ce stade de l’application, nous souhaitons créer un nouveau View Controller dont le rôle sera d’afficher les détails d’un film que l’on sélectionne sur la page d’accueil. Pour cela, commençons par créer les fichiers relatifs au nouveau controller : Fichier > Nouveau > Fichier puis sélectionnez « Classe Objective-C ». Nous allons appeler cette nouvelle classe « MAMovieDetailsViewController ».
Ce controller devant afficher les détails d’un film, il nous faut déclarer une nouvelle propriété qui représente le film en question :
@interface MAMovieDetailsViewController : UIViewController @property (nonatomic, strong) MAMovie *movie; @end
Il est maintenant temps de passer dans le storyboard de l’application afin de réaliser la transition entre les cellules de la home page et notre page détails. Une fois dans le storyboard, commençons par ajouter un « Navigation View Controller » avec le controller de la page d’accueil pour root view controller. Cela nous permettra de pouvoir naviguer entre différents controller :
Ensuite, faites un drag & drop d’un View Controller à côté du controller de la page d’accueil puis dans l’entity inspector, spécifiez qu’il s’agit un « MAMovieDetailsViewController » :
Afin de réaliser la transition entre les cellules et ce controller, tirez un trait entre la cellule et le controller tout en maintenant la touche CTRL enfoncée :
Une popup va s’ouvrir avec différentes options qui permettent de choisir quel type de transition effectuer. Choisissons « push ». Nous pouvons à présent voir dans le storyboard une flèche entre nos deux viewController qui représente le fait qu’une transition est possible entre ces deux derniers.
Afin d’afficher les détails d’un film, le View Controller MAMovieDetailsViewController a besoin d’une instance de MAMovie. Pour cela, il faut lui passer l’instance en paramètre lors de la transition. Nous avons donc besoin d’identifier dans le code la transition, plus communément appelé un « segue ». Toujours dans le storyboard, sélectionnez la flèche entre les deux viewController, puis renseignons un identifiant de segue dans l’ « Attributes inspector » : appelons le « MAShowMovieDetails » :
Passons maintenant dans « MATableViewController » afin de passer l’objet métier MAMovie au View Controller MAMovieDetailsViewController. Il est possible d’intéragir avec le controller cible lorsqu’une transition est déclenchée. Afin de le faire, il faut overrider la méthode prepareForSegue:sender:. Voici à quoi ressemble notre implémentation :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([[segue identifier] isEqualToString:@"MAShowMovieDetails"]) { NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; MAMovie *aMovie = [self.movies objectAtIndex:indexPath.row]; MAMovieDetailsViewController *vc = [segue destinationViewController]; vc.movie = aMovie; } }
Une fois cela implémenté, votre MAMovieDetailsViewController sera instancié et affiché tout en ayant une référence vers un MAMovie.
Affichage des détails du film
Notre objet MAMovie possède plusieurs propriétés dont les valeurs ont été renseignées lorsque nous avons récupéré la liste du box office, telles que le titre, la durée, le synopsis… Ce sont ces informations que nous allons mettre en avant sur la fiche de détails. Nous avons donc besoin de plusieurs éléments visuels que nous allons ajouter dans MAMovieDetailsViewController.h
@property (nonatomic, weak) IBOutlet UIView *headerView; @property (nonatomic, weak) IBOutlet UIImageView *posterImageView; @property (nonatomic, weak) IBOutlet UILabel *genreLabel; @property (nonatomic, weak) IBOutlet UILabel *runtimeLabel; @property (nonatomic, weak) IBOutlet UIImageView *ratingImageView; @property (nonatomic, weak) IBOutlet UILabel *ratingLabel; @property (nonatomic, weak) IBOutlet UITextView *synopsisTextView;
Afin de pouvoir lier ces éléments directement via le storyboard, nous leur ajoutons la propriété IBOutlet. Nous n’avons plus qu’à les disposer sur le storyboard, et voici le très beau design que nous avons réalisé :
Afin de lier ces éléments aux propriétés de notre controller, sélectionnons-le dans la barre de gauche (1) puis choisissons l’outlet collection dans l’inspecteur (2), avant de lier chaque propriété à un élément du board (3).
Maintenant que tout est prêt, il ne reste qu’à remplir notre vue avec les données du film. Pour cela, nous allons créer une méthode updateView dans MAMovieDetailsViewController.m
-(void) updateView { // If the view is not yet loaded, no outlets will be set so get out of here! if (!self.isViewLoaded) { return; } // Update title self.title = [NSString stringWithFormat:@"%@ (%@)", self.movie.title, self.movie.year]; // Update Poster image [self.posterImageView setImageWithURL:[NSURL URLWithString:self.movie.posters.profile]]; // Update genre self.genreLabel.text = [self.movie.genres componentsJoinedByString:@", "]; // Update runtime self.runtimeLabel.text = [NSString stringWithFormat:@"%@ min", self.movie.runtime]; // Update synopsis self.synopsisTextView.text = self.movie.synopsis; // Update rating and start animations int ratings = [self.movie.ratings[@"audience_score"] intValue]; self.ratingLabel.text = @"0 %"; self.ratingImageView.image = (ratings < 50) ? [UIImage imageNamed:@"rotten"] : [UIImage imageNamed:@"fresh"]; self.ratingImageView.alpha = .0; [UIView animateWithDuration:kRatingAnimationDuration animations:^{ self.ratingImageView.alpha = 1; }]; [NSTimer scheduledTimerWithTimeInterval:kRatingAnimationDuration/ratings target:self selector:@selector(updateRatingText:) userInfo:nil repeats:YES]; } -(void) updateRatingText:(NSTimer*)timer { int rating = [self.ratingLabel.text intValue] + 1; self.ratingLabel.text = [NSString stringWithFormat:@"%d %%",rating]; // Stop the timer if our rating has the final value if (rating >= [self.movie.ratings[@"audience_score"] intValue]) { [timer invalidate]; } }
Afin de rendre les choses un peu plus fun, nous avons ajouté deux petites animations pour afficher la note du public. Tout d’abord, nous affichons soit une tomate, soit un amas vert en fonction de la note (nous restons fidèles au design RottenTomatoes sur ce point !) en modifiant sa propriété alpha afin qu’elle apparaisse en fondu.
self.ratingImageView.alpha = .0; [UIView animateWithDuration:kRatingAnimationDuration animations:^{ self.ratingImageView.alpha = 1; }];
Puis nous voulons faire défiler les chiffres de 0 jusqu’à la valeur de la note réelle. Pour cela, nous créons un timer qui va appeler la méthode updateRatingText toutes les 1/note seconde.
[NSTimer scheduledTimerWithTimeInterval:kRatingAnimationDuration/ratings target:self selector:@selector(updateRatingText:) userInfo:nil repeats:YES];
Il ne nous reste plus qu’à appeler la méthode updateView une fois que l’objet movie a été assigné à notre controller. Mais, si vous avez été attentifs, vous avez remarqué que nous utilisons une propriété « genres » sur notre objet MAMovie, alors que cette clé n’a pas été envoyée lors de la récupération du box office. En fait, pour une raison un peu obscure, cette propriété n’est accessible que lorsque nous requêtons RottenTomatoes pour un film spécifique. Alors comment l’obtenir ? Et bien nous allons effectuer un appel supplémentaire pour récupérer les informations manquantes. Nous retournons donc dans MAMovieAppAPIClient et créons une méthode qui prend en paramètre un objet MAMovie, effectue la requête, met à jour les infos puis appelle un callback s’il a été défini.
- (void) updateMovieData:(MAMovie *)movie withCallback:(void (^)(void))callback;
- (void) updateMovieData:(MAMovie *)movie withCallback:(void (^)(void))callback { // Get the path for the current movie NSString *link = [movie.links[@"self"] stringByReplacingOccurrencesOfString:kMAMovieAppAPIBaseURLString withString:@""]; // Instantiate a dataLoader from your HTTP client and a given resourcePath: XBHttpJsonDataLoader *dataLoader = [XBHttpJsonDataLoader dataLoaderWithHttpClient:self resourcePath:[self rottenTomatoesUrlForPath:link]]; // Instantiate a dataMapper, allowing the response to be deserialized to a given class (e.g. MAMovie): XBJsonToObjectDataMapper * dataMapper = [XBJsonToObjectDataMapper mapperWithRootKeyPath:nil typeClass:[MAMovie class]]; // Create the data source from the dataLoader and the dataMapper: XBReloadableObjectDataSource *dataSource = [XBReloadableObjectDataSource dataSourceWithDataLoader:dataLoader dataMapper:dataMapper]; [dataSource loadDataWithCallback:^{ MAMovie *updatedMovie = (MAMovie*)dataSource.object; movie.genres = [NSArray arrayWithArray:updatedMovie.genres]; if (callback) { callback(); } }]; }
Et maintenant, pour conclure l’affichage correct de nos détails, nous appelons updateView une fois les nouvelles informations renseignées. Dans MAMovieDetailsViewController, définissons le setter de la propriété movie :
-(void) setMovie:(MAMovie *)movie { _movie = movie; [self.movie updateInfoWithCallback:^{ // Call updateView on main thread because it uses graphical elements, which can ONLY be used on the main thread [self performSelectorOnMainThread:@selector(updateView) withObject:nil waitUntilDone:NO]; }]; }
Vous pouvez maintenant tester votre application et voir tous les détails des films actuellement à l’affiche !
Conclusion
Maintenant que vous avez découvert CocoaPods, comment réaliser des appels réseaux avec une bibliothèque ou encore comment désérialiser proprement les données d’un flux, vous détenez de bonnes bases afin de réaliser une application robuste et évolutive. Si vous souhaitez ajouter des fonctionnalités à votre application, n’hésitez pas à parcourir cocoapods.org et cocoacontrols.com pour rechercher et découvrir des composants existants et prêts à être utilisés.
Commentaire
1 réponses pour " Article Programmez « Démarrer avec iOS » (2/2) "
Published by VANBIES , Il y a 6 ans
Bravo pour votre article, vraiment très intéressant, pouvez vous fournir le code source comme pour votre précédent article ? Cordialement. Jean-François.