Définir une sous-classe personnalisée de UINavigationBar dans UINavigationController par programme

Est-ce que quelqu’un sait comment utiliser ma sous-classe personnalisée de UINavigationBar si UINavigationBar UINavigationController programmation (sans IB)?

Faites glisser un UINavigationController dans IB et affichez-le sous la barre de navigation et en utilisant Identity Inspectory Je peux changer le type de classe et définir ma propre sous-classe de UINavigationBar mais par programmation je ne peux pas

Que dois-je faire pour personnaliser la barre de navigation par programmation? IB est-il plus “puissant” que “code”? Je pensais que tout ce qui pouvait être fait dans le cadre de l’IB pouvait être fait également par programmation.

Vous n’avez pas besoin de passer avec le XIB simplement utiliser KVC.

 [self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"]; 

Depuis iOS5, Apple propose une méthode pour le faire directement. Référence

 UINavigationController *navigationController= [[UINavigationController alloc]initWithNavigationBarClass:[CustomNavBar class] toolbarClass:nil]; [navigationController setViewControllers:[NSArray arrayWithObject:yourRootViewController]]; 

Depuis iOS 4, vous pouvez utiliser la classe UINib pour résoudre ce problème.

  1. Créez votre sous-classe UINavigationBar personnalisée.
  2. Créez un fichier xib vide, ajoutez un UINavigationController comme object unique.
  3. Définissez la classe pour UINavigationController de UINavigationBar sur votre sous-classe personnalisée.
  4. Définissez votre contrôleur d’affichage racine via l’une des méthodes suivantes:
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
    • [navController pushViewController:myRootVC];

Dans du code:

 UINib *nib = [UINib nibWithNibName:@"YourCustomXib" bundle:nil]; UINavigationController *navController = [[nib instantiateWithOwner:nil options:nil] objectAtIndex:0]; 

Maintenant, vous avez un UINavigationController avec votre UINavigationBar personnalisé.

Autant que je sache, il est parfois nécessaire de sous-classer UINavigationBar pour effectuer un restyling non standard. Il est parfois possible d’éviter de le faire en utilisant des catégories , mais pas toujours.

Actuellement, pour autant que je sache, la seule manière de définir un UINavigationBar personnalisé dans un UIViewController est via IB (c’est-à-dire via une archive) – cela ne devrait probablement pas être le cas, mais pour l’instant, nous devons le vivre.

C’est souvent bien, mais parfois, utiliser IB n’est pas vraiment réalisable.

Donc, j’ai vu trois options:

  1. Sous-classe UINavigationBar et twigr le tout dans IB, puis de charger le nib chaque fois que je voulais un UINavigationController,
  2. Utiliser le remplacement de méthode dans une catégorie pour modifier le comportement de UINavigationBar, plutôt que de sous-classer, ou
  3. Sous-classe UINavigationBar et faire un peu de recherche sur l’archivage / désarchivage de UINavigationController.

L’option 1 était irréalisable (ou du moins trop agaçante) pour moi dans ce cas, car j’avais besoin de créer l’UINavigationController par programmation, 2 est selon moi une option un peu dangereuse et de dernier recours, j’ai donc choisi l’option 3.

Mon approche consistait à créer une archive ‘modèle’ d’un UINavigationController et à la désarchiver, en la renvoyant dans initWithRootViewController .

Voici comment:

Dans IB, j’ai créé un UINavigationController avec le jeu de classes approprié pour UINavigationBar.

Ensuite, j’ai pris le contrôleur existant et en ai enregistré une copie archivée en utilisant +[NSKeyedArchiver archiveRootObject:toFile:] . Je viens de faire cela dans le délégué de l’application, dans le simulateur.

J’ai ensuite utilisé l’utilitaire ‘xxd’ avec l’indicateur -i pour générer du code c à partir du fichier enregistré afin d’incorporer la version archivée dans ma sous-classe ( xxd -i path/to/file ).

Dans initWithRootViewController je désarchive ce modèle et je me règle sur le résultat de la désarchivage:

 // This is the data from [NSKeyedArchiver archivedDataWithRootObject:controller], where // controller is a CTNavigationController with navigation bar class set to CTNavigationBar, // from IB. This c code was created using 'xxd -i' static unsigned char archived_controller[] = { 0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd4, 0x01, 0x02, 0x03, ... }; static unsigned int archived_controller_len = 682; ... - (id)initWithRootViewController:(UIViewController *)rootViewController { // Replace with unarchived view controller, necessary for the custom navigation bar [self release]; self = (CTNavigationController*)[NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithBytes:archived_controller length:archived_controller_len]]; [self setViewControllers:[NSArray arrayWithObject:rootViewController]]; return [self retain]; } 

Ensuite, je peux simplement saisir une nouvelle instance de ma sous-classe UIViewController qui contient le jeu de barres de navigation personnalisé:

 UIViewController *modalViewController = [[[CTNavigationController alloc] initWithRootViewController:myTableViewController] autorelease]; [self.navigationController presentModalViewController:modalViewController animated:YES]; 

Cela me donne un UITableViewController modal avec une barre de navigation et une barre d’outils configurées, et avec la classe de barre de navigation personnalisée en place. Je n’ai pas eu besoin de faire un remplacement de méthode légèrement désagréable, et je n’ai pas à me soucier des nibs quand je veux vraiment travailler par programmation.

Je voudrais voir l’équivalent de +layerClass dans UINavigationController – +navigationBarClass – mais pour l’instant, cela fonctionne.

J’utilise “option 1”

Créez un fichier nib avec uniquement le UINavigationController. Et définissez la classe UINavigationBar sur ma classe personnalisée.

 self.navigationController = [[[NSBundle mainBundle] loadNibNamed:@"navigationbar" owner:self options:nil] lastObject]; [navigationController pushViewController:rootViewController animated:YES]; 

La solution de Michael fonctionne, mais vous pouvez éviter l’utilitaire NSKeyedArchiver et ‘xxd’. Il vous suffit de sous-classer UINavigationController et de remplacer initWithRootViewController , en chargeant directement votre NIB NavigationController personnalisée:

 - (id) initWithRootViewController:(UIViewController *)rootViewController { [self release]; self = [[[[NSBundle mainBundle] loadNibNamed:@"CTNavigationController" owner:nil options:nil] objectAtIndex:0] retain]; [self setViewControllers:[NSArray arrayWithObject:rootViewController]]; return self; } 

Update: Utiliser object_SetClass() ne fonctionne plus comme si iOS5 GM. Une autre solution a été ajoutée ci-dessous.

Utilisez NSKeyedUnarchiver pour définir manuellement la classe désarchivante pour la barre de navigation.

  MyViewController *controller = [[[MyViewController alloc] init] autorelease]; NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:[NSKeyedArchiver archivedDataWithRootObject:controller]] autorelease]; [unarchiver setClass:[MyNavigationBar class] forClassName:@"UINavigationBar"]; controller = [unarchiver decodeObjectForKey:@"root"]; 


