16

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

我正在使用 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]];
4

5 回答 5

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 等。

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

于 2013-09-06T13:00:34.420 回答
17

Update:

This no longer works in iOS 6. Quick Look runs in another process using XPC. See here for more details. I don't foresee any way to customize QLPreviewController. The following answer remains for anyone interested for pre-iOS 6.


I answered an almost identical question the other day here. The question pertained to removing the print button, which isn't too hard. One thing to note about QLPreviewController is that it's not meant to be customized. I have built a subclass of QLPreviewController that can be customized. I've put it here on Github. It's designed to easily remove the action button, among other features too. It wouldn't take much effort at all to replace the button with a custom one.

The biggest thing to watch out for is that the action button is re-added to the navigation bar anytime a new document is displayed. You should notice this in my code. Anytime RBFilePreviewer removes the action button, you just need to re-add your custom buttons. To add your custom buttons, you should create a UIBarButtonItem that holds a custom view with four buttons in it. Then set the right bar button item as the custom UIBarButtonItem you created.

Update:

I've updated RBFilePreviewer to allow you to set a custom right bar button item right out-of-the-box. Just call -setRightBarButtonItem: on RBFilePreviewer and it just works.

于 2011-08-08T18:46:22.697 回答
3

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

注意:我在 UINavigationController 中嵌入了 QLPreviewController!

代码:

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)
}
于 2015-07-22T15:20:20.793 回答
1

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

#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/得到了 想法

祝大家好运。

于 2014-11-27T08:01:18.017 回答
1

通过混合一些现有的答案/评论,我能够让它适用于我的用例:我需要在 a 中显示文件UINavigationController并保持隐藏/显示UINavigationBar文件内容被点击时的能力

根据Lukas Gross的回答和nacross的评论,这就是我最终要做的:

  1. 添加一个(子类)QLPreviewController作为子视图控制器。这将显示两个导航栏:一个用于主导航控制器,一个来自QLPreviewController
  2. 设置从容器视图到顶部布局指南的顶部约束(containerTop在代码中命名)
  3. 将此约束设置为负值,等于UINavigationBar状态栏的加号,以便QLPreviewController'UINavigationBar保持隐藏在 main 下UINavigationBar
  4. 使用 KVO,监视hidden属性,UINavigationBar以便我们可以(1)隐藏/显示我们的 mainUINavigationBar和(2)重置顶部约束

我最终得到了这样的结果:

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
}

动画可能需要稍作调整,而且很hacky,但总比没有这种可能性要好。应该可以将此策略调整到其他场景,而无需UINavigationController

注意:如果在为QLPreviewController情节提要实现容器视图时发生崩溃,请子类化QLPreviewController并实现初始化程序:

class MyPreviewController: QLPreviewController {

    required init?(coder aDecoder: NSCoder) {
        super.init(nibName: nil, bundle: nil)
    }

}
于 2016-05-13T15:02:42.927 回答