objective c - item - Establezca una subclase personalizada de UINavigationBar en UINavigationController mediante programaciĆ³n
navigation item swift (12)
¿Alguien sabe cómo puedo usar mi subclase personalizada de UINavigationBar
si UINavigationBar
UINavigationController
programáticamente (sin IB)?
Arrastra un UINavigationController
en IB muéstrame en la Barra de navegación y usando Identity Inspectory Puedo cambiar el tipo de clase y establecer mi propia subclase de UINavigationBar
pero programáticamente no puedo, la propiedad navigationBar
del Controlador de navegación es de solo lectura ...
¿Qué debo hacer para personalizar la barra de navegación programáticamente? ¿IB es más "poderoso" que "código"? Creí que todo lo que se puede hacer en IB podría hacerse también programáticamente.
A partir de iOS 4, puede usar la clase UINib
para ayudar a resolver este problema.
- Crea tu subclase
UINavigationBar
personalizada. - Cree un xib vacío, agregue un
UINavigationController
como el único objeto. - Establezca la clase para la
UINavigationController
deUINavigationBar
en su subclase personalizada. - Configure su controlador de vista raíz a través de uno de estos métodos:
-
[navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
-
[navController pushViewController:myRootVC];
-
En codigo:
UINib *nib = [UINib nibWithNibName:@"YourCustomXib" bundle:nil];
UINavigationController *navController =
[[nib instantiateWithOwner:nil options:nil] objectAtIndex:0];
Ahora tienes un UINavigationController
con tu UINavigationBar
personalizada.
Además del comentario de obb64, terminé usando su truco con setViewControllers:animated:
para configurar el controlador como el controlador rootController
para el rootController
de navigationController
cargado desde el plumín. Aquí está el código que estoy 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];
}
Desde iOS5, Apple proporciona un método para hacer esto directamente. Reference
UINavigationController *navigationController= [[UINavigationController alloc]initWithNavigationBarClass:[CustomNavBar class] toolbarClass:nil];
[navigationController setViewControllers:[NSArray arrayWithObject:yourRootViewController]];
Estos métodos de categoría son peligrosos y no para principiantes. Además, la complicación de que iOS4 e iOS5 sean diferentes hace que este sea un área que puede causar errores en muchas personas. Aquí hay una subclase simple que uso que admite iOS4.0 ~ iOS6.0 y es muy simple.
.h
@interface XXXNavigatioNBar : UINavigationBar
@end
.metro
#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
La solución de Michael funciona, pero puedes evitar NSKeyedArchiver y la utilidad ''xxd''. Simplemente haga una subclase UINavigationController y anule initWithRootViewController
, cargando su NavigationController NIB personalizado directamente:
- (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;
}
No se recomienda subclasificar la clase UINavigationBar
. La forma preferida de personalizar la barra de navegación es establecer sus propiedades para que aparezca como lo desee y usar vistas personalizadas dentro de UIBarButtonItems junto con un delegado para obtener el comportamiento deseado.
¿Qué estás tratando de hacer que necesita subclasificación?
Además, no creo que IB esté realmente reemplazando la barra de navegación. Estoy bastante seguro de que simplemente no muestra el predeterminado y tiene su barra de navegación personalizada como una subvista. Si llama a UINavigationController.navigationBar, ¿obtiene una instancia de su barra?
No necesita ensuciar con el XIB simplemente use KVC.
[self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"];
Por lo que puedo decir, a veces es necesario subclasificar UINavigationBar, para hacer un restyling no estándar. A veces es posible evitar tener que hacerlo usando categories , pero no siempre.
Actualmente, hasta donde yo sé, la única forma de establecer una UINavigationBar personalizada dentro de un UIViewController es a través de IB (es decir, a través de un archivo); probablemente no sea así, pero por ahora, tenemos que vivir con eso.
Esto a menudo está bien, pero a veces el uso de IB no es realmente factible.
Entonces, vi tres opciones:
- Subclase UINavigationBar y conéctelo en IB, luego ensucie sobre cargar el plumín cada vez que desee un UINavigationController,
- Utilice el reemplazo de métodos dentro de una categoría para cambiar el comportamiento de UINavigationBar, en lugar de crear subclases, o
- Subclase UINavigationBar y averigua un poco sobre archivar / desarchivar el UINavigationController.
La opción 1 era inviable (o al menos molesto) en este caso, ya que necesitaba crear el UINavigationController programáticamente, 2 es una opción un poco peligrosa y más de último recurso en mi opinión, así que elegí la opción 3.
Mi enfoque era crear un archivo ''plantilla'' de un UINavigationController, y desarchivar eso, devolverlo en initWithRootViewController
.
Así es cómo:
En IB, creé un UINavigationController con el conjunto de clases apropiado para UINavigationBar.
Luego, tomé el controlador existente y +[NSKeyedArchiver archiveRootObject:toFile:]
una copia archivada usando +[NSKeyedArchiver archiveRootObject:toFile:]
. Acabo de hacer esto dentro del delegado de la aplicación, en el simulador.
Luego utilicé la utilidad ''xxd'' con el distintivo -i, para generar código c a partir del archivo guardado, para incrustar la versión archivada en mi subclase ( xxd -i path/to/file
).
Dentro de initWithRootViewController
, initWithRootViewController
esa plantilla y establezco el resultado del desarchivo:
// 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];
}
Entonces, puedo tomar una nueva instancia de mi subclase UIViewController que tiene configurada la barra de navegación personalizada:
UIViewController *modalViewController = [[[CTNavigationController alloc] initWithRootViewController:myTableViewController] autorelease];
[self.navigationController presentModalViewController:modalViewController animated:YES];
Esto me da un UITableViewController modal con una barra de navegación y una barra de herramientas configuradas, y con la clase de barra de navegación personalizada en su lugar. No necesité hacer ningún reemplazo de método levemente desagradable, y no tengo que ensuciarme con las puntas cuando realmente solo quiero trabajar programáticamente.
Me gustaría ver el equivalente de +layerClass
dentro de UINavigationController - +navigationBarClass
- pero por ahora, esto funciona.
Si desea subclass navBar solo para cambiar la imagen de fondo, no es necesario en iOS 5. Habrá un método como este setBackgroundImage
Un escenario que he encontrado que necesitamos usar subclase en lugar de categoría es establecer el color de fondo de la barra de navegación con la imagen de patrón, porque en iOS5 sobrescribir drawRect utilizando la categoría ya no funciona. Si desea admitir ios3.1-5.0, la única manera que puede hacer es crear una subclase en la barra de navegación.
Yo uso la "opción 1"
Crea un archivo nib con solo el UINavigationController en él. Y configure la clase UINavigationBar en mi Clase personalizada.
self.navigationController = [[[NSBundle mainBundle] loadNibNamed:@"navigationbar" owner:self options:nil] lastObject];
[navigationController pushViewController:rootViewController animated:YES];
Actualización: el uso de object_SetClass()
ya no funciona como si iOS5 GM. Se agregó una solución alternativa a continuación.
Use NSKeyedUnarchiver para configurar manualmente la clase de desarchivar para la barra de navegación.
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: Esta solución original solo funciona antes de iOS5:
Hay una gran solución, que publiqué here : inserte la subclase navBar directamente en su vista UINavigationController
:
#import <objc/runtime.h>
- (void)viewDidLoad {
[super viewDidLoad];
object_setClass(self.navigationController.navigationBar, [MyNavBar class]);
// the rest of your viewDidLoad code
}