如何将 UIGestureRecognizer 添加到 UIBarButtonItem,就像 iPad 应用程序上常见的撤消/重做 UIPopoverController 方案一样?

发布于 2024-08-29 16:32:18 字数 1610 浏览 5 评论 0原文

问题

在我的 iPad 应用程序中,我无法仅在按住事件后将弹出框附加到按钮栏项目。但这似乎是撤消/重做的标准。其他应用程序如何做到这一点?

背景

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

然而,iPad 上撤消/重做的“标准 UE 约定”是,按撤消会执行撤消,但按住按钮会显示一个弹出控制器,用户可以在其中选择“撤消”或“重做”直至管制员被解雇。

附加弹出窗口控制器的正常方法是使用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 属性,但当 Buttonbaritem 是标准系统类型时(在本例中是),该属性为零。

2) 使用另一个视图。我可以使用 UIToolbar,但这需要一些奇怪的命中测试,并且是一个全面的黑客,如果可能的话。我想不出其他可以使用的替代视图。

3) 使用 customView 属性。像 UIBarButtonSystemItemUndo 这样的标准类型没有 customView(它是 nil)。设置customView将删除它需要具有的标准内容。这相当于重新实现 UIBarButtonSystemItemUndo 的所有外观和功能,如果可能的话,再次实现。

问题

如何将手势识别器附加到这个“按钮”?更具体地说,如何在 iPad 应用程序中实现标准的按住以显示重做弹出框?

有想法吗?非常感谢你,特别是如果有人真的在他们的应用程序中使用了这个功能(我在想你,omni)并且想要分享......

Problem

In my iPad app, I cannot attach a popover to a button bar item only after press-and-hold events. But this seems to be standard for undo/redo. How do other apps do this?

Background

I have an undo button (UIBarButtonSystemItemUndo) in the toolbar of my UIKit (iPad) app. When I press the undo button, it fires it's action which is undo:, and that executes correctly.

However, the "standard UE convention" for undo/redo on iPad is that pressing undo executes an undo but pressing and holding the button reveals a popover controller where the user selected either "undo" or "redo" until the controller is dismissed.

The normal way to attach a popover controller is with presentPopoverFromBarButtonItem:, and I can configure this easily enough. To get this to show only after press-and-hold we have to set a view to respond to "long press" gesture events as in this snippet:

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

With this, after a press-and-hold on the view the method handleLongPressOnUndoGesture: will get called, and within this method I will configure and display the popover for undo/redo. So far, so good.

The problem with this is that there is no view to attach to. self.undoButtonItem is a UIButtonBarItem, not a view.

Possible solutions

1) [The ideal] Attach the gesture recognizer to the button bar item. It is possible to attach a gesture recognizer to a view, but UIButtonBarItem is not a view. It does have a property for .customView, but that property is nil when the buttonbaritem is a standard system type (in this case it is).

2) Use another view. I could use the UIToolbar but that would require some weird hit-testing and be an all around hack, if even possible in the first place. There is no other alternative view to use that I can think of.

3) Use the customView property. Standard types like UIBarButtonSystemItemUndo have no customView (it is nil). Setting the customView will erase the standard contents which it needs to have. This would amount to re-implementing all the look and function of UIBarButtonSystemItemUndo, again if even possible to do.

Question

How can I attach a gesture recognizer to this "button"? More specifically, how can I implement the standard press-and-hold-to-show-redo-popover in an iPad app?