Remarque: cette solution originale ne fonctionne qu’avant iOS5:

Il y a une excellente solution, que j’ai publiée ici – injecter la sous-classe de navBar directement dans votre vue, le UINavigationController :

 #import  - (void)viewDidLoad { [super viewDidLoad]; object_setClass(self.navigationController.navigationBar, [MyNavBar class]); // the rest of your viewDidLoad code } 

Un scénario que j’ai trouvé que nous avons besoin d’utiliser la sous-classe plutôt que la catégorie est de définir la barre de navigation backgroundcolor avec l’image de modèle, car dans iOS5, écraser drawRect using category ne fonctionne plus. Si vous souhaitez prendre en charge ios3.1-5.0, la seule façon de procéder est de sous-classer la barre de navigation.

Ces méthodes de catégories sont dangereuses et non pour les novices. De plus, la complication avec iOS4 et iOS5 étant différente, cela en fait un domaine qui peut causer des erreurs à de nombreuses personnes. Voici une sous-classe simple que j’utilise et qui supporte iOS4.0 ~ iOS6.0 et est très simple.

.h

 @interface XXXNavigatioNBar : UINavigationBar @end 

.m

 #import "XXXNavigationBar.h" #import  @implementation XXXNavigationBar - (void) didMoveToSuperview { if( [self respondsToSelector: @selector(setBackgroundImage:forBarMesortingcs:)]) { //iOS5.0 and above has a system defined method -> use it [self setBackgroundImage: [UIImage imageNamed: @"nav-bar"] forBarMesortingcs: UIBarMesortingcsDefault]; } else { //iOS4.0 requires us to override drawRect:. BUT!! //If you override drawRect: on iOS5.0 the system default will break, //so we dynamically add this method if required IMP implementation = class_getMethodImplementation([self class], @selector(iOS4drawRect:)); class_addMethod([self class], @selector(drawRect:), implementation, "v@:{name=CGRect}"); } } - (void)iOS4drawRect: (CGRect) rect { UIImage* bg = [UIImage imageNamed:@"nav-bar-blue"]; [bg drawInRect: rect]; } @end 

Il n’est pas recommandé de sous- UINavigationBar classe UINavigationBar . La méthode préférée pour personnaliser la barre de navigation consiste à définir ses propriétés pour la faire apparaître comme vous le souhaitez et à utiliser des vues personnalisées dans UIBarButtonItems avec un délégué pour obtenir le comportement souhaité.

Qu’est-ce que vous essayez de faire qui a besoin de sous-classement?

De plus, je ne pense pas qu’IB remplace réellement la barre de navigation. Je suis à peu près sûr que cela n’affiche pas celui par défaut et que votre barre de navigation personnalisée est une sous-vue. Si vous appelez UINavigationController.navigationBar, obtenez-vous une instance de votre barre?

Si vous voulez sous-classe navBar juste pour changer l’image d’arrière-plan – il n’y a pas besoin dans iOS 5. Il y aura une méthode comme celle-ci setBackgroundImage

Suite au commentaire de obb64, j’ai fini par utiliser son tour avec setViewControllers:animated: pour définir le contrôleur comme rootController pour le navigationController chargé depuis le nib. Voici le code que j’utilise:

 - (void) presentModalViewControllerForClass: (Class) a_class { UINavigationController *navController = [[[NSBundle mainBundle] loadNibNamed: @"CustomNavBar" owner:self options:nil] lastObject]; LoginSignupBaseViewController *controller = [[a_class alloc] initWithNibName: nil bundle: nil]; controller.navigationController = navController; [navController setViewControllers: A(controller) animated: NO]; [self presentModalViewController: navController animated: YES]; [controller release]; }