Как вы можете добавить 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) и хочет поделиться...
Ответы
Ответ 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)
}