Swift UIButton: сделайте кнопку, которую нужно нажать три раза - PullRequest
0 голосов
/ 02 августа 2020

Мой вопрос:

Можно ли создать подкласс UIButton таким образом, чтобы вам приходилось нажимать кнопку три раза, прежде чем он фактически вызовет функцию?

Контекст

Я рефери, поэтому во время матча мне нужно следить за оставшимся временем и счетами обеих команд. Поскольку мне надоело использовать и бумагу, и секундомер, я решил сделать приложение, которое справилось бы с этим за меня. В конце концов, я программист, так почему бы и нет.

Так как держать телефон в руках все время непрактично, я всегда кладу телефон (на котором запущено приложение) в карман. Я вынимаю его только тогда, когда мне нужно изменить счет или когда он издает звуковой сигнал (сигнализируя, что время истекло). Чтобы предотвратить случайное нажатие одной из кнопок на моем телефоне, когда он находится в моем кармане, я убедился, что вам нужно нажимать кнопки три раза подряд, чтобы убедиться, что вы действительно собираетесь нажать на нее. Я сделал это, объявив переменные, отслеживающие, сколько раз она была нажата за последнюю секунду для каждой кнопки на экране. Но это также означает, что мне нужно иметь столько дополнительных переменных, сколько кнопок на экране, и когда функции вызываются, я сначала должен проверить, сколько раз они были нажаты, прежде чем определить, выполнять ли код или нет. Это работает, но у меня получился действительно беспорядочный код. Я надеялся, что это можно сделать лучше, создав подкласс UIButton.

Ответы [ 3 ]

2 голосов
/ 02 августа 2020

Если вы хотите избежать запутанного кода, один из вариантов - создать подкласс UIButton из UIKit и реализовать механизм обнаружения трех нажатий в строке, предоставляемый @ vacawama.

Если вы хотите, чтобы это было просто, и поскольку вам нужно отслеживать только последнюю кнопку, для которой произошло 3 нажатия, тогда вам понадобится только один счетчик, связанный с кнопкой, для которой вы подсчитываете, и с последним нажатием.

Как только изменение кнопки или временной интервал слишком длинный, отслеживаемая кнопка меняется, и счетчик возвращается к 1.

Когда обнаруживается ряд из трех нажатий на одну и ту же кнопку, вы запускаете событие в направлении исходной цели и сбросить счетчик на 0.

import UIKit

class UIThreeTapButton : UIButton {

    private static var sender: UIThreeTapButton?
    private static var count = 0
    private static var lastTap = Date.distantPast

    private var action: Selector?
    private var target: Any?

    override func addTarget(_ target: Any?,
                            action: Selector,
                            for controlEvents: UIControl.Event) {
        if controlEvents == .touchUpInside {
            self.target = target
            self.action = action
            super.addTarget(self,
                            action: #selector(UIThreeTapButton.checkForThreeTaps),
                            for: controlEvents)
        } else {
            super.addTarget(target, action: action, for: controlEvents)
        }

    }

    @objc func checkForThreeTaps(_ sender: UIThreeTapButton, forEvent event: UIEvent) {
        let now = Date()
        if UIThreeTabButton.sender == sender &&
            now.timeIntervalSince(UIThreeTapButton.lastTap) < 0.5 {
            UIThreeTapButton.count += 1
            if UIThreeTapButton.count == 3 {
                _ = (target as? NSObject)?.perform(action, with: sender, with: event)
                UIThreeTapButton.count = 0
                UIThreeTapButton.lastTap = .distantPast
            }
        } else {
            UIThreeTapButton.sender = sender
            UIThreeTapButton.lastTap = now
            UIThreeTapButton.count = 1
        }
    }
}
1 голос
/ 02 августа 2020

Это сложно, но выполнимо. Вот реализация ThreeTapButton, являющаяся подклассом UIButton. Он обеспечивает специальную обработку события .touchUpInside, передав его объекту ThreeTapHelper. Все остальные события передаются в суперкласс UIButton.

// This is a helper object used by ThreeTapButton
class ThreeTapHelper {
    private var target: Any?
    private var action: Selector?
    private var count = 0
    private var lastTap = Date.distantPast


    init(target: Any?, action: Selector?) {
        self.target = target
        self.action = action
    }

    @objc func checkForThreeTaps(_ sender: ThreeTapButton, forEvent event: UIEvent) {
        let now = Date()
        if now.timeIntervalSince(lastTap) < 0.5 {
            count += 1
            if count == 3 {
                // Three taps in a short time have been detected.
                // Call the original Selector, forward the original
                // sender and event.
                _ = (target as? NSObject)?.perform(action, with: sender, with: event)
    
                count = 0
                lastTap = .distantPast
            }
        } else {
            lastTap = now
            count = 1
        }
    }
}
    

class ThreeTapButton: UIButton {
    private var helpers = [ThreeTapHelper]()
    
    // Intercept `.touchUpInside` when it is added to the button, and
    // have it call the helper instead of the user's provided Selector
    override func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event) {
        if controlEvents == .touchUpInside {
            let helper = ThreeTapHelper(target: target, action: action)
            helpers.append(helper)
            super.addTarget(helper, action: #selector(ThreeTapHelper.checkForThreeTaps), for: controlEvents)
        } else {
            super.addTarget(target, action: action, for: controlEvents)
        }
    }
}

Чтобы использовать это, либо:

  1. Используйте ThreeTapButton вместо UIButton в вашем коде для программно созданных кнопок.

или

Измените класс на ThreeTapButton в Identity Inspector для кнопок раскадровки.

Вот пример кнопки раскадровки:

class ViewController: UIViewController {

    @IBAction func tapMe3(_ sender: ThreeTapButton) {
        print("tapped 3 times")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

}

В этом случае @IBAction был связан с событием .touchUpInside.

Ограничения:

Это основная c первая попытка решить эту проблему. Он имеет следующие ограничения:

  • Работает только с .touchUpInside. Вы можете легко изменить это на другое событие, если хотите.
  • Это жестко запрограммировано для поиска 3 нажатий. Это можно было бы сделать более общим.
0 голосов
/ 02 августа 2020

Я бы просто попробовал следующее, даже не создавая подклассы. Я бы использовал тег каждой кнопки, чтобы мультиплексировать 2 информации: количество повторений нажатий, время с момента последнего нажатия.

Мультиплексированное значение может быть 1000_000 * nbTaps + время, прошедшее (измеряемое в десятых долях секунды) с начала воспроизведения (это будет работать более одного дня).

Таким образом, второе нажатие через 10 минут после начала воспроизведения установит тег на 2000_000 + 6000 = 2006_000 При создании кнопок (в начале воспроизведения , в viewDidLoad), установите для их тега значение 0.

В IBAction

  • демультиплексирует тег, чтобы найти nbTaps и timeOfLastTap
  • вычислить новое значение timeElapsed (в десятых долях секунды)
  • , если разница с timeOfLastTap больше 10 (1 секунда), сбросить тег на 1000_000 + timeElapsed (так что nbTaps равно 1) и вернуть
  • , если разница меньше 10, проверьте nbTaps (как хранится в теге)
  • если меньше 2, тогда просто сбросьте тег на (nbTaps + 1) * 1000_000 + timeElapsed и верните
  • , если nbTaps> = 2, h
  • сбросить тег на timeElapsed (в десятых долях секунды) (nbTaps теперь равен нулю)
  • выполните необходимое действие IBAction.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...