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

Как вы можете добавить UIGestureRecognizer в UIBarButtonItem, как в общей схеме отмены/повтора UIPopoverController в приложениях iPad?

Проблема

В моем приложении iPad я не могу прикреплять popover к элементу панели кнопок только после событий нажатия и удержания. Но это кажется стандартным для отмены/повтора. Как это делают другие приложения?

Фон

У меня есть кнопка отмены (UIBarButtonSystemItemUndo) на панели инструментов моего приложения UIKit (iPad). Когда я нажимаю кнопку отмены, он вызывает это действие, которое отменяется:, и это выполняется правильно.

Тем не менее, "стандартное соглашение UE" для отмены/повтора на iPad заключается в том, что нажатие на отмену отменяет отмену, но нажатие и удержание кнопки открывает контроллер popover, где пользователь выбирает "отмену" или "повторить" до тех пор, пока контроллер не будет отклонено.

Обычный способ подключения контроллера popover - с currentPopoverFromBarButtonItem:, и я могу настроить это достаточно легко. Чтобы показать это только после нажатия и удержания, мы должны установить представление, чтобы реагировать на события жестов "долгое нажатие", как в этом фрагменте:

UILongPressGestureRecognizer *longPressOnUndoGesture = [[UILongPressGestureRecognizer alloc] 
       initWithTarget:self 
               action:@selector(handleLongPressOnUndoGesture:)];
//Broken because there is no customView in a UIBarButtonSystemItemUndo item
[self.undoButtonItem.customView addGestureRecognizer:longPressOnUndoGesture];
[longPressOnUndoGesture release];

При этом после нажатия и удержания в представлении будет вызван метод handleLongPressOnUndoGesture: и в рамках этого метода я настрою и покажу popover для отмены/повтора. Пока что так хорошо.

Проблема заключается в том, что нет никакого вида для присоединения. self.undoButtonItem - это UIButtonBarItem, а не представление.

Возможные решения

1) [Идеал] Прикрепите распознаватель жестов к элементу панели кнопок. Можно подключить распознаватель жестов к представлению, но UIButtonBarItem - это не представление. У него есть свойство для .customView, но это свойство равно нулю, когда buttonbaritem является стандартным системным типом (в данном случае это).

2) Используйте другое представление. Я мог бы использовать UIToolbar, но для этого потребовалось бы какое-то странное хитрое тестирование и было бы все вокруг взломать, если вообще возможно, в первую очередь. Другого альтернативного взгляда использовать не могу, о чем я могу думать.

3) Используйте свойство customView. Стандартные типы, такие как UIBarButtonSystemItemUndo, не имеют customView (это нуль). Настройка customView приведет к стиранию стандартного содержимого, которое оно должно иметь. Это будет означать повторную реализацию всех взглядов и функций UIBarButtonSystemItemUndo, если это даже возможно сделать.

Вопрос

Как я могу прикрепить распознаватель жестов к этому "button"? Более конкретно, как я могу реализовать стандартное прессование и удерживать-показать-повторить-popover в iPad-приложении?

Идеи? Большое вам спасибо, особенно если кто-то действительно работает в своем приложении (я думаю о вас, omni) и хочет поделиться...

4b9b3361

Ответ 1

Вместо этого беспорядка с попыткой найти представление UIBarButtonItem в списке подзаголовков панели инструментов вы также можете попробовать это, как только элемент будет добавлен на панель инструментов:

[barButtonItem valueForKey:@"view"];

Это использует структуру кодирования ключевого значения для доступа к частной переменной _view UIBarButtonItem, где она сохраняет созданное представление.

Конечно, я не знаю, где это происходит с точки зрения Apple API API (это общедоступный метод, используемый для доступа к частной переменной публичного класса, а не к доступу к частным фреймворкам, чтобы придать эффектные эффекты Apple или что-то еще), но он работает, и довольно безболезненно.

Ответ 2

Это старый вопрос, но он по-прежнему появляется в поиске Google, и все остальные ответы чересчур сложны.

У меня есть кнопочная панель с элементами кнопки, которые вызывают действие: forEvent: метод при нажатии.

В этом методе добавьте следующие строки:

bool longpress=NO;
UITouch *touch=[[[event allTouches] allObjects] objectAtIndex:0];
if(touch.tapCount==0) longpress=YES;

Если это был один кран, tapCount - один. Если это был двойной кран, tapCount - два. Если это длинное нажатие, tapCount равен нулю.

Ответ 3

