iOS introduction


Exemple

Généralement, lorsque vous utilisez UIControl ou UIButton , nous ajoutons un selector tant qu'action de rappel lorsqu'un événement se produit sur un bouton ou un contrôle, par exemple lorsque l'utilisateur appuie sur le bouton ou touche le contrôle.

Par exemple, nous ferions ce qui suit:

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var button: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 44))
        button.addTarget(self, action: #selector(self.onButtonPress(_:)), for: .touchUpInside)
        self.view.addSubview(button)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    func onButtonPress(_ button: UIButton!) {
        print("PRESSED")
    }
}

En ce qui concerne le selector , le compilateur doit seulement savoir qu'il existe. Cela peut être fait via un protocol et ne pas être implémenté.

Par exemple, ce qui suit planterait votre application:

import UIKit

@objc
protocol ButtonEvent {
    @objc optional func onButtonPress(_ button: UIButton)
}

class ViewController: UIViewController, ButtonEvent {
    @IBOutlet weak var button: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 44))
        button.addTarget(self, action: #selector(ButtonEvent.onButtonPress(_:)), for: .touchUpInside)
        self.view.addSubview(button)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

C'est parce que votre application onButtonPress PAS la fonction onButtonPress .

Maintenant, si vous pouviez faire tout cela en même temps que l'initialisation du bouton? Que faire si vous n'aviez pas à spécifier de rappels et que vous pouviez spécifier des blocs pouvant être ajoutés et supprimés à tout moment? Pourquoi se soucier de la mise en œuvre des sélecteurs?


Solution

import Foundation
import UIKit

protocol RemovableTarget {
    func enable();
    func disable();
}

extension UIControl {
    func addEventHandler(event: UIControlEvents, runnable: (control: UIControl) -> Void) -> RemovableTarget {
        
        class Target : RemovableTarget {
            private var event: UIControlEvents
            private weak var control: UIControl?
            private var runnable: (control: UIControl) -> Void
            
            private init(event: UIControlEvents, control: UIControl, runnable: (control: UIControl) -> Void) {
                self.event = event
                self.control = control
                self.runnable = runnable
            }
            
            @objc
            private func run(_ control: UIControl) {
                runnable(control: control)
            }
            
            private func enable() {
                control?.addTarget(self, action: #selector(Target.run(_:)), for: event)
                objc_setAssociatedObject(self, unsafeAddress(of: self), self, .OBJC_ASSOCIATION_RETAIN)
            }
            
            private func disable() {
                control?.removeTarget(self, action: #selector(Target.run(_:)), for: self.event)
                objc_setAssociatedObject(self, unsafeAddress(of: self), nil, .OBJC_ASSOCIATION_ASSIGN)
            }
        }
        
        let target = Target(event: event, control: self, runnable: runnable)
        target.enable()
        return target
    }
}

Ce qui précède est une simple extension sur UIControl . Il ajoute une classe privée interne qui a une func run(_ control: UIControl) rappel func run(_ control: UIControl) utilisée comme action des événements.

Ensuite, nous utilisons l' object association pour ajouter et supprimer la cible car elle ne sera pas conservée par UIControl .

La fonction de gestionnaire d'événements renvoie un Protocol afin de masquer le fonctionnement interne de la classe Target , mais également pour vous enable d' enable et de disable la cible à tout moment.


Exemple d'utilisation:

import Foundation
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        

        //Create a button.
        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 44))
        
        //Add an event action block/listener -- Handles Button Press.
        let target = button.addEventHandler(event: .touchUpInside) { (control) in
            print("Pressed")
        }
        

        self.view.addSubview(button)
        
        //Example of enabling/disabling the listener/event-action-block.
        DispatchQueue.main.after(when: DispatchTime.now() + 5) {
            target.disable() //Disable the listener.
            
            DispatchQueue.main.after(when: DispatchTime.now() + 5) {
                target.enable() //Enable the listener.
            }
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}