QLPreviewController删除或添加UIBarButtonItems

时间:2011-08-05 13:30:20

标签: iphone objective-c ipad uibarbuttonitem qlpreviewcontroller

我在网上看到过很多这样的问题,但似乎没有人真正知道答案?

我正在使用QLPreviewController来显示PDF文档。我首先使用了一个UIWebView,但我建议使用QLPreviewController,而不是出于性能原因而使用更大的文档。

我想要的是右上角的4个自定义UIBarButtonItem(所以打印按钮在哪里)。

我设法在底部设置了一个自定义工具栏,但这不是我想要的。

考虑到无法在打印按钮的位置添加自定义按钮,我仍然想要删除打印按钮并使用自定义工具栏。

编辑(解决方案): 我刚才找到了解决方案,但没有更新这篇文章,所以这就是我如何解决问题:

我手动添加按钮:

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

5 个答案:

答案 0 :(得分:27)

我搜索了这个问题的解决方案好几个月,终于找到了一种方法来自定义QLPreviewController的导航栏。以前我也使用UIWebView来显示文档,因为我不允许在我的应用程序中显示某些机密文档的iOS共享按钮,这就是QLPreviewController的功能。但是我想拥有那些不错的功能,例如目录与小预览和东西。所以我找了一个可靠的方法来摆脱这个按钮。和你们一样,我首先考虑定制QLPreviewController的导航栏。但是,正如其他人已经指出的那样,自iOS6以来这绝对不可能。因此,我们不需要自定义现有的导航栏,而是创建一个自己的导航栏并将其放在QL导航栏的前面,从而隐藏它。

那怎么办呢? 首先,我们需要子类化QLPreviewContoller并覆盖viewDidAppear方法和viewWillLayoutSubviews,如下所示:

- (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 是QLPreviewController拥有的默认导航栏, overlayNavigationBar 是我们的自定义导航栏,将隐藏默认导航栏。我们还将键值观察添加到默认QL导航栏,以便在默认导航栏被隐藏/重新出现时得到通知。在 viewWillLayoutSubviews 方法中,我们会处理自定义导航栏框架。

接下来我们要做的是监听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;
}

所以现在我们需要实现获取QL导航栏所需的方法,并且总是为我们的自定义导航栏提供当前帧:

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

还有什么?当然,你需要定义属性,删除 dealloc 中的观察者,以及定义和设置iOS6属性(网上有很多例子......)。您还需要自定义导航栏并收听按钮回调。就是这样。

我知道这有点hacky ...隐藏/替换默认的QL操作按钮,将其隐藏在另一个导航栏下...但至少它对我来说是可靠的,你不会访问私有API等。< / p>

我在iOS 6.0 - 7.0以及iPad 2和iPad上的所有可用模拟器上测试了我的解决方案。 3,iPhone 4S&amp; 5(后者安装了iOS 7.0 Beta 6)。

答案 1 :(得分:17)

<强>更新

这在iOS 6中不再有效。快速查看在使用XPC的另一个进程中运行。有关详细信息,请参阅here。我没有预见到任何定制QLPreviewController的方法。以下答案适用于对iOS 6之前感兴趣的任何人。


前几天我回答了一个几乎相同的问题here。问题在于移除打印按钮,这不是太难。有关QLPreviewController的一点需要注意的是,它并不是要定制的。我已经构建了一个可以自定义的QLPreviewController子类。我把它here放在Github上。它的设计也可以轻松删除动作按钮等功能。用自定义按钮替换按钮不会花费太多精力。

最值得注意的是,只要显示新文档,操作按钮就会重新添加到导航栏。你应该在我的代码中注意到这一点。无论何时RBFilePreviewer删除操作按钮,您只需重新添加自定义按钮即可。要添加自定义按钮,您应创建一个UIBarButtonItem,其中包含一个自定义视图,其中包含四个按钮。然后将右侧栏按钮项设置为您创建的自定义UIBarButtonItem

<强>更新

我已更新RBFilePreviewer,允许您开箱即用设置自定义右侧栏按钮项。只需在-setRightBarButtonItem:上致电RBFilePreviewer即可。

答案 2 :(得分:3)

我接受了 Lukas Gross 的回复,并在iOS 8上将其应用于Swift,并提出了适合我的解决方案:

注意:我将QLPreviewController嵌入到UINavigationController中!

<强>代码:

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() ]
}

并应用此代码:

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)
}

答案 3 :(得分:1)

通过混合现有答案/评论中的一点我能够使用这个用例:我需要在UINavigationController内显示文件并保持隐藏/显示{{1}的能力轻触文件内容时

基于the answer from Lukas Gross以及来自 nacross 的评论,我最终在做什么:

  1. 添加({1}}的(子类)作为子视图控制器。这将显示两个导航栏:一个用于主导航控制器,另一个用于UINavigationBar
  2. 设置从容器视图到顶部布局指南的顶级约束(在代码中名为QLPreviewController
  3. 将此约束设置为负值,等于QLPreviewController加上状态栏,以便containerTop&#39; s UINavigationBar保持隐藏在主{{1}下}}。
  4. 使用KVO监控QLPreviewController的{​​{1}}属性,以便我们可以(1)隐藏/显示我们的主UINavigationBar和(2)重置顶部约束
  5. 我最终得到了类似的东西:

    UINavigationBar

    动画可能需要稍微调整一下,这很麻烦,但它比不具备这种可能性更好。 应该可以在没有hidden

    的情况下将此策略调整为其他方案

    注意:如果从故事板实现UINavigationBar的容器视图时遇到崩溃,请继承UINavigationBar并实现初始化程序:

    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
    }
    

答案 4 :(得分:0)

我知道这个答案有点晚了。 但我确实找到了解决方案。

#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

将其添加为类别并将其导入到QLPreviewController的子类中,然后调用

self.navigationItem.rightBarButtonItem = nil;//something you want

它对我有用。 我从http://nshipster.com/method-swizzling/了解到这一点 和来自http://codego.net/507056/

的想法

祝你好运,伙计们。