Ideas? Thank you very much, especially if someone actually has this working in their app (I'm thinking of you, omni) and wants to share...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

帅哥哥的热头脑 2024-09-05 16:32:18

注意:从 iOS 11 开始这不再有效

为了避免在工具栏的子视图列表中查找 UIBarButtonItem 的视图,您也可以尝试一下,一旦item 添加到工具栏:

[barButtonItem valueForKey:@"view"];

这使用键值编码框架来访问 UIBarButtonItem 的私有 _view 变量,该变量在其中保存它创建的视图。

诚然,我不知道这在苹果的私有 API 方面属于什么位置(这是用于访问公共类的私有变量的公共方法 - 不像访问私有框架来制作奇特的苹果专用效果或任何东西),但是它确实有效,而且相当无痛。

Note: this no longer works as of iOS 11

In lieu of that mess with trying to find the UIBarButtonItem's view in the toolbar's subview list, you can also try this, once the item is added to the toolbar:

[barButtonItem valueForKey:@"view"];

This uses the Key-Value Coding framework to access the UIBarButtonItem's private _view variable, where it keeps the view it created.

Granted, I don't know where this falls in terms of Apple's private API thing (this is public method used to access a private variable of a public class - not like accessing private frameworks to make fancy Apple-only effects or anything), but it does work, and rather painlessly.

岛徒 2024-09-05 16:32:18

这是一个老问题,但它仍然出现在谷歌搜索中,并且所有其他答案都过于复杂。

我有一个带有按钮栏项目的按钮栏,按下时调用 action:forEvent: 方法。

在该方法中,添加以下行:

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

如果是单击,则 tapCount 为 1。如果是双击,则 tapCount 为 2。如果是长按,则 tapCount 为零。

This is an old question, but it still comes up in google searches, and all of the other answers are overly complicated.

I have a buttonbar, with buttonbar items, that call an action:forEvent: method when pressed.

In that method, add these lines:

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

If it was a single tap, tapCount is one. If it was a double tap, tapCount is two. If it's a long press, tapCount is zero.

笑脸一如从前 2024-09-05 16:32:18

选项1确实是可能的。不幸的是,找到 UIBarButtonItem 创建的 UIView 是一件痛苦的事情。我是这样发现的:

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

这比应有的更困难,但这显然是为了阻止人们愚弄按钮的外观和感觉。

请注意,固定/灵活空间算作浏览次数!

为了处理空格,您必须有某种方法来检测它们,遗憾的是 SDK 没有简单的方法来做到这一点。有一些解决方案,以下是其中的一些:

1) 将 UIBarButtonItem 的标记值设置为其在工具栏上从左到右的索引。在我看来,这需要太多的手动工作才能保持同步。

2) 将任何空间的启用属性设置为“否”。然后使用此代码片段为您设置标签值:

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 以供以后使用。

Option 1 is indeed possible. Unfortunately it's a painful thing to find the UIView that the UIBarButtonItem creates. Here's how I found it:

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

This is more difficult than it ought to be, but this is clearly designed to stop people from fooling around with the buttons look and feel.

Note that Fixed/Flexible spaces are not counted as views!

In order to handle spaces you must have some way of detecting them, and sadly the SDK simply has no easy way to do this. There are solutions and here are a few of them:

1) Set the UIBarButtonItem's tag value to it's index from left to right on the toolbar. This requires too much manual work to keep it in sync IMO.

2) Set any spaces' enabled property to NO. Then use this code snippet to set the tag values for you:

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

Of course this has a potential pitfall if you disable a button for some other reason.

3) Manually code the toolbar and handle the indexes yourself. As you'll be building the UIBarButtonItem's yourself, so you'll know in advance what index they'll be in the subviews. You could extend this idea to collecting up the UIView's in advance for later use, if necessary.

逆流 2024-09-05 16:32:18

您可以自己创建按钮并添加带有自定义视图的按钮栏项目,而不用摸索子视图。然后将 GR 连接到您的自定义按钮。

Instead of groping around for a subview you can create the button on your own and add a button bar item with a custom view. Then you hook up the GR to your custom button.

薄荷→糖丶微凉 2024-09-05 16:32:18

虽然这个问题已经存在一年多了,但这仍然是一个非常烦人的问题。我已向 Apple 提交了一份错误报告 (rdar://9982911),我建议其他有相同感觉的人也复制该报告。

While this question is now over a year old, this is still a pretty annoying problem. I've submitted a bug report to Apple (rdar://9982911) and I suggest that anybody else who feels the same duplicate it.

赠我空喜 2024-09-05 16:32:18

您也可以简单地执行此操作...

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

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

You also can simply do this...

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

func longPress(sender: UILongPressGestureRecognizer) {
let location = sender.locationInView(navigationController?.toolbar)
println(location)
}
悲歌长辞 2024-09-05 16:32:18

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

Until iOS 11, let barbuttonView = barButton.value(forKey: "view") as? UIView will give us the reference to the view for barButton in which we can easily add gestures, but in iOS 11 the things are quite different, the above line of code will end up with nil so adding tap gesture to the view for key "view" is meaningless.

No worries we can still add tap gestures to the UIBarItems, since it have a property customView. What we can do is create a button with height & width 24 pt(according to Apple Human Interface Guidelines) and then assign the custom view as the newly created button. The below code will help you perform one action for single tap and another for tapping bar button 5 times.

NOTE For this purpose you must already have a reference to the 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)          
}
与酒说心事 2024-09-05 16:32:18

