Blog d'un développeur multi-support

[DIM] pour les intimes :)

Iphone & UITableView & UISearchBar

Bonjour, je recommence à développer sur après quelques mois d’arrêt, j’en profite aussi pour tester l’Ipad (sur simulateur :p).

Cette semaine j’ai cherché à faire fonctionner une UISearchBar avec le controller qui va bien et j’ai eu quelque souci. Si techniquement c’est assez simple à mettre en place, graphiquement j’ai eu quelque souci.

Mon architecture :

Mon application a une UITabBar, puis sur une des sous vue j’ai une liste de résultat avec la SearchBar. Cet écran n’est pas  directement une UITableView, c’est un navigation controller qui contient une UITableView.

Mon archi

Ma UITableView est crée directement en code et est à hauteur fixe, et mes cellules ont une hauteur de 60:

1
2
3
4
5
6
7
8
9
- (void)viewDidLoad {  
	CGRect frame = CGRectMake(0, 75, 320, 262);
	// Initialise une table view.
	myTableView = [[UITableView alloc] initWithFrame:frame];
	myTableView.rowHeight = 60;
 
	// Ajout la tableView à l'écran et autres par la suite
 
}

Mes soucis

?Les soucis viennent quand j’utilise la barre de recherche:

  • Les cellules sont bien réutilisées mais la hauteur est celle par défaut du coup tout mes items sont mal placés. Joli bug graphique.
  • Mes résultats sont bien filtrés, mais il est impossible de scroller. La liste revient toujours en haut. Comme si ma liste avait une hauteur tellement grande que le scroll était inefficace.

Solutions

Bon, j’en suis pas vraiment fier, ça tient plus de hooks qu’autres choses mais ça a le mérite de marcher et ça n’a pas l’air trop lourd en terme de performance sur mon 3GS.

En fait c’est simple, à chaque fois il faut refixer les hauteurs au moment oppertun.

  • Pour les cellules, c’est à leur initialisation :
    1
    2
    3
    4
    
    - (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath
    {
    	return 60;
    }
  • Pour la liste, c’est quand on lance une recherche :

    1
    2
    3
    4
    5
    6
    7
    
    - (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
    {
    	CGRect frame = CGRectMake(0, 75, 320, 262);
    	[[[self searchDisplayController] searchResultsTableView] setFrame:frame];
     
    	// faire sa recherche ensuite
    }

Donc en terme de performance y doit y avoir moyen de fixer ses tailles une fois pour toute mais je n’ai pas trouvé comment. Si quelqu’un a la solution je suis preneur.

Tags : , , , ,

iPhone & Delegate

Bonjour,

Voici juste un petit « How to » pour comprendre et jour avec les sur cocoa.

Admettons vous avez une classe « ListingController » qui hérite d’une UIViewController qui gère une liste d’élément et que vous avez une classe « DetailController » qui hérite aussi d’une UIViewController mais qui donne le detail de l’élément cliqué.

Vous voulez que lorsqu’on click sur un bouton posé sur la vue Détail revenir à la liste principal, puis la recharger. Revenir à la vue principal est assez simple il suffit de crée une méthode « IBAction » dans le controller Detail, liée le bouton à cette méthode via Interface builder puis écrire  :

1
2
3
-(IBAction)goHome {
[self.view removeFromSuperview];
}

Et le tour est joué mais bon, la liste n’est pas rechargé pour autant et il n’y a pas eu la levé d’un évènement pour la classe « Liste ». C’est là que les delegate peuvent nous être utile.

Le principe est de définir des méthodes sur la classe de Detail puis les appeller/utiliser lors du click mais c’est la classe Liste qui les implémentera effectivement.

Voici la marche à suivre :

  1. Dans la classe Detail : Définir une ou plusieurs méthodes dite « delegate » qui devront être implémenter. On appelle ce genre de déclaration un « protocole ».
  2. Dans la classe Detail : Définir une propriété de classe de type « id » qui utilise le protocole. Cette propriété représente un lien vers la classe « parente » qui implémentera effectivement notre protocole.
  3. Dans la classe Detail : Appeller lors du click les méthodes « delegate » sur notre protocole.
  4. Dans la classe Liste : Implémenter les méthodes delegate dans notre classe « parente ».

Etape 1 :

Dans le fichier « DetailController.h », juste après les #import et avant @interface DetailController : UIViewController { } :

1
2
3
4
@protocol DetailControllerDelegate
@required
-(void)RetourListe;
@end

Etape 2 :

Toujours dans le fichier « DetailController.h », après « @interface DetailController : UIViewController { » nous définissons une propriété que nous appellerons « delegate » :

1
id delegate;

puis après l’accolade définissant l’interface :

1
2
3
@property (nonatomic,assign) id < DetailControllerDelegate > delegate;
 
@end

Notre propriété « delegate » est de type « id » mais « implémente » le protocole précédement utilisé.

Etape 3 :

Nous avons définit une propriété dans le fichier « .h », il faut maintenant s’en servir dans notre fichier « DetailController.m » :

1
2
3
4
5
6
7
8
9
#import "DetailController.h"
@implementation DetailController
@synthesize delegate; //synthetisé une propriété signifie que le sdk créera les getters/setters pour nous
-(IBAction)goHome
{
	NSLog(@"Go home Liste");
	[self.delegate RetourListe]; // dispatch l'évènement à la classe parente.
	[self.view removeFromSuperview]; // supprime la vue courante
}

C’est fini pour notre classe Detail  ! Vous remarquerez qu’à aucun moment il n’y a de lien vers la classe « Liste ». Le couplage est donc très faible entre ces classes. Ce principe de couplage faible est assez puissant si on y réfléchi à deux fois vu que nos classes sont, de fait, totalement indépendantes vis à vis de leur « parent » (ou du contexte d’execution pour être plus bien parlant). Bon bref ça c’était le coup de pub raté pour la puissance du modèle objet.

Etape 4

Implémentons les méthodes ! Dans le fichier « ListingController.h » nous déclarons notre interface comme ceci :

1
2
3
@interface ListingController : UIViewController < DetailControllerDelegate > {
// some code
}

Et dans notre fichier « ListingController.m » il suffit d’implémenter la méthode « RetourListe ». Surtout, ne pas la déclarer dans l’interface (le fichier .h) sinon le programme se mélangera les pinceaux ! (Oui, vous utilisez deux méthodes avec le même nom ayant des portés différentes ..)

1
2
3
4
5
-(void)RetourListe
{
    NSLog("coucou");
    // some code
}

Edit : Oups, erreur. On crée la méthode mais à un aucun moment on dit que notre classe Listing va implémenter les méthodes de detail hormis dans la déclaration (fichier.h). Il faut donc qu’à notre objet « DetailController », juste après qu’il soit instanciée, au moment du click par exemple faire un :

1
2
DetailController*myView = [[DetailControlleralloc] initWithNibName:@"DetailView" bundle:[NSBundle mainBundle]];
myView.delegate = self;

On définit que c’est la classe Listing (self) va implémenter les méthodes delegate de détail.

Et voilà, normallement ça doit marcher.

Ce « How to » a été écrit à partir d’un projet personnel, je ne pourrais donc pas donnée des sources qui utilisent ce concept. A l’heure où j’écrit ce billet je n’ai pas de Mac à ma disposition.

Tags : , , , ,