29

我已经提到了无数其他关于按住按钮的问题,但与 Swift 相关的问题并不多。我有一个使用 touchUpInside 事件连接到按钮的功能:

@IBAction func singleFire(sender: AnyObject){
    //code
}

...以及另一个函数,该函数旨在在按住同一个按钮时重复调用上述函数,并在不再按下按钮时停止:

@IBAction func speedFire(sender: AnyObject){

    button.addTarget(self, action: "buttonDown:", forControlEvents: .TouchDown)
    button.addTarget(self, action: "buttonUp:", forControlEvents: .TouchUpOutside)

    func buttonDown(sender: AnyObject){
        timer = NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: "singleFire", userInfo: nil, repeats: true)     
    }

    func buttonUp(sender: AnyObject){
        timer.invalidate()
    }
}

我不确定自己做错了什么,也不知道如何为同一个按钮设置触摸事件以实现不同的功能。

4

6 回答 6

45

当您的按钮被按住时,您想要快速重复射击。

您的buttonDownandbuttonUp方法需要在顶层定义,而不是在另一个函数内部。出于演示目的,更清楚的是放弃@IBAction从情节提要中连接 s 并只需在 中设置按钮viewDidLoad

class ViewController: UIViewController {

    @IBOutlet weak var button: UIButton!
    var timer: Timer?
    var speedAmmo = 20

    @objc func buttonDown(_ sender: UIButton) {
        singleFire()
        timer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(rapidFire), userInfo: nil, repeats: true)
    }

    @objc func buttonUp(_ sender: UIButton) {
        timer?.invalidate()
    }

    func singleFire() {
        print("bang!")
    }

    @objc func rapidFire() {
        if speedAmmo > 0 {
            speedAmmo -= 1
            print("bang!")
        } else {
            print("out of speed ammo, dude!")
            timer?.invalidate()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // These could be added in the Storyboard instead if you mark
        // buttonDown and buttonUp with @IBAction
        button.addTarget(self, action: #selector(buttonDown), for: .touchDown)
        button.addTarget(self, action: #selector(buttonUp), for: [.touchUpInside, .touchUpOutside])
    }
}

此外,我更改.touchUpOutside[.touchUpInside, .touchUpOutside](捕捉两个修饰事件)并调用singleFire初始buttonDown单火。通过这些更改,按下按钮会立即触发,然后0.3只要按住按钮,就会每秒钟触发一次。


该按钮可以在 Storyboard 中连接,而不是在viewDidLoad. 在这种情况下,@IBAction添加buttonDownbuttonUp。然后Control- 单击情节提要中的按钮并从Touch Down旁边的圆圈拖动到func buttonDown,然后从Touch Up InsideTouch Up Outside旁边的圆圈拖动到func buttonUp

在此处输入图像描述

于 2015-12-12T04:03:02.103 回答
23

在我最初的答案中,我回答了如何让按钮同时识别轻按和长按的问题。在澄清的问题中,只要用户按住手指,您似乎希望此按钮持续“触发”。如果是这种情况,则只需要一个手势识别器。

例如,在 Interface Builder 中,将对象库中的长按手势识别器拖到按钮上,然后将“Min Duration”设置为零:

在此处输入图像描述

然后,您可以control从长按手势识别器 - 拖动到助手编辑器中的代码并添加一个@IBAction来处理长按:

weak var timer: Timer?

@IBAction func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
    if gesture.state == .began {
        timer?.invalidate()
        timer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { [weak self] timer in
            guard let self = self else {
                timer.invalidate()
                return
            }
            self.handleTimer(timer)
        }
    } else if gesture.state == .ended || gesture.state == .cancelled {
        timer?.invalidate()
    }
}

func handleTimer(_ timer: Timer) {
    print("bang")
}

或者,如果您还想在用户将手指从按钮上移开时停止触发,请添加对手势位置的检查:

@IBAction func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
    if gesture.state == .began {
        timer?.invalidate()
        timer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { [weak self] timer in
            guard let self = self else {
                timer.invalidate()
                return
            }
            self.handleTimer(timer)
        }
    } else if gesture.state == .ended || gesture.state == .cancelled || (gesture.state == .changed && !gesture.view!.bounds.contains(gesture.location(in: gesture.view))) {
        timer?.invalidate()
    }
}

我的原始答案,回答了如何识别按钮上的点击和长按的不同问题,如下所示:


就个人而言,我会使用点击和长按手势识别器,例如:

override func viewDidLoad() {
    super.viewDidLoad()

    let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
    button.addGestureRecognizer(longPress)

    let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
    tap.shouldRequireFailure(of: longPress)
    button.addGestureRecognizer(tap)
}