我尝试了类似于本建议的方法。我使用 UIButton 创建了一个自定义视图,并将其用作 UIBarButtonItem 的自定义视图。对于这种方法,我有一些不喜欢的地方:

  • 按钮的样式需要设计得不会像 UIToolBar 上的拇指酸痛一样突出
  • 使用 UILongPressGestureRecognizer 我似乎没有收到“Touch up Inside”的单击事件“(这可能/很可能是我的编程错误。)

相反,我充其量选择了一些黑客的东西,但它对我有用。我使用的是 XCode 4.2,并且在下面的代码中使用了 ARC。我创建了一个名为 CustomBarButtonItemView 的新 UIViewController 子类。在 CustomBarButtonItemView.xib 文件中,我创建了一个 UIToolBar 并向工具栏添加了一个 UIBarButtonItem。然后我将工具栏缩小到几乎与按钮一样的宽度。然后,我将文件所有者视图属性连接到 UIToolBar。

CustomBarButtonViewController 的界面生成器视图

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

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

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

我发现的一个问题是 customview 属性按原样获取视图。如果您从 CustomBarButtonItemView.xib 更改自定义 UIBarButtonitem 以将标签更改为 @"really long string" 例如,按钮将调整自身大小,但视图中仅显示按钮的最左侧部分由 UIGestuerRecognizer 实例监视。

I tried something similar to what Ben suggested. I created a custom view with a UIButton and used that as the customView for the UIBarButtonItem. There were a couple of things I didn't like about this approach:

  • The button needed to be styled to not stick out like a sore thumb on the UIToolBar
  • With a UILongPressGestureRecognizer I didn't seem to get the click event for "Touch up Inside" (This could/is most likely be programing error on my part.)

Instead I settled for something hackish at best but it works for me. I'm used XCode 4.2 and I'm using ARC in the code below. I created a new UIViewController subclass called CustomBarButtonItemView. In the CustomBarButtonItemView.xib file I created a UIToolBar and added a single UIBarButtonItem to the toolbar. I then shrunk the toolbar to almost the width of the button. I then connected the File's Owner view property to the UIToolBar.

Interface Builder view of CustomBarButtonViewController

Then in my ViewController's viewDidLoad: message I created two UIGestureRecognizers. The first was a UILongPressGestureRecognizer for the click-and-hold and second was UITapGestureRecognizer. I can't seem to properly get the action for the UIBarButtonItem in the view so I fake it with the UITapGestureRecognizer. The UIBarButtonItem does show itself as being clicked and the UITapGestureRecognizer takes care of the action just as if the action and target for the UIBarButtonItem was set.

- (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");
}

Now when a single click occurs in the ViewController's barButtonItem (Connected via the xib file) the tap gesture calls the buttonPressed: message. If the button is held down longPressGestured is fired.

For changing the appearance of the UIBarButton I'd suggest making a property for CustomBarButtonItemView to allow access to the Custom BarButton and store it in the ViewController class. When the longPressGestured message is sent you can change the system icon of the button.

One gotcha I've found is the customview property takes the view as is. If you alter the custom UIBarButtonitem from the CustomBarButtonItemView.xib to change the label to @"really long string" for example the button will resize itself but only the left most part of the button shown is in the view being watched by the UIGestuerRecognizer instances.

友欢 2024-09-05 16:32:18

我尝试了 @voi1d 的解决方案,该解决方案非常有效,直到我更改了添加了长按手势的按钮的标题。更改标题似乎会为按钮创建一个新的 UIView 来替换原始的 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 发生更改(从而创建新视图)时,我的自定义手势都会附加,因此它始终有效!

