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 !!!
Pour cela nous allons réaliser un petit lecteur de flux RSS :
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»
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 :
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 !
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 :
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
(
nonatomic
,
copy
)
NSString
*title;
@property
(
nonatomic
,
copy
)
NSString
*lienURL;
- (
void
)viewDidLoad {
[
super
viewDidLoad];
lblTitle.text=title;
NSString
*fixedURL = (
NSString
*)CFURLCreateStringByAddingPercentEscapes(
NULL
, (CFStringRef)lienURL,
NULL
,
NULL
, 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 :
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
;
}