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

Задача

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

Фон

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

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

Обычный способ подключить контроллер поповера с помощью presentPopoverFromBarButtonItem:, и я могу достаточно легко это настроить. Чтобы это отображалось только после нажатия и удержания, мы должны установить представление, реагирующее на события жеста «длинное нажатие», как в следующем фрагменте:

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: и в этом методе я настрою и отобразлю всплывающее окно для отмены / возврата. Пока все хорошо.

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

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

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

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

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

Вопрос

Как я могу прикрепить распознаватель жестов к этой "кнопке"? В частности, как я могу реализовать стандартное нажатие и удержание для показа повтора в приложении для iPad?

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

Ответы [ 15 ]

24 голосов
/ 21 февраля 2012

Примечание: это больше не работает с iOS 11

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

[barButtonItem valueForKey:@"view"];

Это использует платформу Key-Value Coding для доступа к закрытой переменной _view UIBarButtonItem, где она сохраняет созданное представление.

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

12 голосов
/ 25 февраля 2014

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

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

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

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

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

9 голосов
/ 19 июня 2010

Вариант 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 заранее для последующего использования, если это необходимо.

7 голосов
/ 26 июля 2010

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

5 голосов
/ 19 августа 2011

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

3 голосов
/ 03 февраля 2015

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

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

func longPress(sender: UILongPressGestureRecognizer) {
let location = sender.locationInView(navigationController?.toolbar)
println(location)
}
2 голосов
/ 26 декабря 2017

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

Не беспокойтесь, мы все еще можем добавлять жесты касания в UIBarItems, так как он имеет свойство customView. Что мы можем сделать, так это создать кнопку с высотой и шириной 24 пт (в соответствии с рекомендациями Apple Human Interface Guidelines), а затем назначить пользовательский вид новой созданной кнопки. Приведенный ниже код поможет вам выполнить одно действие для одного нажатия, а другое - для нажатия кнопки панели 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)          
}
2 голосов
/ 20 апреля 2017

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

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

@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 вашей точки между этими двумя проставками.

2 голосов
/ 25 января 2012

Я пробовал решение @ 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 изменяется (и, таким образом, создается новое представление), мой собственный жест присоединяется, поэтому он всегда работает!

2 голосов
/ 04 ноября 2011

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

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

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

Interface Builder view of CustomBarButtonViewController

Затем в сообщении ViewDontLoad: моего ViewController я создал два UIGestureRecognizer. Первым был 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");
}

Теперь, когда в barButtonItem ViewController (подключенного через файл xib) происходит одиночный щелчок, жест жеста вызывает сообщение buttonPressed :. Если кнопка нажата, вызывается longPressGestured.

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

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

...