Tutoriel n°13 : TableViewsControllers et NavigationControllers

Aujourd’hui nous allons nous attacher à comprendre un des points sensibles de la programmation iPhone : Les architectures mélangeant Tab Bar, Navigation Controller, et Table View. Bref que des choses appétissantes !!!  tongue

Pour cela nous allons réaliser un petit lecteur de flux RSS :

http://www.ipup.fr/forum/userimages/tuto13.jpeg

1) L’architecture :

Pour créer notre lecteur nous n’allons pas utiliser le mode «Navigation-Based Application»  ni celui dédié au Tab Bar. En effet ces pré-projets ne nous assurent pas une assez grande latitude vis-à-vis du code.
Nous nous contenterons donc d’une bonne vieille «Window-Based Application»

http://www.ipup.fr/forum/userimages/Image03.jpg

Tout d’abord, mettons nous d’accord sur les besoins de notre lecteur RSS. Un espace dédié aux tutoriels, un autre aux news et un dernier réservé au forum est un bon compromis.
Enfin une dernière partie, que nous pourrons customiser, viendra compléter l’application.

Trève de blabla et passons à l’acte.

Tout d’abord, nous allons créer les 4 controllers de notre lecteur.
Pour cela et pour que tout soit plus clair nous allons tous nommer nos fichiers de la même manière :

– 3 UITableViewControllers : «TutoViewController», «NewsViewController», et «ForumViewController»
– 1 UIViewController : «InfoViewController»

Voilà, maintenant que nous avons tout créé, passons au code. Dans l’»AppDelegate» déclarons un objet correspond à chacun des controllers ainsi qu’un UITabBarController qui nous permettra de switcher entre les vues.

Dans le header on retrouve donc :

#import <UIKit/UIKit.h>
 
@class TutoViewController;
@class NewsViewController;
@class ForumViewController;
@class InfoViewController;
 
@interface iPhone_uPAppDelegate : NSObject <UIApplicationDelegate> {
 
UIWindow *window;
UITabBarController *tabBarController;
TutoViewController *tutoViewController;
NewsViewController *newsViewController;
ForumViewController *forumViewController;
InfoViewController *infoViewController;
}
 
@end

Passons donc à la définition et à l’utilisation de ces objets. Et ça ce passe dans le .m :

#import "iPhone_uPAppDelegate.h"
#import "TutoViewController.h";
#import "NewsViewController.h";
#import "ForumViewController.h";
#import "InfoViewController.h";
 
@implementation iPhone_uPAppDelegate
 
@synthesize window;
 
- (void)applicationDidFinishLaunching:(UIApplication *)application {
 
tabBarController = [[UITabBarController alloc] init];        
 
tutoViewController = [[TutoViewController alloc] init];                 
UINavigationController *tableNavController = [[[UINavigationController alloc] initWithRootViewController:tutoViewController] autorelease];
[tutoViewController release];
[tableNavController setNavigationBarHidden:TRUE];
 
newsViewController = [[NewsViewController alloc] init];  
UINavigationController *table2NavController = [[[UINavigationController alloc] initWithRootViewController:newsViewController] autorelease];
[newsViewController release];
[table2NavController setNavigationBarHidden:TRUE];
 
forumViewController = [[ForumViewController alloc] init];  
UINavigationController *table3NavController = [[[UINavigationController alloc] initWithRootViewController:forumViewController] autorelease];
[forumViewController release];
[table3NavController setNavigationBarHidden:TRUE];
 
infoViewController = [[InfoViewController alloc] init];
UINavigationController *table4NavController = [[[UINavigationController alloc] initWithRootViewController:infoViewController] autorelease];
table4NavController.title = @"iPuP";
[infoViewController release];
[table4NavController setNavigationBarHidden:TRUE];
 
tabBarController.viewControllers = [NSArray arrayWithObjects:tableNavController, table2NavController, table3NavController, table4NavController, nil];
 
[window addSubview:tabBarController.view];      // adds the tab bar's view property to the window
[window makeKeyAndVisible];                     // makes the window visible
}

Il s’agit ici de créer des NavigationController pour les différents objets, et de les placer dans le tableau de ViewController du TabBarController.

Pour y voir un peu plus clair, la documentation apple nous fournit un schéma récapitulatif concernant les architectures TabBar :

http://www.ipup.fr/forum/userimages/Image05.jpg