Вариант 1 действительно возможен. К сожалению, очень сложно найти UIView, создаваемый UIBarButtonItem. Вот как я нашел это:

[[[myToolbar subviews] objectAtIndex:[[myToolbar items] indexOfObject:myBarButton]] addGestureRecognizer:myGesture];

Это сложнее, чем должно быть, но это явно предназначено для того, чтобы люди не обманывали себя с помощью кнопок и чувств.

Обратите внимание, что фиксированные/гибкие пробелы не считаются как виды!

Чтобы обрабатывать пробелы, вы должны каким-то образом их обнаружить, и, к сожалению, SDK просто не имеет простого способа сделать это. Есть решения, и вот некоторые из них:

1) Установите значение тега UIBarButtonItem для него, указав слева направо на панели инструментов. Это требует слишком много ручной работы, чтобы синхронизировать IMO.

2) Установите для свойства с включенными пробелами значение NO. Затем используйте этот фрагмент кода, чтобы установить для него значения тегов:

NSUInteger index = 0;
for (UIBarButtonItem *anItem in [myToolbar items]) {
    if (anItem.enabled) {
        // For enabled items set a tag.
        anItem.tag = index;
        index ++;
    }
}

// Tag is now equal to subview index.
[[[myToolbar subviews] objectAtIndex:myButton.tag] addGestureRecognizer:myGesture];

Конечно, у этого есть потенциальная ошибка, если вы отключите кнопку по какой-либо другой причине.

3) Вручную запрограммируйте панель инструментов и обработайте индексы самостоятельно. Поскольку вы сами будете создавать UIBarButtonItem, вы заранее узнаете, какой индекс они будут в подзаголовках. Вы можете расширить эту идею до сбора UIView заранее для последующего использования, если это необходимо.

Ответ 4

Вместо того, чтобы нащупывать subview, вы можете создать кнопку самостоятельно и добавить элемент панели кнопок с настраиваемым представлением. Затем вы подключите GR к своей пользовательской кнопке.

Ответ 5

Пока этот вопрос уже более года, это все еще довольно неприятная проблема. Я отправил отчет об ошибке Apple (rdar://9982911), и я предлагаю, чтобы кто-то другой, кто чувствует то же самое, дублирует его.

Ответ 6

Я попробовал нечто похожее на то, что предложил Бен. Я создал пользовательский вид с помощью UIButton и использовал это как customView для UIBarButtonItem. Мне не нравилось подобное предложение:

  • Кнопка должна быть стилизована, чтобы не торчать, как больной палец на UIToolBar
  • С UILongPressGestureRecognizer я, похоже, не получил событие click для "Touch in Inside" (Это может быть, скорее всего, будет ошибка программирования с моей стороны.)

Вместо этого я решился на что-то хакерское в лучшем случае, но это работает для меня. Я использую XCode 4.2, и я использую ARC в коде ниже. Я создал новый подкласс UIViewController под названием CustomBarButtonItemView. В файле CustomBarButtonItemView.xib я создал UIToolBar и добавил на панель один элемент UIBarButtonItem. Затем я укрепил панель инструментов почти на ширину кнопки. Затем я подключил свойство представления File Owner к UIToolBar.

Interface Builder view of CustomBarButtonViewController

Затем в моем представлении ViewController viewDidLoad: я создал два UIGestureRecognizers. Первым был UILongPressGestureRecognizer для щелчка и удержания, а второй - UITapGestureRecognizer. Кажется, я не могу правильно выполнить действие для UIBarButtonItem в представлении, поэтому я подделываю его с помощью UITapGestureRecognizer. UIBarButtonItem показывает себя как щелкнув, и UITapGestureRecognizer выполняет действие так же, как если бы действие и цель для UIBarButtonItem были установлены.

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib

    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGestured)];

    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(buttonPressed:)];

    CustomBarButtomItemView* customBarButtonViewController = [[CustomBarButtomItemView alloc] initWithNibName:@"CustomBarButtonItemView" bundle:nil];

    self.barButtonItem.customView = customBarButtonViewController.view;

    longPress.minimumPressDuration = 1.0;

    [self.barButtonItem.customView addGestureRecognizer:longPress];
    [self.barButtonItem.customView addGestureRecognizer:singleTap];        

}

-(IBAction)buttonPressed:(id)sender{
    NSLog(@"Button Pressed");
};
-(void)longPressGestured{
    NSLog(@"Long Press Gestured");
}

