iphone - QLPreviewController elimina o agrega UIBarButtonItems
objective-c ipad (5)
He visto mucho este tipo de preguntas en Internet, pero parece que nadie realmente sabe la respuesta.
Estoy usando QLPreviewController para mostrar documentos PDF. Primero utilicé un UIWebView pero me recomendaron usar QLPreviewController en su lugar por motivos de rendimiento con documentos más grandes.
Lo que quiero son 4 UIBarButtonItem personalizados en la parte superior derecha (donde está el botón de impresión).
Logré obtener una barra de herramientas personalizada en la parte inferior, pero eso no es realmente lo que quiero.
Teniendo en cuenta que no es posible agregar un botón personalizado en el lugar del botón de impresión, todavía quiero eliminar el botón de impresión y usar la barra de herramientas personalizada en su lugar.
EDITAR (Solución): Encontré la solución hace un tiempo pero no actualicé esta publicación, así que aquí es cómo resolví el problema:
Añado todos los botones manualmente:
// Create a toolbar to have the buttons at the right side of the navigationBar
UIToolbar* toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 180, 44.01)];
[toolbar setTranslucent:YES];
// Create the array to hold the buttons, which then gets added to the toolbar
NSMutableArray* buttons = [[NSMutableArray alloc] initWithCapacity:4];
// Create button 1
button1 = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(button1Pressed)];
[buttons addObject:button1];
// Create button 2
button2 = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCompose target:self action:@selector(button2Pressed)];
[buttons addObject:button2];
// Create button 3
button3 = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self action:@selector(button3Pressed)];
[buttons addObject:button3];
// Create a action button
openButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(openWith)];
[buttons addObject:openButton];
// insert the buttons in the toolbar
[toolbar setItems:buttons animated:NO];
// and put the toolbar in the navigation bar
[[self navigationItem] setRightBarButtonItem:[[UIBarButtonItem alloc] initWithCustomView:toolbar]];
Al mezclar un poco las respuestas / comentarios existentes, pude lograr que esto funcionara para mi caso de uso: necesitaba mostrar los archivos dentro de un UINavigationController
y mantener la capacidad de ocultar / mostrar la UINavigationBar
cuando se UINavigationBar
el contenido del archivo
Basándome en la respuesta de Lukas Gross y el comentario de nacross, esto es lo que terminé haciendo:
- Agregue un (subclase de)
QLPreviewController
como un controlador de vista secundario. Esto mostrará dos barras de navegación: una para el controlador de navegación principal y otra paraQLPreviewController
- Configure una restricción superior desde la vista del contenedor a la guía de diseño superior (denominada
containerTop
en el código) - Establezca esta restricción en un valor negativo, igual a la
UINavigationBar
más la barra de estado, de modo que laQLPreviewController
de laUINavigationBar
permanezca oculta debajo de laUINavigationBar
. - Usando KVO, supervise la propiedad
hidden
de laUINavigationBar
para que podamos (1) ocultar / mostrar nuestraUINavigationBar
principalUINavigationBar
y (2) restablecer la restricción superior
Terminé con algo como esto:
var qlNavigationBar: UINavigationBar?
func getQLNavigationBar(fromView view: UIView) -> UINavigationBar? {
for v in view.subviews {
if v is UINavigationBar {
return v as? UINavigationBar
} else {
if let navigationBar = self.getQLNavigationBar(fromView: v) {
return navigationBar
}
}
}
return nil
}
func setObserverForNavigationBar() {
self.qlNavigationBar = self.getQLNavigationBar(fromView: self.view)
if let qln = self.qlNavigationBar {
qln.addObserver(self, forKeyPath: "hidden", options: [NSKeyValueObservingOptions.New, NSKeyValueObservingOptions.Old], context: nil)
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
self.navigationController?.setNavigationBarHidden(self.qlNavigationBar!.hidden, animated: true)
self.containerTop.constant = self.qlNavigationBar!.hidden ? self.getStatusBarHeight() * -1 : self.getFullNavigationBarHeight() * -1
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
self.setObserverForNavigationBar()
self.containerTop.constant = self.getFullNavigationBarHeight() * -1
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated);
if let qln = self.qlNavigationBar {
qln.removeObserver(self, forKeyPath: "hidden")
}
}
func getFullNavigationBarHeight() -> CGFloat {
if let nav = self.navigationController {
return nav.navigationBar.frame.origin.y + nav.navigationBar.frame.size.height
}
return 0
}
func getStatusBarHeight() -> CGFloat {
return UIApplication.sharedApplication().statusBarFrame.size.height
}
Las animaciones pueden necesitar un poco de ajuste y es intrépida, pero es mejor que no tener esta posibilidad. Debería ser posible adaptar esta estrategia a otros escenarios sin el UINavigationController
Nota: si tiene un bloqueo al implementar la vista del contenedor para el QLPreviewController
desde un guión gráfico, subclase el QLPreviewController
e implemente el inicializador:
class MyPreviewController: QLPreviewController {
required init?(coder aDecoder: NSCoder) {
super.init(nibName: nil, bundle: nil)
}
}
Busqué una solución a este problema durante meses y finalmente encontré una manera de personalizar la barra de navegación de un QLPreviewController. Anteriormente, también estaba usando UIWebView para mostrar documentos, ya que no tengo permiso para mostrar el botón Compartir en iOS de ciertos documentos confidenciales dentro de mi aplicación y esto es lo que hace el QLPreviewController. Sin embargo, quería tener esas características agradables como la tabla de contenido con las pequeñas vistas previas y esas cosas. Así que busqué una manera confiable de deshacerme de este botón. Al igual que ustedes, primero estuve estudiando la personalización de la barra de navegación del QLPreviewController. Sin embargo, como ya han dicho otros, esto no es posible desde iOS6. Entonces, en lugar de personalizar la barra de navegación existente, lo que debemos hacer es crear una propia y colocarla frente a la barra de navegación QL, ocultándola.
Entonces, ¿cómo hacer esto? En primer lugar, necesitamos subclase QLPreviewContoller y sobrescribir el método viewDidAppear y viewWillLayoutSubviews como esto:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.qlNavigationBar = [self getNavigationBarFromView:self.view];
self.overlayNavigationBar = [[UINavigationBar alloc] initWithFrame:[self navigationBarFrameForOrientation:[[UIApplication sharedApplication] statusBarOrientation]]];
self.overlayNavigationBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self.view addSubview:self.overlayNavigationBar];
NSAssert(self.qlNavigationBar, @"could not find navigation bar");
if (self.qlNavigationBar) {
[self.qlNavigationBar addObserver:self forKeyPath:@"hidden" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
}
// Now initialize your custom navigation bar with whatever items you like...
UINavigationItem *item = [[UINavigationItem alloc] initWithTitle:@"Your title goes here"];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneButtonTapped:)];
item.leftBarButtonItem = doneButton;
item.hidesBackButton = YES;
[self.overlayNavigationBar pushNavigationItem:item animated:NO];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
self.overlayNavigationBar.frame = [self navigationBarFrameForOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
}
qlNavigationBar es la barra de navegación predeterminada que pertenece a QLPreviewController, overlayNavigationBar es nuestra barra personalizada que ocultará la predeterminada. También agregamos una observación clave-valor a la barra de navegación de QL predeterminada para recibir una notificación cuando la barra de navegación predeterminada se oculta / reaparece. En el método viewWillLayoutSubviews nos ocupamos de nuestro marco personalizado de la barra de navegación.
Lo siguiente que debemos hacer es escuchar los cambios de visibilidad de la barra de navegación de Quicklook:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// Toggle visiblity of our custom navigation bar according to the ql navigationbar
self.overlayNavigationBar.hidden = self.qlNavigationBar.isHidden;
}
Así que ahora necesitamos implementar los métodos que necesitamos para obtener la barra de navegación QL y uno que siempre nos da el marco actual para nuestra barra de navegación personalizada:
- (UINavigationBar*)getNavigationBarFromView:(UIView *)view {
// Find the QL Navigationbar
for (UIView *v in view.subviews) {
if ([v isKindOfClass:[UINavigationBar class]]) {
return (UINavigationBar *)v;
} else {
UINavigationBar *navigationBar = [self getNavigationBarFromView:v];
if (navigationBar) {
return navigationBar;
}
}
}
return nil;
}
- (CGRect)navigationBarFrameForOrientation:(UIInterfaceOrientation)orientation {
// We cannot use the frame of qlNavigationBar as it changes position when hidden, also there seems to be a bug in iOS7 concerning qlNavigationBar height in landscape
return CGRectMake(0.0f, self.isIOS6 ? 20.0f : 0.0f, self.view.bounds.size.width, [self navigationBarHeight:orientation]);
}
- (CGFloat)navigationBarHeight:(UIInterfaceOrientation)orientation {
if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
if(UIInterfaceOrientationIsLandscape(orientation)) {
return self.isIOS6 ? 32.0f : 52.0f;
} else {
return self.isIOS6 ? 44.0f : 64.0f;
}
} else {
return self.isIOS6 ? 44.0f : 64.0f;
}
}
¿Qué más? Bueno, por supuesto, necesita definir propiedades, eliminar el observador en dealloc , así como definir y configurar la propiedad iOS6 (hay muchos ejemplos en la web ...). También necesitas personalizar tu barra de navegación y escuchar las devoluciones de llamada de los botones. Eso es.
Sé que esto es un poco intrépido ... ocultar / reemplazar el botón de acción QL predeterminado escondiéndolo debajo de otra barra de navegación ... pero bueno, al menos funciona de forma confiable para mí y no accedes a API privadas, etc.
Probé mi solución en todos los simuladores disponibles para iOS 6.0 - 7.0, así como en iPad 2 y 3, iPhone 4S y 5 (este último con iOS 7.0 Beta 6 instalado).
Entiendo que esta respuesta es un poco tarde para esto. Pero realmente encuentro una solución para esto.
#import "UINavigationItem+Custome.h"
#import <QuickLook/QuickLook.h>
#import <objc/runtime.h>
@implementation UINavigationItem (Custome)
void MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL);
- (void) override_setRightBarButtonItem:(UIBarButtonItem *)item animated:(BOOL)animated{
if (item && [item.target isKindOfClass:[QLPreviewController class]] && item.action == @selector(actionButtonTapped:)){
QLPreviewController* qlpc = (QLPreviewController*)item.target;
[self override_setRightBarButtonItem:qlpc.navigationItem.rightBarButtonItem animated: animated];
}else{
[self override_setRightBarButtonItem:item animated: animated];
}
}
+ (void)load {
MethodSwizzle(self, @selector(setRightBarButtonItem:animated:), @selector(override_setRightBarButtonItem:animated:));
}
void MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL) {
Method origMethod = class_getInstanceMethod(c, origSEL);
Method overrideMethod = class_getInstanceMethod(c, overrideSEL);
if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
}else{
method_exchangeImplementations(origMethod, overrideMethod);
}
}
@end
Agregue esto como una categoría e importe esto en la subclase de su QLPreviewController y solo llame al
self.navigationItem.rightBarButtonItem = nil;//something you want
Esto funciona para mi. Aprendo esto de http://nshipster.com/method-swizzling/ y pensamientos de http://codego.net/507056/
Buena suerte chicos.
Tomé la respuesta de Lukas Gross y la apliqué en Swift en iOS 8 y encontré esta solución que funcionó para mí:
NOTA: ¡Tengo el QLPreviewController integrado en un UINavigationController!
Código:
var QLNavigationBar: UINavigationBar?
var overlayNavigationBar: UINavigationBar?
func getQLNavigationBar(fromView view: UIView) -> UINavigationBar? {
for v in view.subviews {
if v is UINavigationBar {
return v as? UINavigationBar
} else {
if let navigationBar = self.getQLNavigationBar(fromView: (v as! UIView)) {
return navigationBar
}
}
}
return nil
}
func handleNavigationBar() {
self.QLNavigationBar = self.getQLNavigationBar(fromView: self.navigationController!.view)
self.overlayNavigationBar = UINavigationBar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 64.0))
self.overlayNavigationBar?.autoresizingMask = UIViewAutoresizing.FlexibleWidth
if let qln = self.QLNavigationBar {
qln.addObserver(self, forKeyPath: "hidden", options: (NSKeyValueObservingOptions.New | NSKeyValueObservingOptions.Old), context: nil)
qln.superview?.addSubview(self.overlayNavigationBar!)
}
var item = UINavigationItem(title: self.navigationItem.title!)
var doneBtn = UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: "doneBtnPressed")
item.leftBarButtonItem = doneBtn
item.hidesBackButton = true
self.overlayNavigationBar?.pushNavigationItem(item, animated: false)
self.overlayNavigationBar?.tintColor = .whiteColor()
self.overlayNavigationBar?.barTintColor = .blackColor()
self.overlayNavigationBar?.titleTextAttributes = [
NSForegroundColorAttributeName : UIColor.whiteColor() ]
}
Y aplicando este código así:
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if self.QLNavigationBar!.hidden {
self.overlayNavigationBar?.hidden = self.QLNavigationBar!.hidden
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), {
self.QLNavigationBar?.superview?.sendSubviewToBack(self.QLNavigationBar!)
if !self.QLNavigationBar!.hidden {
self.overlayNavigationBar?.hidden = self.QLNavigationBar!.hidden
}
})
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.handleNavigationBar()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.overlayNavigationBar?.frame = CGRectMake(0, 0, self.view.bounds.size.width, 64.0)
}
Actualizar:
Esto ya no funciona en iOS 6. Quick Look se ejecuta en otro proceso usando XPC. Vea here para más detalles. No preveo ninguna forma de personalizar QLPreviewController. La siguiente respuesta sigue siendo para cualquier persona interesada en pre-iOS 6.
Respondí una pregunta casi idéntica el otro día here . La pregunta se refería a la eliminación del botón de impresión, que no es demasiado difícil. Una cosa a tener en cuenta acerca de QLPreviewController
es que no se debe personalizar. He construido una subclase de QLPreviewController
que se puede personalizar. Lo he puesto here en Github. Está diseñado para eliminar fácilmente el botón de acción, entre otras características también. No tomaría mucho esfuerzo reemplazar el botón con uno personalizado.
Lo más importante a tener en cuenta es que el botón de acción se vuelve a agregar a la barra de navegación cada vez que se muestra un nuevo documento. Debes notar esto en mi código. En RBFilePreviewer
momento, RBFilePreviewer
elimina el botón de acción, solo debe volver a agregar sus botones personalizados. Para agregar sus botones personalizados, debe crear un UIBarButtonItem
que contenga una vista personalizada con cuatro botones. A continuación, establezca el elemento del botón de la barra derecha como el UIBarButtonItem
personalizado que creó.
Actualizar:
He actualizado RBFilePreviewer
para permitirle configurar un elemento personalizado del botón de la barra derecha justo fuera de la caja. Simplemente llame a -setRightBarButtonItem:
en RBFilePreviewer
y simplemente funciona.