如何在UIBarButtonItem中将UIGestureRecognizer添加到iPad应用程序中常见的撤销/重做UIPopoverControllerscheme中?

问题

在我的iPad应用程序中, 只有在按住事件之后,才能将popup窗口附加到button栏项目。 但是这似乎是撤销/重做的标准。 其他应用程序如何做到这一点?

背景

我有我的UIKit(iPad)应用程序工具栏中的撤消button(UIBarButtonSystemItemUndo)。 当我按下撤消button,它会触发它的撤消操作,并正确执行。

然而,在iPad上撤销/重做的“标准UE约定”是按下撤销执行撤消操作,但按住button显示一个popup式控制器,用户在撤销控制器之前select“撤消”或“重做”。

使用presentPopoverFromBarButtonItem来附加popover控制器的正常方法是:我可以很容易地configuration它。 为了让这个只在按下之后才显示,我们必须设置一个视图来响应“长按”手势事件,如下面的代码片段所示:

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]; 

有了这个,在view上按下并按住handleLongPressOnUndoGesture:方法之后,将调用此方法,我将configuration并显示撤销/重做的popup窗口。 到现在为止还挺好。

这个问题是没有意见的 。 self.undoButtonItem是一个UIButtonBarItem,而不是一个视图。

可能的解决scheme

1)[理想] 将手势识别器附加到button栏项目 。 可以将一个手势识别器附加到一个视图,但UIButtonBarItem不是一个视图。 它具有.customView的属性,但是当buttonbaritem是标准系统types(在这种情况下)时,该属性为零。

2) 使用另一个视图 。 我可以使用UIToolbar,但是这需要进行一些奇怪的命中testing,并且如果可能的话,还是可以的。 没有其他可供select的使用方法,我可以想到。

3) 使用customView属性 。 像UIBarButtonSystemItemUndo这样的标准types没有customView(它是零)。 设置customView会删除它需要的标准内容。 这相当于重新实现了UIBarButtonSystemItemUndo的所有外观和function,如果可能的话。

我怎样才能附加一个手势识别器到这个“button”? 更具体地说,我如何在iPad应用程序中实现标准的按下并保持显示的重做popup窗口?

想法? 非常感谢你,特别是如果有人真的有他们的应用程序(我在想你,全向),并希望分享…

代替尝试在工具栏的子视图列表中查找UIBarButtonItem的视图的混乱,您也可以试试这个,一旦项目被添加到工具栏:

 [barButtonItem valueForKey:@"view"]; 

这使用Key-Value Coding框架来访问UIBarButtonItem的private _viewvariables,在那里它保持它创build的视图。

诚然,我不知道这是苹果的私人API的东西(这是用于访问公共类的私有variables的公共方法 – 不喜欢访问私人框架来制作独特的苹果效果或任何东西),但它确实有用,而且非常痛苦。

这是一个古老的问题,但仍然出现在谷歌search,所有其他答案过于复杂。

我有一个button栏,button栏项目,按下时调用一个操作:forEvent:方法。

在该方法中,添加这些行:

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

如果是单击,tapCount就是一个。 如果是双击,tapCount是两个。 如果是长按,tapCount为零。

选项1确实是可能的。 不幸的是,findUIBarButtonItem创build的UIView是件痛苦的事情。 以下是我的发现:

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

这比它应该更困难,但这显然是为了阻止人们用button的外观和感觉来玩弄游戏。

请注意,固定/灵活空间被视为视图!

为了处理空间,你必须有一些方法来检测它们,可悲的是SDK没有简单的方法来做到这一点。 有解决scheme,这里有几个:

1)在工具栏上将UIBarButtonItem的标签值设置为从左到右的索引。 这需要太多的手动工作,以保持同步国际海事组织。

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]; 

当然这有一个潜在的陷阱,如果你禁用某个button的其他原因。

3)手工编写工具栏并自己处理索引。 因为您将自己构buildUIBarButtonItem,所以您将提前知道子视图中的索引。 如果需要的话,可以将这个想法扩展到事先收集UIView以备后用。

而不是摸索子视图,你可以自己创buildbutton,并添加一个自定义视图的button栏项目。 然后,将GR连接到您的自定义button。