I tried @voi1d's solution, which worked great until I changed the title of the button that I had added a long press gesture to. Changing the title appears to create a new UIView for the button that replaces the original, thus causing the added gesture to stop working as soon as a change is made to the button (which happens frequently in my app).

My solution was to subclass UIToolbar and override the addSubview: method. I also created a property that holds the pointer to the target of my gesture. Here's the exact code:

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

In my particular situation, the button I'm interested in is 150 pixels across (and it's the only button that is), so that's the test I use. It's probably not the safest test, but it works for me. Obviously you'd have to come up with your own test and supply your own gesture and selector.

The benefit of doing it this way is that any time my UIBarButtonItem changes (and thus creates a new view), my custom gesture gets attached, so it always works!

梦罢 2024-09-05 16:32:18

我知道这已经过时了,但我花了一个晚上把头撞在墙上试图找到一个可接受的解决方案。我不想使用 customView 属性,因为它将摆脱所有内置功能,如按钮色调、禁用色调,并且长按将受到如此小的点击框的影响,而 UIBarButtonItems 将其点击框分散到相当大的范围内方式。我想出了这个解决方案,我认为它非常有效,并且实施起来只是有点痛苦。

就我而言,如果长按,栏上的前 2 个按钮将转到同一个位置,因此我只需要检测按下发生在某个 X 点之前。我将长按手势识别器添加到 UIToolbar(如果将其添加到 UINavigationBar,也可以工作),然后在第二个按钮后面添加一个额外的 1 像素宽的 UIBarButtonItem。当视图加载时,我将一个单像素宽的 UIView 添加到该 UIBarButtonItem,因为它是 customView。现在,我可以测试长按发生的点,然后看看它的 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 是否为在这两个垫片之间。

I know this is old but I spent a night banging my head against the wall trying to find an acceptable solution. I didn't want to use the customView property because would get rid of all of the built in functionality like button tint, disabled tint, and the long press would be subjected to such a small hit box while UIBarButtonItems spread their hit box out quite a ways. I came up with this solution that I think works really well and is only a slight pain to implement.

In my case, the first 2 buttons on my bar would go to the same place if long pressed, so I just needed to detect that a press happened before a certain X point. I added the long press gesture recognizer to the UIToolbar (also works if you add it to a UINavigationBar) and then added an extra UIBarButtonItem that's 1 pixel wide right after the 2nd button. When the view loads, I add a UIView that's a single pixel wide to that UIBarButtonItem as it's customView. Now, I can test the point where the long press happened and then see if it's X is less than the X of the customview's frame. Here's a little 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")
    }
}

Definitely not ideal, but easy enough for my use case. If you need a specify a long press on specific buttons in specific locations, it gets a little more annoying but you should be able to surround the buttons you need to detect the long press on with thin spacers and then just check that your point's X is between both of those spacers.

Oo萌小芽oO 2024-09-05 16:32:18

@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

@voi1d's 2nd option answer is the most useful for those not wanting to rewrite all the functionality of UIBarButtonItem's. I wrapped this in a category so that you can just do:

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

with a little error handling in case you are interested. NOTE: each time you add or remove items from the toolbar using setItems, you will have to re-add any gesture recognizers -- I guess UIToolbar recreates the holding UIViews every time you adjust the items array.

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
清眉祭 2024-09-05 16:32:18

我知道这不是最好的解决方案,但我将发布一个对我有用的相当简单的解决方案。

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

I know it is not the best solution, but I am going to post a rather easy solution that worked for me.

I have created a simple extension for UIBarButtonItem:

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

After this, you can simply add your gesture recognizers to the items in your ViewController's viewDidLoad method:

@IBOutlet weak var myBarButtonItem: UIBarButtonItem!

