iOS
UIControl - Ereignisbehandlung mit Blöcken
Suche…
Einführung
Normalerweise fügen wir bei Verwendung von UIControl
oder UIButton
einen selector
als UIControl
UIButton
, wenn ein Ereignis auf einer Schaltfläche oder einem Steuerelement auftritt, z.
Zum Beispiel würden wir Folgendes tun:
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")
}
}
Wenn es um selector
, muss der Compiler nur wissen, dass er existiert. Dies kann über ein protocol
und nicht implementiert werden.
Zum Beispiel würde die folgende Anwendung Ihre Anwendung zum Absturz bringen:
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()
}
}
Dies liegt daran, dass Ihre Anwendung die onButtonPress
Funktion NICHT implementiert.
Was wäre, wenn Sie all dies neben der Initialisierung der Schaltfläche tun könnten? Was wäre, wenn Sie keine Rückrufe angeben müssten und stattdessen Blöcke angeben könnten, die jederzeit hinzugefügt und entfernt werden können? Warum sollten Sie sich Gedanken über die Implementierung von Selektoren machen?
Lösung
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
}
}
Das obige ist eine einfache Erweiterung von UIControl
. Es fügt eine innere private Klasse hinzu, die über eine Callback-Funktion func run(_ control: UIControl)
, die als Ereignisaktion verwendet wird.
Als Nächstes verwenden wir die object association
, um das Ziel hinzuzufügen und zu entfernen, da es von UIControl
nicht beibehalten wird.
Die Event-Handler-Funktion gibt ein Protocol
zurück, um die inneren Abläufe der Target
Klasse auszublenden, aber auch, disable
das Target jederzeit enable
und disable
zu können.
Verwendungsbeispiel:
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()
}
}