虽然这个问题已经过了一年多了,但这仍然是一个相当烦人的问题。 我已经向苹果公司( rdar:// 9982911 )提交了一个错误报告,我build议任何其他感觉相同的人重复。

我尝试了类似于Ben的build议。 我使用UIButton创build了一个自定义视图,并将其用作UIBarButtonItem的自定义视图。 有几件事我不喜欢这种方法:

  • 该button需要被devise成不会像UIToolBar上的拇指那样突出
  • 使用UILongPressGestureRecognizer,我似乎没有得到“Touch up Inside”的单击事件(这很可能是我编写的程序错误)。

相反,我最好的解决办法是啥,但它对我有用。 我使用XCode 4.2,我在下面的代码中使用ARC。 我创build了一个名为CustomBarButtonItemView的新的UIViewController子类。 在CustomBarButtonItemView.xib文件中,我创build了一个UIToolBar,并将一个UIBarButtonItem添加到工具栏中。 然后我将工具栏缩小到几乎button的宽度。 然后,我将文件的所有者视图属性连接到UIToolBar。

CustomBarButtonViewController的Interface Builder视图

然后在我的ViewController的viewDidLoad:消息中,我创build了两个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"); } 

现在当ViewController的barButtonItem(通过xib文件连接)发生单击时,点击手势调用buttonPressed:消息。 如果长按button被按下,则触发。

为了更改UIBarButton的外观,我build议为CustomBarButtonItemView创build一个属性,以允许访问Custom BarButton并将其存储在ViewController类中。 当发送longPressGestured消息时,您可以更改button的系统图标。

我发现的一个问题是customview属性将视图作为原样。 如果您改变CustomBarButtonItemView.xib中的自定义UIBarButtonitem以将标签更改为@“真正的长string” ,例如该button将自行resize,但只有显示的button的最左侧部分位于由UIGestuerRecognizer实例监视的视图中。

我尝试了@ voi1d的解决scheme,它的工作很好,直到我改变了我添加了长按手势的button的标题。 改变标题似乎创build一个新的UIView替代原来的button,从而导致添加的手势停止工作,只要更改button(这经常发生在我的应用程序)。

我的解决scheme是子类UIToolbar并重写addSubview:方法。 我还创build了一个属性,指向我手势的目标。 这是确切的代码:

 - (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]; } } 

在我的特殊情况下,我感兴趣的button是150像素(这是唯一的button),所以这是我使用的testing。 这可能不是最安全的testing,但它适用于我。 显然你必须拿出你自己的testing,并提供你自己的手势和select。

这样做的好处是,任何时候我的UIBarButtonItem改变(从而创build一个新的视图),我的自定义手势被附加,所以它总是工作!

你也可以简单地做到这一点…

 let longPress = UILongPressGestureRecognizer(target: self, action: "longPress:") navigationController?.toolbar.addGestureRecognizer(longPress) func longPress(sender: UILongPressGestureRecognizer) { let location = sender.locationInView(navigationController?.toolbar) println(location) } 

对于那些不想重写UIBarButtonItem的所有function的人UIBarButtonItem @ voi1d的第二个选项答案是最有用的。 我将它包装在一个类别中,以便您可以:

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

如果您有兴趣,请稍等一些error handling。 注意:每次使用setItems添加或删除工具栏中的项目时,都必须重新添加任何手势识别器 – 我猜UIToolbar每次调整items数组时都会重新创build保存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 

我知道这是古老的,但我花了一个晚上,我的头靠在墙上试图find一个可以接受的解决scheme。 我不想使用customView属性,因为会摆脱所有内置的function,如button色调,禁用色彩,长按将受到这样一个小小的打击框,而UIBarButtonItems传播他们的命中盒相当方法。 我想出了这个解决scheme,我觉得它工作得很好,而且实施起来只是轻微的痛苦。

在我的情况下,如果长时间按下,我栏上的前2个button将会到同一个地方,所以我只需要检测到某个button在某个X点之前就已经发生了。 我将长按手势识别器添加到UIToolbar(也可以将它添加到UINavigationBar中),然后在第二个button之后添加一个1像素宽的额外UIBarButtonItem。 当视图加载时,我添加一个UIView,这是一个单一的像素宽UIBarButtonItem,因为它的customView。 现在,我可以testing长按发生的地方,然后看看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") } } 

绝对不是理想的,但足以容易我的用例。 如果你需要指定长按在特定位置的特定button,它会变得更烦人一些,但是你应该能够围绕你需要的button来检测长按薄的垫片,然后检查你的点的X是在这两个垫片之间。

我知道这不是最好的解决scheme,但我会发布一个相当简单的解决scheme,为我工作。

我为UIBarButtonItem创build了一个简单的扩展:

 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) }