@objc func handleTap(_ gesture: UITapGestureRecognizer) {
    print("tap")
}

@objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
    if gesture.state == .Began {
        print("long press")
    }
}

如果您愿意,也可以通过长按手势在 上执行您的操作.Ended。它仅取决于所需的用户体验。

仅供参考,您也可以直接在 Interface Builder 中添加这两个手势识别器(只需将相应的手势从对象库拖到按钮上,然后control从手势识别器拖到@IBAction函数),但更容易说明发生了什么通过以编程方式显示它。

于 2015-12-12T03:57:20.977 回答
4

在提出自己的解决方案时,我采用了不同的方法。我创建了一个 UIButton 子类并将所有解决方案包含在其中。不同之处在于,我没有为处理程序使用 IBAction,而是在 UIButton 中创建了一个属性

class RapidFireButton: UIButton {

    private var rapidFireTimer: Timer?
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

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

    init() {
        super.init(frame: .zero)
        commonInit()
    }

    private func commonInit() {
        addTarget(self, action: #selector(touchDownHandler), for: .touchDown)
        addTarget(self, action: #selector(touchUpHandler), for: .touchUpOutside)
        addTarget(self, action: #selector(touchUpHandler), for: .touchUpInside)
    }

    @objc private func touchDownHandler() {
        rapidFireTimer?.invalidate()
        rapidFireTimer = nil
        tapHandler(self)
        rapidFireTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [unowned self] (timer) in
            self.tapHandler(self)
        })
    }

    @objc private func touchUpHandler() {
        rapidFireTimer?.invalidate()
        rapidFireTimer = nil
    }

    var tapHandler: (RapidFireButton) -> Void = { button in

    } 
}

用法基本上是为按钮创建一个出口并像这样实现处理程序

rapidFireButton.tapHandler = { button in
    //do stuff
}
于 2019-05-30T09:36:50.190 回答
2

斯威夫特 5+

根据 rob 的回答,现在有一种更好的方法可以做到这一点。

通过将长按手势识别器拖动到情节提要中的按钮顶部来添加长按手势识别器,然后...

然后,您可以从长按手势识别器控制并拖动到助手编辑器中的代码,并添加一个@IBAction 来处理长按: - 引用 Rob 的回答

不同之处在于下面列出的代码:

var timer: Timer?

@IBAction func downButtonLongPressHandler(_ sender: UILongPressGestureRecognizer) {
        if sender.state == .began {
            timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true, block: {_ in
                self.downButtonPressedLogic()
                self.doCalculations()
            })
        } else if sender.state == .ended || sender.state == .cancelled {
            print("FINISHED UP LONG PRESS")
            timer?.invalidate()
            timer = nil
        }
}

不再需要使用 NSTimer,您现在可以使用Timer并且您可以将计时器的代码放在更紧凑且不需要选择器的块中。

在我的例子中,我有另一个函数来处理按下 downButton 时要做什么的逻辑,但是你可以把你想要处理的代码放在那里。

您可以通过更改withTimeInterval参数值来控制重复射击的速度。

您可以通过在情节提要中找到 longPressGesture 并更改它的Min Duration值来更改计时器开始的时间。我通常将我的设置为0.5,这样您的正常按钮按下操作仍然可以工作(除非您不在乎)。 如果您将其设置为 0,这将始终覆盖您的正常按钮按下操作。

于 2020-02-07T20:59:19.423 回答
2

我将@vacawama 示例代码更新为 swift 3。谢谢。

@IBOutlet var button: UIButton!

var timer: Timer!
var speedAmmo = 100

@IBAction func buttonDown(sender: AnyObject) {
    singleFire()
    timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector:#selector(rapidFire), userInfo: nil, repeats: true)
}

@IBAction func buttonUp(sender: AnyObject) {
    timer.invalidate()
}

func singleFire() {
    if speedAmmo > 0 {
        speedAmmo -= 1
        print("bang!")
    } else {
        print("out of speed ammo, dude!")
        timer.invalidate()
    }
}

func rapidFire() {
    if speedAmmo > 0 {
        speedAmmo -= 1
        print("bang!")
    } else {
        print("out of speed ammo, dude!")
        timer.invalidate()
    }
}

override func viewDidLoad() {
    super.viewDidLoad()

    button.addTarget(self, action:#selector(buttonDown(sender:)), for: .touchDown)
    button.addTarget(self, action:#selector(buttonUp(sender:)), for: [.touchUpInside, .touchUpOutside])
}
于 2017-01-17T08:57:38.173 回答
0

您可以使用计时器

按照 github 上的这个例子

https://github.com/sadeghgoo/RunCodeWhenUIbuttonIsHeld

于 2019-12-08T07:39:48.847 回答