Impostare una sottoclasse personalizzata di UINavigationBar in UINavigationController a livello di codice


92

Qualcuno sa come posso utilizzare la mia sottoclasse personalizzata di UINavigationBarse installo in modo UINavigationControllerprogrammatico (senza IB)?

Trascina un UINavigationControllerin IB mostrami un sotto la barra di navigazione e usando Identity Inspectory posso cambiare il tipo di classe e impostare la mia sottoclasse di UINavigationBarma a livello di codice non posso, la navigationBarproprietà del controller di navigazione è di sola lettura ...

Cosa devo fare per personalizzare la barra di navigazione a livello di codice? IB è più "potente" del "codice"? Credevo che tutto ciò che si può fare in IB potesse essere fatto anche a livello di programmazione.


hai avuto fortuna a trovare una soluzione altrove?
prendio2

hai una risposta al riguardo?
Hrushikesh Betai

Risposte:


89

Non hai bisogno di muck con XIB, usa semplicemente KVC.

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

Questa soluzione funziona a meraviglia (almeno su iOS 5.1). Tutte le altre soluzioni sembrano molto più laboriose. Sto ancora cercando il rovescio della medaglia.
Daniel

6
come hai trovato questo percorso chiave @ "navigationBar" per il controller di navigazione. puoi condividere questo
Iqbal Khan

Sei sicuro che ciò non comporterà il rifiuto dell'app? In realtà non l'ho visto documentato da nessuna parte.
Bani Uppal

Grazie! Funziona alla grande anche con iOS 6 beta 3! @BaniUppal KVC è sicuramente documentato, lo usiamo solo con una chiave un po 'difficile da trovare. Penso che questo sia il punto principale.
Johannes Lund

9
Sembra un AF hacky
mattsven

66

A partire da iOS5, Apple fornisce un metodo per farlo direttamente. Riferimento

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

@nonamelive In realtà no, aggiunto in iOS6: developer.apple.com/library/ios/#releasenotes/General/…
Pascalius

7
Sì, è aggiunto in iOS 6, ma è anche supportato in ingegnere iOS 5. Una Mela menzionato che nel WWDC 2012 Sessione 216.
nonamelive

37

A partire da iOS 4, puoi utilizzare la UINibclasse per risolvere questo problema.

  1. Crea la tua UINavigationBarsottoclasse personalizzata .
  2. Crea un xib vuoto, aggiungi a UINavigationControllercome singolo oggetto.
  3. Impostare la classe per le UINavigationController's UINavigationBarper il tuo sottoclasse personalizzata.
  4. Imposta il tuo controller di visualizzazione root tramite uno di questi metodi:
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
    • [navController pushViewController:myRootVC];

In codice:

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


Ora hai una UINavigationControllercon la tua abitudine UINavigationBar.


Tranne allora come si imposta il rootViewController?
memmons

usi [navcontroller setViewControllers: [NSArray arrayWithObject: <YOUR_ROOT_CONTROLLER>]]
coneybeare

scusa, usi tu usi [navcontroller pushViewController: <YOUR_ROOT_CONTROLLER> animated: NO]
coneybeare

3
IMHO questo è il modo meno "hacker" qui di fare questo hack a volte inevitabile.
David Pisoni

2
In realtà, uno strano problema. Quando lo faccio, il navigationItem del nuovo navigationController non viene impostato sulla proprietà navigationItem assegnata al viewController che sto inserendo nello stack (vuoto).
David Pisoni

26

Per quanto ne so, a volte è davvero necessario sottoclassare UINavigationBar, per fare un restyling non standard. A volte è possibile evitare di doverlo fare utilizzando le categorie , ma non sempre.

Attualmente, per quanto ne so, l' unico modo per impostare una UINavigationBar personalizzata all'interno di un UIViewController è tramite IB (cioè tramite un archivio) - probabilmente non dovrebbe essere così, ma per ora, dobbiamo conviverci.

Questo spesso va bene, ma a volte l'uso di IB non è realmente fattibile.

Quindi, ho visto tre opzioni:

  1. Sottoclasse UINavigationBar e aggancia il tutto in IB, quindi muck circa il caricamento del pennino ogni volta che volevo un UINavigationController,
  2. Utilizzare la sostituzione del metodo all'interno di una categoria per modificare il comportamento di UINavigationBar, anziché creare sottoclassi o
  3. Sottoclasse UINavigationBar e fai un po 'di confusione con l'archiviazione / annullamento dell'archiviazione di UINavigationController.

L'opzione 1 era irrealizzabile (o almeno troppo fastidiosa) per me in questo caso, poiché avevo bisogno di creare l'UINavigationController a livello di programmazione, 2 è un po 'pericolosa e più di un'opzione di ultima istanza secondo me, quindi ho scelto l'opzione 3.

Il mio approccio consisteva nel creare un archivio "modello" di un UINavigationController e disarchiviarlo, restituendolo in initWithRootViewController.

Ecco come:

In IB, ho creato un UINavigationController con la classe appropriata impostata per UINavigationBar.

