web-dev-qa-db-fra.com

Appuyez et maintenez le bouton pour "répétez le feu"

J'ai évoqué d'innombrables autres questions sur un bouton-poussoir mais il n'y en a pas beaucoup qui soient liées à Swift. Une fonction est connectée à un bouton à l'aide de l'événement touchUpInside:

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

... et une autre fonction destinée à appeler la fonction ci-dessus plusieurs fois lorsque le même bouton est maintenu enfoncé et à s'arrêter lorsque le bouton n'est plus enfoncé:

@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()
    }
}

Je ne sais pas trop ce que je fais de mal et je ne sais pas comment configurer les événements tactiles sur le même bouton pour une fonction différente.

19
Tim

Vous voulez une répétition rapide du tir lorsque votre bouton est maintenu enfoncé.

Vos méthodes buttonDownet buttonUpdoivent être définies au niveau supérieur, et non à l'intérieur d'une autre fonction. À des fins de démonstration, il est plus facile de ne pas connecter @IBActions à partir du storyboard et de configurer le bouton dans viewDidLoadname__:

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])
    }
}

De plus, j'ai remplacé .touchUpOutside par [.touchUpInside, .touchUpOutside] (pour intercepter les deux retouches) et appelé singleFiresur le buttonDowninitial pour un tir unique. Avec ces modifications, une pression sur le bouton se déclenche immédiatement, puis toutes les 0.3 secondes tant que le bouton est maintenu enfoncé.


Le bouton peut être connecté au Storyboard au lieu de le configurer dans viewDidLoadname__. Dans ce cas, ajoutez @IBAction à buttonDownet buttonUpname__. ensuite Control-Cliquez sur votre bouton dans le scénarimage et faites-le glisser du cercle situé à côté de . Appuyez vers le bas jusqu'à func buttonDown, puis faites-le glisser des cercles situé à côté de Retouchez à l'intérieur et Retouchez l'extérieur vers func buttonUp.

enter image description here

32
vacawama

Dans ma réponse initiale, j'ai répondu à la question de savoir comment un bouton reconnaît à la fois un appui long et un appui long. Dans la question clarifiée, il semble que vous souhaitiez que ce bouton "se déclenche" continuellement tant que l'utilisateur maintient son doigt appuyé. Si tel est le cas, un seul identificateur de geste est nécessaire.

Par exemple, dans Interface Builder, faites glisser un outil de reconnaissance des gestes appuyés depuis la bibliothèque d'objets vers le bouton, puis définissez la "Durée minimale" sur zéro:

enter image description here

Ensuite vous pouvez control-Faites glisser la reconnaissance de geste de presse longue vers votre code dans l'éditeur adjoint et ajoutez un @IBAction pour gérer la pression longue:

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

Ou, si vous souhaitez également arrêter de tirer lorsque l'utilisateur fait glisser son doigt hors du bouton, cochez la case suivante pour afficher l'emplacement du geste:

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

Ma réponse initiale, répondant à la question différente de savoir comment reconnaître à la fois les tapotements et les appuis prolongés sur un bouton, est la suivante:


Personnellement, j'utiliserais des outils de reconnaissance de gestes au toucher et aux appuis longs, par exemple

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

Si vous le souhaitez, vous pouvez également appuyer sur .Ended en effectuant un appui long. Cela dépend de l'UX souhaitée.

Pour votre information, vous pouvez également ajouter ces deux dispositifs de reconnaissance de mouvements directement dans Interface Builder (il suffit de faire glisser les mouvements respectifs de la bibliothèque d'objets vers le bouton, puis control-fait glisser de la reconnaissance de geste vers les fonctions @IBAction), mais il était plus facile d'illustrer ce qui se passait en le montrant par programme.

16
Rob

J'ai mis à jour les exemples de codes @vacawama vers Swift 3. Merci.

@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])
}
1
fozoglu