【问题标题】:iOS Swift - How to assign a default action to all buttons programmaticallyiOS Swift - 如何以编程方式为所有按钮分配默认操作
【发布时间】:2017-07-18 07:23:21
【问题描述】:

我正在使用处于原型开发阶段的应用。某些界面元素没有通过情节提要或以编程方式分配给它们的任何操作。

根据用户体验指南,我想在应用程序中找到这些“非活动”按钮,并在测试期间点击它们时显示“功能不可用”警报。这可以通过 UIButton 的扩展来实现吗?

除非通过界面构建​​器或以编程方式分配另一个操作,否则如何为 UIButton 分配默认操作以显示警报?

【问题讨论】:

  • 我建议遵循method swizzling的方法。
  • 请查看我的回答。

标签: ios swift uibutton user-experience


【解决方案1】:

你想要达到的目标是可以实现的。我已经使用UIViewController 扩展名完成了这项工作,并添加了一个闭包作为没有目标的按钮的目标。如果按钮没有动作,则会显示警报。

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.checkButtonAction()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

    }
    @IBAction func btn_Action(_ sender: UIButton) {

    }

}

extension UIViewController{
    func checkButtonAction(){
        for view in self.view.subviews as [UIView] {
            if let btn = view as? UIButton {
                if (btn.allTargets.isEmpty){
                    btn.add(for: .touchUpInside, {
                        let alert = UIAlertController(title: "Test 3", message:"No selector", preferredStyle: UIAlertControllerStyle.alert)

                        // add an action (button)
                        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))

                        // show the alert
                        self.present(alert, animated: true, completion: nil)
                    })
                }
            }
        }

    }
}
class ClosureSleeve {
    let closure: ()->()

    init (_ closure: @escaping ()->()) {
        self.closure = closure
    }

    @objc func invoke () {
        closure()
    }
}

extension UIControl {
    func add (for controlEvents: UIControlEvents, _ closure: @escaping ()->()) {
        let sleeve = ClosureSleeve(closure)
        addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
        objc_setAssociatedObject(self, String(format: "[%d]", arc4random()), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    }
}

我已经测试过了。希望这可以帮助。编码愉快。

【讨论】:

    【解决方案2】:

    由于您无法覆盖扩展中的方法,因此剩下的唯一选项是:
    1. 子类化您的按钮 - 但可能不是您想要的,因为我假设您想将此功能用于现有按钮
    2. method swizzling - 改变现有函数的实现,即init

    【讨论】:

    • 如果你最终需要调配,你可能还需要调配initWithCoder,因为这会在界面生成器中添加的按钮被调用。
    • 我在 swizzling 上看到了这个答案,但仍然不知道如何使用该代码将选择器分配给按钮。但是,我对 swift3 的了解不足以使其为按钮分配自定义操作。我该怎么做呢? stackoverflow.com/questions/39562887/…
    【解决方案3】:

    如何为 UIButton 分配默认操作以显示警报,除非 另一个动作是通过界面生成器还是以编程方式分配的?

    我会建议做 method swizzling

    通过swizzling,可以将方法的实现替换为 在运行时改变一个不同的 选择器(方法)和包含其实现的函数。

    https://www.uraimo.com/2015/10/23/effective-method-swizzling-with-swift/

    备注:我建议查看上面的文章。

    作为您问题的确切答案,以下代码 sn-p 应该是-一般而言-您要实现什么:

    class ViewController: UIViewController {
        // MARK:- IBOutlets
        @IBOutlet weak var lblMessage: UILabel!
    
        // MARK:- IBActions
        @IBAction func applySwizzlingTapped(_ sender: Any) {
            swizzleButtonAction()
        }
    
        @IBAction func buttonTapped(_ sender: Any) {
            print("Original!")
            lblMessage.text = "Original!"
        }
    }
    
    extension ViewController {
        func swizzleButtonAction() {
            let originalSelector = #selector(buttonTapped(_:))
            let swizzledSelector = #selector(swizzledAction(_:))
    
            let originalMethod = class_getInstanceMethod(ViewController.self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(ViewController.self, swizzledSelector)
    
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    
        func swizzledAction(_ sender: Any) {
            print("Swizzled!")
            lblMessage.text = "Swizzled!"
        }
    }
    

    调用swizzleButtonAction()后-通过点击“Apply Swizzling”按钮(applySwizzlingTapped)-,“按钮”的选择器应该从buttonTapped变为swizzledAction


    输出:

    【讨论】:

      【解决方案4】:

      我将通过使用 IBOutlet 集合变量来实现这种功能,然后在实现该功能后立即从该集合中删除按钮。像这样:

      class ViewController {
          @IBOutlet var inactiveBtns: [UIButton]!
      
          func viewDidLoad() {
              inactiveBtns.forEach { (button) in
                 button.addTarget(self, action: #selector(showDialog), for: UIControlEvents())
              }
          }
      
          func showDialog() {
              // show the dialog
          }
      }
      

      IBOutlet 在界面构建器中可见,因此您可以在其中添加多个按钮。

      【讨论】:

      • 通过这种方式,我们需要在所有控制器中进行操作,并且如果有任何修改也必须更新。
      • 我考虑过这样的解决方案,但我看不到自己实施它,因为它会触及多个故事板并导致与其他开发人员的合并冲突。
      猜你喜欢
      • 2018-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-22
      • 1970-01-01
      • 2018-01-24
      • 1970-01-01
      • 2015-10-23
      相关资源
      最近更新 更多