【问题标题】:QLPreviewController remove or add UIBarButtonItemsQLPreviewController 删除或添加 UIBarButtonItems
【发布时间】:2011-10-20 21:19:32
【问题描述】:

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

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

【问题讨论】:

  • RBFilePreviewer 现在支持您正在寻找的功能,无需修改。
  • 我的回答是否足以被接受并获得赏金?
  • 这不是我真正想要的,我现在遇到了 QLPreviewController 的另一个问题:stackoverflow.com/questions/7038438/… 但我会排除你的答案,因为它是最好的(也是唯一的)一个,这对我有点帮助。
  • 不能在ios6下,现在是一个单独的应用程序::见stackoverflow.com/questions/12675378/…

标签: iphone objective-c ipad uibarbuttonitem qlpreviewcontroller


【解决方案1】:

我为这个问题寻找了几个月的解决方案,终于找到了一种自定义 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 属性(网上有很多示例......)。您还需要自定义导航栏并收听按钮回调。就是这样。

我知道这有点老套...通过将默认 QL 操作按钮隐藏在另一个导航栏下方来隐藏/替换它...但至少它对我来说是可靠的并且您不访问私有 API 等。

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

【讨论】:

  • 这个需要选为答案,应该高很多!这很 hacky,但由于我们无法直接自定义 QLPreviewController 的导航栏,因此它是最佳答案并且它可以工作。我不喜欢上面的回答,“嘿,下载我的坏代码,它不适用于 iOS 6 或 7。”
  • 它不适用于 iOS 8 GM Seed。请提出其他建议。
  • 您的问题到底是什么?你能详细说明一下吗?我刚刚对其进行了测试,对我来说效果很好
  • 感谢分享这个卢卡斯。这绝对可行,到目前为止(不管子类化和放置我的视图优先)似乎是唯一可行的选择。干得好。
  • 使用QLPreviewController作为子视图控制器总是会在iOS8中产生两个导航栏。使用您找到 QL navigationBar 的方法,我能够使用 KVO 强制它始终保持隐藏状态。允许我使用父控制器的UINavigationController 自定义条形按钮项。
【解决方案2】:

更新:

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


前几天我回答了一个几乎相同的问题here。问题与删除打印按钮有关,这并不难。关于QLPreviewController 需要注意的一件事是它不是要定制的。我已经建立了一个可以自定义的QLPreviewController 的子类。我已经把它放在了 Github 上 here。它旨在轻松删除操作按钮以及其他功能。用自定义按钮替换按钮根本不需要太多努力。

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

更新:

我已更新 RBFilePreviewer 以允许您立即设置自定义右栏按钮项目。只需在RBFilePreviewer 上拨打-setRightBarButtonItem: 即可。

【讨论】:

  • 出色的工作!但是,是否可以更改整个 navigationItem tinColor(或整个背景颜色)?我无法做到这一点:(
  • 我刚刚添加了设置导航栏和工具栏色调颜色的功能。您只需要拉出最新版本。您实际上可以在没有我最新添加的情况下设置色调颜色,但是您必须根据您是推入导航堆栈还是模态呈现它而采取不同的做法。我的添加使这两种情况相同。
  • 谢谢,我看看。到目前为止,我可以通过继承 QLPreviewController、重写 viewWillAppear 方法、遍历 self.view 并搜索 UINavigationBar 视图对象,然后设置它的 tintColor 来实现它:)
  • 我不认为这已经针对 iOS 6 进行了更新,对吧?显然,Apple 更改了 QLpreviewController 导航栏及其所在位置或其他内容
  • 它不适用于 iOS 8 GM Seed。请提出其他建议。
【解决方案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)
}

【讨论】:

    【解决方案4】:

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

    #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/的想法

    祝你好运,伙计们。

    【讨论】:

      【解决方案5】:

      通过混合一些现有的答案/cmets,我能够让这个适用于我的用例:我需要在 UINavigationController 中显示文件并保持隐藏/显示 UINavigationBar 时的能力文件内容被窃听

      根据the answer from Lukas Grossnacross 的评论,我最终做了以下事情:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-05
        • 1970-01-01
        • 1970-01-01
        • 2022-01-22
        • 1970-01-01
        • 1970-01-01
        • 2011-01-23
        • 2018-08-02
        相关资源
        最近更新 更多