Теперь, когда в элементе ViewControllerViewButtonItem появляется один клик (Connected через xib файл), жест выделения вызывает вызов buttonPressed:. Если кнопка удерживается нажатой, то длится нажатие кнопки LongPressGestured.

Для изменения внешнего вида UIBarButton я предлагаю создать свойство CustomBarButtonItemView, чтобы разрешить доступ к Custom BarButton и сохранить его в классе ViewController. Когда отправляется сообщение longPressGestured, вы можете изменить значок системы на кнопке.

Один из найденных мной свойств: свойство customview принимает вид как есть. Если вы измените пользовательский UIBarButtonitem из CustomBarButtonItemView.xib, чтобы изменить ярлык на @ "действительно длинную строку" , например, кнопка изменит размер, но только левая большая часть отображаемой кнопки находится в представлении которые просматриваются экземплярами UIGestuerRecognizer.

Ответ 7

Я попробовал решение @voi1d, которое отлично поработало, пока я не сменил название кнопки, на которую я добавил длинный жест прессы. Изменение названия означает создание нового UIView для кнопки, которая заменяет оригинал, в результате чего добавленный жест перестает работать, как только происходит изменение кнопки (что часто происходит в моем приложении).

Мое решение заключалось в подклассе UIToolbar и переопределении метода addSubview:. Я также создал свойство, которое содержит указатель на цель моего жеста. Вот точный код:

- (void)addSubview:(UIView *)view {
    // This method is overridden in order to add a long-press gesture recognizer
    // to a UIBarButtonItem. Apple makes this way too difficult, but I am clever!
    [super addSubview:view];
    // NOTE - this depends the button of interest being 150 pixels across (I know...)
    if (view.frame.size.width == 150) {
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:targetOfGestureRecognizers 
                                                                                                action:@selector(showChapterMenu:)];
        [view addGestureRecognizer:longPress];
    }
}

В моей конкретной ситуации кнопка, которая меня интересует, составляет 150 пикселей в поперечнике (и это единственная кнопка), поэтому я использую этот тест. Вероятно, это не самый безопасный тест, но он работает для меня. Очевидно, вам придется придумать свой собственный тест и поставить свой собственный жест и селектор.

Преимущество этого в том, что в любой момент, когда мой UIBarButtonItem изменяется (и, таким образом, создает новое представление), мой пользовательский жест присоединяется, поэтому он всегда работает!

Ответ 8

Вы также можете просто сделать это...

let longPress = UILongPressGestureRecognizer(target: self, action: "longPress:")
navigationController?.toolbar.addGestureRecognizer(longPress)

func longPress(sender: UILongPressGestureRecognizer) {
let location = sender.locationInView(navigationController?.toolbar)
println(location)
}

Ответ 9

@voi1d Ответ на второй вариант является наиболее полезным для тех, кто не хочет переписывать все функции UIBarButtonItem. Я завернул это в категорию, чтобы вы могли просто сделать:

[myToolbar addGestureRecognizer:(UIGestureRecognizer *)recognizer toBarButton:(UIBarButtonItem *)barButton];

с небольшой обработкой ошибок, если вы заинтересованы. ПРИМЕЧАНИЕ: каждый раз, когда вы добавляете или удаляете элементы с панели инструментов с помощью setItems, вам придется повторно добавить распознаватели жестов - я думаю, UIToolbar воссоздает холдинг UIViews каждый раз, когда вы настраиваете массив элементов.

UIToolbar + Gesture.h

#import <UIKit/UIKit.h>

@interface UIToolbar (Gesture)

- (void)addGestureRecognizer:(UIGestureRecognizer *)recognizer toBarButton:(UIBarButtonItem *)barButton;

@end

UIToolbar + Gesture.m

#import "UIToolbar+Gesture.h"

@implementation UIToolbar (Gesture)

- (void)addGestureRecognizer:(UIGestureRecognizer *)recognizer toBarButton:(UIBarButtonItem *)barButton {
  NSUInteger index = 0;
  NSInteger savedTag = barButton.tag;

  barButton.tag = NSNotFound;
  for (UIBarButtonItem *anItem in [self items]) {
    if (anItem.enabled) {
      anItem.tag = index;
      index ++;
    }
  }
  if (NSNotFound != barButton.tag) {
    [[[self subviews] objectAtIndex:barButton.tag] addGestureRecognizer:recognizer];
  }
  barButton.tag = savedTag;
}

@end

Ответ 10