Et n’oublions pas les releases !

C’en est tout pour l’architecture et nous n’avons même pas besoin d’utiliser Interface Builder !  wink

2) Les UITableViewControllers et la récupération des données :

Prenons pour exemple le fichier nommé «TutoViewController» (les autres reprendront la même logique).

Ce controller gérera une TableView, y insérera les titres des différents tutoriels et transmettra l’url de la page web à ouvrir.

Tout d’abord nous devons déclarer la TableView ainsi que les éléments nécessaires à la récupération du flux dans notre header :

#import <UIKit/UIKit.h>
 
 
@interface TutoViewController : UITableViewController {
 
IBOutlet UITableView *tutorielsTable;
CGSize cellSize;
NSXMLParser *rssParser;
NSMutableArray *stories;
 
NSMutableDictionary *item;
 
NSString *path;
 
NSString *currentElement;
NSMutableString *currentTitle, *currentDate, *currentSummary, *currentLink;
}
 
- (void)parseXMLFileAtURL:(NSString *)URL;
 
@end

Décortiquons maintenant les différentes méthodes nécessaires au bon fonctionnement de notre controller.

Tout d’abord les méthodes «classiques» : ViewDidLoad et Init :

- (id)initWithStyle:(UITableViewStyle)style {
 
self.title = @"Tutoriels"//Ici nous définissons le titre qui apparaîtra dans l'onglet du TabBar
// Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
if (self = [super initWithStyle:style]) {
}
return self;
}
 
 
- (void)viewDidLoad {
[super viewDidLoad];
 
if ([stories count]==0)
{
path = @"http://www.ipup.fr/forum/externtuto.php?action=new&;type=RSS&fid=2";
[self parseXMLFileAtURL:path];
}   
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}

Ici nous appelons la fonction parseXMLFileAtURL avec comme paramètre l’adresse correspondant aux tutoriels. Pour les news  l’adresse sera : http://www.ipup.fr/forum/externtuto.php … mp;show=20 et pour le forum nous aurons : http://www.ipup.fr/forum/rssmobile.php

Voyons donc maintenant cette méthode parseXMLFileAtURL ainsi que celle liée à la récupération des données :

