如何在iPad应用程序的常见撤销/重做UIPopoverController方案中向UIBarButtonItem添加UIGestureRecognizer?

40

问题

我的iPad应用程序在长按按钮后,无法将弹出窗口附加到按钮栏项上。但这似乎是撤消/重做的标准操作方式。其他应用程序如何实现呢?

背景

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

然而,在iPad上,撤消/重做的“标准UE约定”是按下撤消执行撤消操作,但是如果“按住不放”该按钮,则会显示弹出控制器,用户可以选择“undo”或“redo”,直到控制器被关闭。

附加弹出控制器的正常方式是使用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是标准系统类型(在本例中是)时,该属性为nil。
2) 使用另一个视图。我可以使用UIToolbar,但是那需要进行一些奇怪的命中测试,并且是所有的黑客,即使首先可能也无法实现。我想不到其他可用的替代视图。
3) 使用customView属性。像UIBarButtonSystemItemUndo这样的标准类型没有customView(为nil)。设置customView将删除它需要具有的标准内容。这将相当于重新实现UIBarButtonSystemItemUndo的所有外观和功能,即使可以做到也是如此。
问题:如何将手势识别器附加到此“按钮”上?更具体地说,在iPad应用程序中如何实现标准的按住并保持以显示重做弹出窗口?
想法?非常感谢,特别是如果有人实际上在他们的应用程序中使用了这个(我在想你,omni),并且愿意分享...
15个回答

1

@voi1d的第二个选项答案对于那些不想重写UIBarButtonItem所有功能的人来说是最有用的。我将其包装在一个类别中,这样您就可以只需执行以下操作:

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

带有一些错误处理,以防您感兴趣。注意:每次使用setItems添加或删除工具栏中的项目时,您都必须重新添加任何手势识别器--我猜UIToolbar会在每次调整项目数组时重新创建持有UIView。

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

我的使用情况是在导航项中的UIBarButtonItem...在这种情况下,应该如何调整/使用您的代码? - Reuven
1
你的实现对我(再次)不起作用了,但是thustin2121的实现可以:
  • (void)addGestureRecognizer:(UIGestureRecognizer *)recognizer toBarButton:(UIBarButtonItem *)barButton { UIView *view = [barButton valueForKey:@"view"]; [view addGestureRecognizer:recognizer]; }
- fellowworldcitizen

0

@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 ...
}

0

这是我想到的最适合Swift且最不破坏性的方法。在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
}

0

准备好使用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)
        }
    }
}

0

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

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

1
自iOS11起,value(forKey: "view")将返回nil。唯一的解决方案似乎是实现自定义视图。另请参阅laxman khanal的答案。 - EckhardN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接