Я знаю, что это уже давно, но я провел ночь, ударяя головой о стену, пытаясь найти приемлемое решение. Я не хотел использовать свойство customView, потому что избавился бы от всех встроенных функций, таких как оттенок кнопки, отключенный оттенок, и длительное нажатие будет подвержено такому маленькому ящику, в то время как UIBarButtonItems распространили свой хит-пакет довольно пути. Я придумал это решение, которое, как мне кажется, работает очень хорошо, и это всего лишь небольшая боль для реализации.

В моем случае первые две кнопки на моем баре будут идти в одно и то же место, если они будут длительное нажатие, поэтому мне просто нужно было обнаружить, что пресса произошла до определенной точки Х. Я добавил длинный идентификатор распознавания жестов в UIToolbar (также работает, если вы добавите его в UINavigationBar), а затем добавил дополнительный UIBarButtonItem с шириной 1 пиксель сразу после второй кнопки. Когда загружается представление, я добавляю UIView, что один пиксель в ширину для этого UIBarButtonItem, как customView. Теперь я могу проверить точку, в которой произошло длинное нажатие, а затем посмотреть, меньше ли X, чем X кадра пользовательского вида. Здесь немного Swift 3 Code

@IBOutlet var thinSpacer: UIBarButtonItem!

func viewDidLoad() {
    ...
    let thinView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 22))
    self.thinSpacer.customView = thinView

    let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(gestureRecognizer:)))
    self.navigationController?.toolbar.addGestureRecognizer(longPress)
    ...
}

func longPressed(gestureRecognizer: UIGestureRecognizer) {
    guard gestureRecognizer.state == .began, let spacer = self.thinSpacer.customView else { return }
    let point = gestureRecognizer.location(ofTouch: 0, in: gestureRecognizer.view)
    if point.x < spacer.frame.origin.x {
        print("Long Press Success!")
    } else {
        print("Long Pressed Somewhere Else")
    }
}

Определенно не идеальный, но достаточно простой для моего использования. Если вам нужно указать длительное нажатие на определенные кнопки в определенных местах, это становится немного более раздражающим, но вы должны быть в состоянии окружить кнопки, которые вам нужны, чтобы обнаружить длительное нажатие с помощью тонких прокладок, а затем просто убедитесь, что ваша точка X между обеими этими разделителями.

Ответ 11

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

Я создал простое расширение для UIBarButtonItem:

fileprivate extension UIBarButtonItem {
    var view: UIView? {
        return value(forKey: "view") as? UIView
    }
    func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
        view?.addGestureRecognizer(gestureRecognizer)
    }
}

После этого вы можете просто добавить свои распознаватели жестов к элементам вашего метода ViewController viewDidLoad:

@IBOutlet weak var myBarButtonItem: UIBarButtonItem!

func setupLongPressObservation() {
    let recognizer = UILongPressGestureRecognizer(
        target: self, action: #selector(self.didLongPressMyBarButtonItem(recognizer:)))
    myBarButtonItem.addGestureRecognizer(recognizer)
}

Ответ 12

До тех пор, пока iOS 11, let barbuttonView = barButton.value(forKey: "view") as? UIView не даст нам ссылку на представление для barButton, в котором мы можем легко добавлять жесты, но в iOS 11 все совсем по-другому, вышеприведенная строка кода заканчивается nil, поэтому добавление жестов нажатия к виду для ключевого "представления" бессмысленно.

Не беспокойтесь, мы все равно можем добавить жесты жесты в UIBarItems, так как у него есть свойство customView. Мы можем создать кнопку с высотой и шириной 24 pt (в соответствии с Руководством по человеческому интерфейсу Apple) и пользовательским представлением в качестве вновь созданной кнопки. Следующий код поможет вам выполнить одно действие для одного нажатия, а другое - нажать кнопку 5 раз.

ПРИМЕЧАНИЕ Для этого у вас уже есть ссылка на barbuttonitem.

func setupTapGestureForSettingsButton() {
      let multiTapGesture = UITapGestureRecognizer()
      multiTapGesture.numberOfTapsRequired = 5
      multiTapGesture.numberOfTouchesRequired = 1
      multiTapGesture.addTarget(self, action: #selector(HomeTVC.askForPassword))
      let button = UIButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
      button.addTarget(self, action: #selector(changeSettings(_:)), for: .touchUpInside)
      let image = UIImage(named: "test_image")withRenderingMode(.alwaysTemplate)
      button.setImage(image, for: .normal)
      button.tintColor = ColorConstant.Palette.Blue
      settingButton.customView = button
      settingButton.customView?.addGestureRecognizer(multiTapGesture)          
}