iOS
UIControl - Eventafhandeling met blokken
Zoeken…
Invoering
Meestal voegen we bij het gebruik van UIControl
of UIButton
een selector
als een terugbelactie voor wanneer een gebeurtenis plaatsvindt op een knop of bedieningselement, zoals de gebruiker die op de knop drukt of de bedieningsknop aanraakt.
We zouden bijvoorbeeld het volgende doen:
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")
}
}
Als het gaat om selector
, hoeft de compiler alleen te weten dat deze bestaat. Dit kan via een protocol
en niet worden geïmplementeerd.
Het volgende zou bijvoorbeeld uw applicatie laten crashen:
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()
}
}
Dit komt omdat uw toepassing de functie onButtonPress
NIET implementeert.
Wat als u dit allemaal zou kunnen doen naast de initialisatie van de knop? Wat als u geen callbacks hoeft op te geven en in plaats daarvan blokken kunt opgeven die op elk gewenst moment kunnen worden toegevoegd en verwijderd? Waarom zorgen maken over het implementeren van selectors?
Oplossing
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
}
}
Het bovenstaande is een eenvoudige uitbreiding op UIControl
. Het voegt een interne privéklasse toe met een callback- func run(_ control: UIControl)
die wordt gebruikt als actie van de gebeurtenis.
Vervolgens gebruiken we object association
om het doel toe te voegen en te verwijderen, omdat het niet door de UIControl
wordt behouden.
Het event handler functie geeft een Protocol
om de interne werking van het verbergen Target
klasse, maar ook om u te enable
en disable
het doel op een bepaald moment.
Gebruiksvoorbeeld:
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()
}
}