func setupLongPressObservation() {
    let recognizer = UILongPressGestureRecognizer(
        target: self, action: #selector(self.didLongPressMyBarButtonItem(recognizer:)))
    myBarButtonItem.addGestureRecognizer(recognizer)
}
眼波传意 2024-09-05 16:32:18

@utopians 在 Swift 4.2 中回答

@objc func myAction(_ sender: UIBarButtonItem, forEvent event:UIEvent) {
    let longPressed:Bool = (event.allTouches?.first?.tapCount).map {$0 == 0} ?? false

    ... handle long press ...
}

@utopians answer in Swift 4.2

@objc func myAction(_ sender: UIBarButtonItem, forEvent event:UIEvent) {
    let longPressed:Bool = (event.allTouches?.first?.tapCount).map {$0 == 0} ?? false

    ... handle long press ...
}
灼疼热情 2024-09-05 16:32:18

准备使用UIBarButtonItem子类:

@objc protocol BarButtonItemDelegate {
    func longPress(in barButtonItem: BarButtonItem)
}

class BarButtonItem: UIBarButtonItem {
    @IBOutlet weak var delegate: BarButtonItemDelegate?
    private let button = UIButton(type: .system)

    override init() {
        super.init()
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private func setup() {
        let recognizer = UILongPressGestureRecognizer(
            target: self,
            action: #selector(longPress)
        )
        button.addGestureRecognizer(recognizer)
        button.setImage(image, for: .normal)
        button.tintColor = tintColor
        customView = button
    }

    override var action: Selector? {
        set {
            if let action = newValue {
                button.addTarget(target, action: action, for: .touchUpInside)
            }
        }
        get { return nil }
    }

    @objc private func longPress(sender: UILongPressGestureRecognizer) {
        if sender.state == .began {
            delegate?.longPress(in: self)
        }
    }
}

Ready for use UIBarButtonItem subclass:

@objc protocol BarButtonItemDelegate {
    func longPress(in barButtonItem: BarButtonItem)
}

class BarButtonItem: UIBarButtonItem {
    @IBOutlet weak var delegate: BarButtonItemDelegate?
    private let button = UIButton(type: .system)

    override init() {
        super.init()
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private func setup() {
        let recognizer = UILongPressGestureRecognizer(
            target: self,
            action: #selector(longPress)
        )
        button.addGestureRecognizer(recognizer)
        button.setImage(image, for: .normal)
        button.tintColor = tintColor
        customView = button
    }

    override var action: Selector? {
        set {
            if let action = newValue {
                button.addTarget(target, action: action, for: .touchUpInside)
            }
        }
        get { return nil }
    }

    @objc private func longPress(sender: UILongPressGestureRecognizer) {
        if sender.state == .began {
            delegate?.longPress(in: self)
        }
    }
}
孤君无依 2024-09-05 16:32:18

这是我想出的对 Swift 最友好且最少 hack 的方法。适用于 iOS 12。

Swift 5

var longPressTimer: Timer?

let button = UIButton()
button.addTarget(self, action: #selector(touchDown), for: .touchDown)
button.addTarget(self, action: #selector(touchUp), for: .touchUpInside)
button.addTarget(self, action: #selector(touchCancel), for: .touchCancel)
let undoBarButton = UIBarButtonItem(customView: button)

@objc func touchDown() {
    longPressTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(longPressed), userInfo: nil, repeats: false)
}

@objc func touchUp() {
    if longPressTimer?.isValid == false { return } // Long press already activated
    longPressTimer?.invalidate()
    longPressTimer = nil
    // Do tap action
}

@objc func touchCancel() {
    longPressTimer?.invalidate()
    longPressTimer = nil
}

@objc func longPressed() {
    // Do long press action
}

This is the most Swift-friendly and least hacky way I came up with. Works in iOS 12.

Swift 5

var longPressTimer: Timer?

let button = UIButton()
button.addTarget(self, action: #selector(touchDown), for: .touchDown)
button.addTarget(self, action: #selector(touchUp), for: .touchUpInside)
button.addTarget(self, action: #selector(touchCancel), for: .touchCancel)
let undoBarButton = UIBarButtonItem(customView: button)

@objc func touchDown() {
    longPressTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(longPressed), userInfo: nil, repeats: false)
}

@objc func touchUp() {
    if longPressTimer?.isValid == false { return } // Long press already activated
    longPressTimer?.invalidate()
    longPressTimer = nil
    // Do tap action
}

@objc func touchCancel() {
    longPressTimer?.invalidate()
    longPressTimer = nil
}

@objc func longPressed() {
    // Do long press action
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文