Подтвердить что ты не робот

QLPreviewController удаляет или добавляет UIBarButtonItems

Я видел такой вопрос в Интернете, но, похоже, никто не знает ответа?

Я использую QLPreviewController для отображения PDF-документов. Сначала я использовал UIWebView, но вместо этого мне рекомендовалось использовать QLPreviewController для повышения производительности с большими документами.

то, что я хочу, это 4 пользовательских UIBarButtonItem в правом верхнем углу (так, где кнопка печати).

Мне удалось получить пользовательскую панель инструментов внизу, но это не совсем то, что я хочу.

Учитывая, что добавление настраиваемой кнопки в месте кнопки печати невозможно, я все же хочу удалить кнопку печати и использовать вместо нее панель инструментов.

EDIT (решение): Я нашел решение некоторое время назад, но не обновил этот пост, так вот как я решил проблему:

Я добавляю все кнопки вручную:

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

Ответ 1

Update:

Это больше не работает в iOS 6. Quick Look запускается в другом процессе с использованием XPC. Подробнее см. здесь. Я не предвижу никакого способа настроить QLPreviewController. Следующий ответ остается для всех, кто заинтересован в pre-iOS 6.


Я ответил на почти идентичный вопрос на днях здесь. Вопрос касался удаления кнопки печати, что не слишком сложно. Одно дело отметить QLPreviewController заключается в том, что он не предназначен для настройки. Я создал подкласс QLPreviewController, который можно настроить. Я добавил здесь в Github. Он предназначен для простого удаления кнопки действия, среди других функций. Это не займет много усилий, чтобы заменить кнопку на обычную.

Самое главное, на что нужно обратить внимание, это то, что кнопка действия снова добавляется в панель навигации в любое время, когда отображается новый документ. Вы должны заметить это в моем коде. В любое время RBFilePreviewer удаляется кнопка действия, вам просто нужно повторно добавить свои пользовательские кнопки. Чтобы добавить пользовательские кнопки, вы должны создать UIBarButtonItem, который содержит пользовательский вид с четырьмя кнопками в нем. Затем установите элемент кнопки правой панели как пользовательский UIBarButtonItem, который вы создали.

Update:

Я обновил RBFilePreviewer, чтобы вы могли настроить пользовательский элемент правой строки справа прямо из коробки. Просто позвоните -setRightBarButtonItem: на RBFilePreviewer, и он просто работает.

Ответ 2

Я искал решение этой проблемы в течение нескольких месяцев и, наконец, нашел способ настроить навигационную панель 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).

Ответ 3

Я получил ответ от Lukas Gross и применил его в Swift на iOS 8 и придумал это решение, которое сработало для меня:

ПРИМЕЧАНИЕ. У меня есть 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)
}

Ответ 4

Смешивая немного с существующими ответами/комментариями, я смог заставить это работать для моего варианта использования: мне нужно было отображать файлы внутри UINavigationController и сохранять возможность скрывать/показывать UINavigationBar, когда содержимое файла удаляется

На основе ответа от Лукаса Гросса и комментария от nacross здесь, что я закончил:

  • Добавить (подкласс) QLPreviewController в качестве контроллера детского представления. Это покажет две навигационные панели: один для главного навигационного контроллера и один из QLPreviewController
  • Настройте верхнее ограничение в представлении контейнера на верхний макет (с именем containerTop в коде)
  • Установите это ограничение в отрицательное значение, равное UINavigationBar плюс строку состояния, так что QLPreviewController UINavigationBar остается скрытым под основным UINavigationBar.
  • Используя KVO, просмотрите свойство hidden UINavigationBar, чтобы мы могли (1) скрыть/показать наши основные UINavigationBar и (2) reset верхнее ограничение

У меня получилось что-то вроде этого:

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
}

Анимации могут нуждаться в небольшой настройке, и это взломанно, но это лучше, чем отсутствие этой возможности. Должна быть возможность адаптировать эту стратегию к другим сценариям без UINavigationController

Примечание. Если при реализации представления контейнера для QLPreviewController из раскадровки произошел сбой, подкласс QLPreviewController и реализовать инициализатор:

class MyPreviewController: QLPreviewController {

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

}

Ответ 5

Я понимаю, что этот ответ немного запоздал для этого. Но я действительно нахожу решение для этого.

#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/

Удачи, ребята.