web-dev-qa-db-fra.com

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

Quelqu'un sait-il comment utiliser ma sous-classe personnalisée de UINavigationBar si j'instancie UINavigationController par programme (sans IB)?

Faites glisser un UINavigationController dans IB montrez-moi un 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 programme je ne peux pas, navigationBar la propriété du contrôleur de navigation est en lecture seule ...

Que dois-je faire pour personnaliser la barre de navigation par programme? IB est-il plus "puissant" que "code"? Je pensais que tout ce qui pouvait être fait dans IB pouvait l'être aussi par programme.

92
Duccio

Vous n'avez pas besoin de foutre avec le XIB, utilisez simplement KVC.

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

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

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

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 xib vide, ajoutez un UINavigationController comme objet unique.
  3. Définissez la classe des UINavigationControllerUINavigationBar de votre sous-classe personnalisée.
  4. Configurez votre contrôleur de vue 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];


Vous avez maintenant un UINavigationController avec votre UINavigationBar personnalisé.

37
gorbster

Pour autant que je sache, il est parfois nécessaire de sous-classer UINavigationBar, pour effectuer un restylage non standard. Il est parfois possible d'éviter d'avoir à le faire en utilisant categories , mais pas toujours.

Actuellement, pour autant que je sache, la seule façon de définir un UINavigationBar personnalisé dans un UIViewController est via IB (c'est-à-dire via une archive) - il ne devrait probablement pas être comme ça, mais pour l'instant, nous devons vivre avec.

C'est souvent bien, mais parfois utiliser IB n'est pas vraiment faisable.

J'ai donc vu trois options:

  1. Sous-classe UINavigationBar et connectez le tout dans IB, puis détruisez le chargement de la plume chaque fois que je voulais un UINavigationController,
  2. Utilisez 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 faites un peu de nettoyage avec l'archivage/désarchivage du UINavigationController.

L'option 1 était irréalisable (ou au moins trop ennuyeuse) pour moi dans ce cas, car j'avais besoin de créer le programme UINavigationController par programme, 2 est un peu dangereux et plus une option de dernier recours à mon avis, 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 l'ensemble de classes approprié pour l'UINavigationBar.

Ensuite, j'ai pris le contrôleur existant et j'en ai sauvegardé une copie archivée à l'aide de +[NSKeyedArchiver archiveRootObject:toFile:]. Je viens de le faire au sein du 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é, pour incorporer la version archivée dans ma sous-classe (xxd -i path/to/file).

Dans initWithRootViewController je désarchive ce modèle et me fixe 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 a la barre de navigation personnalisée définie:

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 toutes configurées, et avec la classe de barre de navigation personnalisée en place. Je n'avais pas besoin de faire de remplacement de méthode légèrement méchant, et je n'ai pas à me moquer des nibs quand je veux vraiment travailler par programmation.

J'aimerais voir l'équivalent de +layerClass dans UINavigationController - +navigationBarClass - mais pour l'instant, cela fonctionne.

26
Michael Tyson

La solution de Michael fonctionne, mais vous pouvez éviter NSKeyedArchiver et l'utilitaire "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;
}
5
100grams

J'utilise "l'option 1"

Créez un fichier nib contenant uniquement l'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];
5
obb64

Mise à jour: L'utilisation de object_SetClass() ne fonctionne plus comme si iOS5 GM. Une solution alternative a été ajoutée ci-dessous.

Utilisez NSKeyedUnarchiver pour définir manuellement la classe de désarchivage de 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 fonctionne uniquement avant iOS5:

Il existe une excellente solution, que j'ai publiée ici - injectez la sous-classe navBar directement dans votre vue UINavigationController:

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

    object_setClass(self.navigationController.navigationBar, [MyNavBar class]);
    // the rest of your viewDidLoad code
}
4
memmons

Ces méthodes de catégorie sont dangereuses et ne conviennent pas aux novices. De plus, la complication avec iOS4 et iOS5 étant différente, en fait un domaine qui peut provoquer des bugs pour de nombreuses personnes. Voici une sous-classe simple que j'utilise qui prend en charge iOS4.0 ~ iOS6.0 et est très simple.

. h

@interface XXXNavigatioNBar : UINavigationBar
@end

. m

#import "XXXNavigationBar.h"

#import <objc/runtime.h>

@implementation XXXNavigationBar

- (void) didMoveToSuperview {
    if( [self respondsToSelector: @selector(setBackgroundImage:forBarMetrics:)]) {
        //iOS5.0 and above has a system defined method -> use it
        [self setBackgroundImage: [UIImage imageNamed: @"nav-bar"]
                   forBarMetrics: UIBarMetricsDefault];
    }
    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
1
Paul de Lange

Un scénario que j'ai trouvé que nous devons utiliser la sous-classe plutôt que la catégorie consiste à définir la couleur de fond de la barre de navigation avec l'image du motif, car dans iOS5 l'écrasement de drawRect en utilisant la catégorie ne fonctionne plus. Si vous souhaitez prendre en charge ios3.1-5.0, la seule façon de le faire est de sous-classer la barre de navigation.

1
james

C'est non recommandé pour sous-classer la classe UINavigationBar. La meilleure façon de personnaliser la barre de navigation est de définir ses propriétés pour qu'elle apparaisse comme vous le souhaitez et d'utiliser des vues personnalisées dans UIBarButtonItems avec un délégué pour obtenir le comportement souhaité.

Qu'essayez-vous de faire qui nécessite un sous-classement?

De plus, je ne pense pas qu'IB remplace réellement la barre de navigation. Je suis presque sûr qu'il n'affiche simplement pas celui par défaut et a votre barre de navigation personnalisée comme sous-vue. Si vous appelez UINavigationController.navigationBar, obtenez-vous une instance de votre barre?

0
Ben S

Si vous voulez sous-classer navBar juste pour changer l'image d'arrière-plan - ce n'est pas nécessaire dans iOS 5. Il y aura une méthode comme celle-ci setBackgroundImage

0
off

Suite au commentaire de obb64, j'ai fini par utiliser son astuce avec setViewControllers:animated: pour définir le contrôleur comme rootController pour le navigationController chargé à partir de la plume. 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];
}
0
smtlaissezfaire