- (void)parseXMLFileAtURL:(NSString *)URL {
stories = [[NSMutableArray alloc] init];
 
NSURL *xmlURL = [NSURL URLWithString:URL];
 
rssParser=[[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
 
[rssParser setDelegate:self];
 
[rssParser setShouldProcessNamespaces:NO];
[rssParser setShouldReportNamespacePrefixes:NO];
[rssParser setShouldResolveExternalEntities:NO];
 
[rssParser parse];
}
 
- (void)parserDidStartDocument:(NSXMLParser *)parser {
NSLog(@"found file and started parsing");
}
 
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
NSString * errorString = [NSString stringWithFormat:@"Unable to download story feed from web site (Error code %i )", [parseError code]];
NSLog(@"error parsing XML: %@", errorString);
 
UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:@"Error loading content"message:errorString delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[errorAlert show];
}
 
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
NSLog(@"found this element: %@", elementName);
currentElement = [elementName copy];
 
if ([elementName isEqualToString:@"item"]) {
// clear out our story item caches...
item = [[NSMutableDictionary alloc] init];
currentTitle = [[NSMutableString alloc] init];
currentDate = [[NSMutableString alloc] init];
currentSummary = [[NSMutableString alloc] init];
currentLink = [[NSMutableString alloc] init];
}
}
 
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString *)qName{
 
NSLog(@"ended element: %@", elementName);
if ([elementName isEqualToString:@"item"]) {
// save values to an item, then store that item into the array...
  • ;
  • ;
  • ;
  • ;
     
    [stories addObject:
  • ];
    NSLog(@"adding story: %@", currentTitle);
    }
    }
     
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
    NSLog(@"found characters: %@", string);
    // save the characters for the current item...
    if ([currentElement isEqualToString:@"title"]) {
    [currentTitle appendString:string];
    else if ([currentElement isEqualToString:@"link"]) {
    [currentLink appendString:string];
    else if ([currentElement isEqualToString:@"description"]) {
    [currentSummary appendString:string];
    else if ([currentElement isEqualToString:@"pubDate"]) {
    [currentDate appendString:string];
    }
    }
     
    - (void)parserDidEndDocument:(NSXMLParser *)parser {
     
    NSLog(@"all done!");
    NSLog(@"stories array has %d items", [stories count]);
    [forumTable reloadData];
    }

    Nous récupérons donc le titre, le lien la description et la date de publication de chacun des éléments du flux RSS afin de les placer dans le tableau stories.

    Finissons donc par les méthodes propres à la TableView :

    #pragma mark Table view methods
     
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
    }
     
     
    // Customize the number of rows in the table view.
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [stories count];
    }
     
     
    // Customize the appearance of table view cells.
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    }
     
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    }

    Les deux dernières méthodes nécessitent un peu plus de temps et toute notre attention. En effet, la première  nous permettra de personnaliser nos cellules et nécessitent donc la création d’un TableViewCell et d’un .xib.
    La seconde quant à elle nous permettra d’appeler une sous-vue lors du clic sur une cellule. Nous allons donc ensuite créer cette sous-vue.

    NB : nous vous présentons ici un moyen plus poussé pour la création de la cellule. Pour une cellule « normale », celle d’origine suffit amplement avec notamment cell.image et cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; pour afficher le chevron. L’avantage avec la création d’une cellule perso est de pouvoir y mettre ce que l’on souhaite !

    Commençons par la création d’un TableViewCell comprenant un label , que nous nommerons originalement «TableViewCell», ainsi que du .xib correspondant :

    http://www.ipup.fr/forum/userimages/Image06.jpg
    http://www.ipup.fr/forum/userimages/Image07.jpg

    Maintenant que notre cellule est créée il faut lui transmettre les données c’est à dire le titre du tutoriel :

    Dans «TutoViewController.m» :

    // Customize the appearance of table view cells.
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     
    static NSString *CellIdentifier = @"CellIndent";
     
    TableViewCell *cell = (TableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
    UIViewController *c = [[UIViewController alloc] initWithNibName:@"TableViewCell" bundle:nil];
    cell = (TableViewCell *)c.view;
    [c release];
    }
     
    // Set up the cell...
    int storyIndex = [indexPath indexAtPosition:[indexPath length]-1];
    cell.label1.text = [[stories objectAtIndex:storyIndex] objectForKey:@"title"];
    return cell;
    }

    Enfin, (ouf on y arrive !) Créons notre « FirstDetailViewController » (UIViewController) qui contiendra la WebView permettant d’afficher la bonne page web correspondant à une URL donnée. En voici les éléments principaux :

    @interface FirstDetailViewController : UIViewController {
     
    IBOutlet UIWebView *webView;
    IBOutlet UILabel *lblTitle;
    IBOutlet UILabel *lblLink;
    IBOutlet UIActivityIndicatorView *chargement;
     
    NSString *title;
    NSString *lienURL;
    }
     
    @property (nonatomiccopyNSString *title;
    @property (nonatomiccopyNSString *lienURL;

    - (void)viewDidLoad {
    [super viewDidLoad];
    lblTitle.text=title;
     
    NSString *fixedURL = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)lienURL, NULLNULL, kCFStringEncodingUTF8);
     
    NSURL *URL = [NSURL URLWithString:fixedURL];
     
    //Création d'un objet de type NSURLRequest
    NSURLRequest *requestObj = [NSURLRequest requestWithURL:URL];   
     
    [webView loadRequest:requestObj];
    NSLog(@"%@", lienURL);
    NSLog(@"%@", URL);
     
    [fixedURL release];
    }

    Bien sûr, il nous faut créer le .xib correspondant :

    http://www.ipup.fr/forum/userimages/Image09.jpg

    http://www.ipup.fr/forum/userimages/Image08.jpg

    Appelons maintenant cette sous-vue lorsque une cellule est activée :

    Dans «TutoViewController.m» :

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     
    int storyIndex = [indexPath indexAtPosition:[indexPath length] - 1];
    NSString *title=[[stories objectAtIndex:storyIndex] objectForKey:@"title"];
    NSString *lien=[[stories objectAtIndex:storyIndex] objectForKey:@"link"];
    FirstDetailViewController *dvController = [[FirstDetailViewController alloc] initWithNibName:@"FirstDetailView" bundle:[NSBundle mainBundle]];
    dvController.lienURL=lien;
    dvController.title=title;
    [self.navigationController pushViewController:dvController animated:YES];
    [dvController release];
    dvController = nil;
    }