【问题标题】:Swift UIButton: Make a button that you need to press three timesSwift UIButton:制作一个需要按三下的按钮
【发布时间】:2020-11-22 18:59:27
【问题描述】:

我的问题:

是否可以将 UIButton 子类化为在实际调用该函数之前必须按三下按钮?

上下文

我是一名裁判,所以在比赛期间我需要记录剩余时间和两支球队的比分。因为我厌倦了同时使用纸和秒表,所以我决定制作一个应用程序来为我处理它。毕竟我是程序员,为什么不呢。

由于始终将手机放在手中是不切实际的,因此我总是将手机(运行应用程序的手机)放在口袋里。我只在需要更改分数或发出哔哔声(表示时间到了)时才将其取出。为了防止我的手机在口袋里时意外按下其中一个按钮,我确保您必须连续按三次按钮以确保您真的打算按下它。我通过声明变量来跟踪我在屏幕上的每个按钮在最后一秒被按下的次数来做到这一点。但这也意味着我必须拥有与屏幕上按钮数量一样多的变量,并且在调用函数时,我首先必须检查之前按下了多少次以确定是否执行代码。它有效,但我最终得到了一些非常混乱的代码。我希望通过子类化 UIButton 可以做得更好。

【问题讨论】:

  • 这能回答你的问题吗? how to init a UIButton subclass?
  • @nvidot 我看过那个帖子。我确实了解子类化的工作原理,因此我知道如何添加变量和覆盖初始化程序。我不知道如何使连接到按钮的 IBAction 仅在您连续按下该按钮 3 次后才被调用。
  • 在动作/方法中放置一个计数器。
  • @Magnas 好吧,我想我已经做到了你的意思(见上下文)。它有效,但它真的很乱。
  • 拥有一个带有一个按钮的前置“锁定”屏幕不是更好吗?在解锁 viewController 以显示所有控制按钮之前的两秒长点击手势?

标签: ios swift uibutton


【解决方案1】:

如果你想避免混乱的代码,一种选择是从 UIKit 继承 UIButton 并实现@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
        }
    }
}

【讨论】:

    【解决方案2】:

    这很棘手,但可行。这是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 以编程方式创建按钮。

    1. 在情节提要按钮的Identity Inspector中将类更改为ThreeTapButton

    这是一个故事板按钮的示例:

    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 事件相关。


    限制:

    这是这个问题的基本第一次尝试。它有以下限制:

    • 它只适用于.touchUpInside。如果您愿意,可以轻松地将其更改为另一个活动。
    • 硬编码以查找 3 个抽头。这可以更笼统。

    【讨论】:

    • 控制器可以将 ThreeTagHelper 设置为按钮的“正常”动作处理程序。休息不会改变,但它适用于没有任何子类的所有控件,不是吗?但除了那个 id 做某事也喜欢它
    • 为了避免 ivar,对象可以使用关联存储;)我们甚至不需要指针,所以它纯粹是为了让它保持活动状态
    • @Daij-Djan,我认为我们需要子类才能使其适用于 Storyboard 按钮,对吧?你能告诉我你所说的关联存储是什么意思吗?除了消除ivar,它还有什么优势?
    • 哦,是的,对于故事板。我有一段时间没用过:D 是有道理的。 :)
    【解决方案3】:

    我会简单地尝试以下,甚至没有子类化。 我会使用每个按钮的标签来复用 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,就在这里。
    • 将标签重置为 timeElapsed(十分之一秒)(nbTaps 现在为零)
    • 执行您需要的 IBAction。

    【讨论】:

      猜你喜欢
      • 2021-03-25
      • 1970-01-01
      • 2012-03-08
      • 2015-09-18
      • 1970-01-01
      • 1970-01-01
      • 2021-09-26
      • 2018-10-29
      • 2023-03-10
      相关资源
      最近更新 更多