Quindi, ho preso il controller esistente e ne ho salvato una copia archiviata utilizzando +[NSKeyedArchiver archiveRootObject:toFile:]. L'ho appena fatto all'interno del delegato dell'app, nel simulatore.

Ho quindi utilizzato l'utility 'xxd' con il flag -i, per generare il codice c dal file salvato, per incorporare la versione archiviata nella mia sottoclasse ( xxd -i path/to/file).

All'interno initWithRootViewControllerdisarchivo quel modello e mi imposto al risultato dell'annullamento dell'archiviazione:

// 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];
}

Quindi, posso semplicemente prendere una nuova istanza della mia sottoclasse UIViewController che ha la barra di navigazione personalizzata impostata:

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

Questo mi dà un UITableViewController modale con una barra di navigazione e una barra degli strumenti tutte impostate e con la classe della barra di navigazione personalizzata in posizione. Non ho avuto bisogno di sostituire un metodo leggermente sgradevole, e non devo perdere tempo con i pennini quando voglio solo lavorare in modo programmatico.

Mi piacerebbe vedere l'equivalente +layerClassall'interno di UINavigationController - +navigationBarClass- ma per ora funziona.


5

Uso "opzione 1"

Crea un file pennino contenente solo UINavigationController. E imposta la classe UINavigationBar sulla mia classe personalizzata.

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

[navigationController pushViewController:rootViewController animated:YES];

quindi il nome appropriato del file pennino sarebbe loadNibNamed: @ "navigationController? In realtà stai ottenendo l'intero navigationController dal pennino e non solo dalla barra. Giusto?
aneuryzm

questo funziona per me, ma quando faccio clic su qualsiasi opzione nella mia visualizzazione tabella perdo il pulsante Indietro.
jfisk

5

La soluzione di Michael funziona, ma puoi evitare NSKeyedArchiver e l'utilità "xxd". Sottoclasse semplicemente UINavigationController e sovrascrivi initWithRootViewController, caricando direttamente il tuo NIB NavigationController personalizzato:

- (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;
}

4

Aggiornamento: l' utilizzo object_SetClass()non funziona più come se iOS5 GM. Di seguito è stata aggiunta una soluzione alternativa.

Utilizza NSKeyedUnarchiver per impostare manualmente la classe di annullamento dell'archiviazione per la barra di navigazione.

   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"];




Nota: questa soluzione originale funziona solo prima di iOS5:

C'è un'ottima soluzione, che ho postato qui : inserisci la sottoclasse navBar direttamente nella tua vista UINavigationController:

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

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

Come ho indicato nella risposta precedente, hai pubblicato questo: il gold master di iOS5 ha rotto questo. Tuttavia, sono disponibili altre opzioni, quindi modifico con un metodo alternativo.
memmons

1

Uno scenario che ho scoperto che dobbiamo usare la sottoclasse piuttosto che la categoria è impostare il colore di sfondo della barra di navigazione con l'immagine del motivo, perché in iOS5 la sovrascrittura di drawRect usando la categoria non funziona più. Se vuoi supportare ios3.1-5.0, l'unico modo per farlo è creare una sottoclasse della barra di navigazione.


1

Questi metodi di categoria sono pericolosi e non per i principianti. Anche la complicazione con iOS4 e iOS5 che sono diversi, rende questa un'area che può causare bug a molte persone. Ecco una semplice sottoclasse che uso che supporta iOS4.0 ~ iOS6.0 ed è molto semplice.

.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

0

Non è consigliabile creare una sottoclasse della UINavigationBarclasse. Il modo preferito per personalizzare la barra di navigazione è impostare le sue proprietà in modo che appaia come desideri e utilizzare visualizzazioni personalizzate all'interno di UIBarButtonItems insieme a un delegato per ottenere il comportamento desiderato.

Cosa stai cercando di fare che necessita di sottoclassi?

Inoltre, non credo che IB stia effettivamente sostituendo la barra di navigazione. Sono abbastanza sicuro che semplicemente non mostri quello predefinito e abbia la tua barra di navigazione personalizzata come sottoview. Se chiami UINavigationController.navigationBar, ottieni un'istanza del tuo bar?


2
Ciao Ben, grazie. So che non è il modo migliore per creare una sottoclasse di UINavigationBar ma vorrei impostare un'immagine di sfondo che sovrascriva il metodo drawRect :. Il problema è che usando IB posso cambiare la classe della barra di navigazione in un UINavigationController ma a livello di codice non posso. E sì, IB sta effettivamente sostituendo la barra di navigazione: NSLog (@ "% @", self.navigationController.navigationBar); <CustomNavigationBar: 0x1806160; baseClass = UINavigationBar; frame = (0 20; 320 44); clipsToBounds = YES; opaco = NO; autoresize = W; layer = <CALayer: 0x1806da0 >>
Duccio

Sto anche usando la stessa tecnica e continua a funzionare in iOS5 (a differenza della tecnica della categoria UINavigationBar.)
David Pisoni


0

A seguito del commento di obb64, ho finito per usare il suo trucco con setViewControllers:animated:per impostare il controller come rootControlleril navigationControllercaricato dal pennino. Ecco il codice che sto usando:

- (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];
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.