按下"重复按下"

时间:2015-12-12 03:28:16

标签: ios swift nstimer addtarget

我已经提到了关于按下按钮的无数其他问题,但是没有很多与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()
    }
}

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

6 个答案:

答案 0 :(得分:35)

按住按钮时,您希望快速重复射击。

您的buttonDownbuttonUp方法需要在顶层定义,而不是在另一个函数内定义。出于演示目的,放弃从故事板中连接@IBAction并在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秒触发一次。

按钮可以在故事板中连接,而不是在viewDidLoad中设置。在这种情况下,请将@IBAction添加到buttonDownbuttonUp。然后控制 - 点击故事板中的按钮,然后从触摸旁边的圆圈拖到func buttonDown,然后从旁边的圆圈拖动触摸内部触摸外部func buttonUp

enter image description here

答案 1 :(得分:17)

在我原来的回答中,我回答了如何让按钮识别水龙头和长按的问题。在澄清的问题中,您似乎希望此按钮能够持续不断地消防"只要用户握住他们的手指。如果是这种情况,则只需要一个手势识别器。

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

enter image description here

然后你可以从长按手势识别器控制 -drag到助手编辑器中的代码并添加@IBAction来处理长按:

var timer: NSTimer?

@IBAction func longPressHandler(gesture: UILongPressGestureRecognizer) {
    if gesture.state == .Began {
        timer = NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: "handleTimer:", userInfo: nil, repeats: true)
    } else if gesture.state == .Ended || gesture.state == .Cancelled {
        timer?.invalidate()
        timer = nil
    }
}

func handleTimer(timer: NSTimer) {
    NSLog("bang")
}

或者,如果您还希望在用户将手指拖离按钮时停止触发,请添加对手势位置的检查:

@IBAction func longPressHandler(gesture: UILongPressGestureRecognizer) {
    if gesture.state == .Began {
        timer = NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: "handleTimer:", userInfo: nil, repeats: true)
    } else if gesture.state == .Ended || gesture.state == .Cancelled || (gesture.state == .Changed && !CGRectContainsPoint(gesture.view!.bounds, gesture.locationInView(gesture.view))) {
        timer?.invalidate()
        timer = nil
    }
}

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

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

override func viewDidLoad() {
    super.viewDidLoad()

    let longPress = UILongPressGestureRecognizer(target: self, action: "handleLongPress:")
    button.addGestureRecognizer(longPress)

    let tap = UITapGestureRecognizer(target: self, action: "handleTap:")
    tap.requireGestureRecognizerToFail(longPress)
    button.addGestureRecognizer(tap)
}

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

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

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

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

答案 2 :(得分: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,则它​​将始终覆盖您的常规按钮按下操作。

答案 3 :(得分:1)

我将@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])
}

答案 4 :(得分:0)

在提出自己的解决方案时,我采用了不同的方法。我创建了一个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
}

答案 5 :(得分:0)

您可以使用计时器

在github上遵循此示例

https://github.com/sadeghgoo/RunCodeWhenUIbuttonIsHeld