按住 "repeat fire" 的按钮
Press-and-hold button for "repeat fire"
我已经提到了无数关于按住按钮的其他问题,但与 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()
}
}
我不确定我做错了什么,我不知道如何将触摸事件设置到同一个按钮以实现不同的功能。
在我原来的回答中,我回答了如何让按钮同时识别点击和长按的问题。在已澄清的问题中,您似乎希望只要用户按住手指,此按钮就会持续“触发”。如果是这样的话,只需要一个手势识别器。
例如,在 Interface Builder 中,将对象库中的长按手势识别器拖到按钮上,然后将“最短持续时间”设置为零:
然后你可以控制-从长按手势识别器拖到助手编辑器中的代码并添加一个@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
函数),但通过以编程方式显示它更容易说明发生了什么。
您希望在按住按钮时快速连发。
您的 buttonDown
和 buttonUp
方法需要在顶层定义,而不是在另一个函数内部。出于演示目的,放弃从情节提要中连接 @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]
(以捕获两个补全事件)并在初始 buttonDown
上调用 singleFire
进行单次射击。通过这些更改,按下按钮会立即触发,然后只要按住按钮,就会每隔 0.3
秒触发一次。
可以在 Storyboard 中连接按钮,而不是在 viewDidLoad
中进行设置。在这种情况下,将 @IBAction
添加到 buttonDown
和 buttonUp
。然后 Control-单击 Storyboard 中的按钮并从 Touch Down 旁边的圆圈拖动到 func buttonDown
,然后从Touch Up Inside 和 Touch Up Outside 旁边的圆圈到 func buttonUp
.
我将@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])
}
我在提出自己的解决方案时采用了不同的方法。我创建了一个 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
}
您可以使用定时器
在 github
上遵循此示例
Swift 5+
根据 rob 的回答,现在有更好的方法可以做到这一点。
添加长按手势识别器,方法是将其拖动到情节提要中的按钮顶部,然后...
Then you can control-drag from the long press gesture recognizer to
your code in the assistant editor and add an @IBAction to handle the
long press:
- Quote from Rob's Answer
区别在于下面列出的代码:
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,您只需将定时器的代码放入块更紧凑,不需要选择器。
在我的例子中,我有另一个函数来处理当按下向下按钮时要做什么的逻辑,但是你可以把你想要处理的代码放在那里。
您可以通过更改 withTimeInterval 参数值来控制 连发 的速度。
您可以通过在情节提要中找到 longPressGesture 并更改它的 Min Duration 值来更改计时器的启动时间。我通常将我的设置为 0.5,以便您的正常按钮按下操作仍然有效(除非您不关心那个)。 如果您将其设置为 0,这将始终覆盖您的正常按钮按下操作。
我已经提到了无数关于按住按钮的其他问题,但与 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()
}
}
我不确定我做错了什么,我不知道如何将触摸事件设置到同一个按钮以实现不同的功能。
在我原来的回答中,我回答了如何让按钮同时识别点击和长按的问题。在已澄清的问题中,您似乎希望只要用户按住手指,此按钮就会持续“触发”。如果是这样的话,只需要一个手势识别器。
例如,在 Interface Builder 中,将对象库中的长按手势识别器拖到按钮上,然后将“最短持续时间”设置为零:
然后你可以控制-从长按手势识别器拖到助手编辑器中的代码并添加一个@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
函数),但通过以编程方式显示它更容易说明发生了什么。
您希望在按住按钮时快速连发。
您的 buttonDown
和 buttonUp
方法需要在顶层定义,而不是在另一个函数内部。出于演示目的,放弃从情节提要中连接 @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]
(以捕获两个补全事件)并在初始 buttonDown
上调用 singleFire
进行单次射击。通过这些更改,按下按钮会立即触发,然后只要按住按钮,就会每隔 0.3
秒触发一次。
可以在 Storyboard 中连接按钮,而不是在 viewDidLoad
中进行设置。在这种情况下,将 @IBAction
添加到 buttonDown
和 buttonUp
。然后 Control-单击 Storyboard 中的按钮并从 Touch Down 旁边的圆圈拖动到 func buttonDown
,然后从Touch Up Inside 和 Touch Up Outside 旁边的圆圈到 func buttonUp
.
我将@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])
}
我在提出自己的解决方案时采用了不同的方法。我创建了一个 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
}
您可以使用定时器
在 github
上遵循此示例Swift 5+
根据 rob 的回答,现在有更好的方法可以做到这一点。
添加长按手势识别器,方法是将其拖动到情节提要中的按钮顶部,然后...
Then you can control-drag from the long press gesture recognizer to your code in the assistant editor and add an @IBAction to handle the long press: - Quote from Rob's Answer
区别在于下面列出的代码:
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,您只需将定时器的代码放入块更紧凑,不需要选择器。
在我的例子中,我有另一个函数来处理当按下向下按钮时要做什么的逻辑,但是你可以把你想要处理的代码放在那里。
您可以通过更改 withTimeInterval 参数值来控制 连发 的速度。
您可以通过在情节提要中找到 longPressGesture 并更改它的 Min Duration 值来更改计时器的启动时间。我通常将我的设置为 0.5,以便您的正常按钮按下操作仍然有效(除非您不关心那个)。 如果您将其设置为 0,这将始终覆盖您的正常按钮按下操作。