在 Swift 中,您如何检测哪些 UIControl 事件触发了操作?
In Swift, how do you detect which UIControlEvents triggered the action?
我目前有 4 个 UITextField
@IBOutlet weak var fNameTextField: UITextField!
@IBOutlet weak var lNameTextField: UITextField!
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var phoneTextField: UITextField!
我想跟踪他们的各种活动:
[UIControlEvents.EditingChanged, UIControlEvents.EditingDidBegin, UIControlEvents.EditingDidEnd ]
但我不想有 3 个独立的事件处理程序,所以我创建了一个这样的函数。这个函数很好地告诉我哪个 UITextField 触发了一个事件,但它没有告诉我触发了哪个事件。:
fNameTextField.addTarget(self, action: "onChangeTextField:", forControlEvents: UIControlEvents.AllTouchEvents)
lNameTextField.addTarget(self, action: "onChangeTextField:", forControlEvents: UIControlEvents.AllTouchEvents)
emailTextField.addTarget(self, action: "onChangeTextField:", forControlEvents: UIControlEvents.AllTouchEvents)
phoneTextField.addTarget(self, action: "onChangeTextField:", forControlEvents: UIControlEvents.AllTouchEvents)
func onChangeTextField(sender:UITextField){
switch(sender){
case fNameTextField:
print("First Name")
case lNameTextField:
print("Last Name")
case emailTextField:
print("E-mail")
case phoneTextField:
print("Phone")
default: break
}
}
如何打印发件人姓名和触发事件的名称(例如:.EditingDidEnd、.EditingDidEnd、.EditingDidEnd)?
理想情况下,我不想编写多个事件处理程序,我更喜欢一个函数。
像这样:
func onChangeTextField(sender:UITextField){
switch(sender.eventTriggerd){
case UIControlEvents.EditingChanged:
println("EditingChanged")
case UIControlEvents.EditingDidBegin:
println("EditingDidBegin")
case UIControlEvents.EditingDidEnd:
println("EditingDidEnd")
default: break
}
}
遗憾的是,您无法区分哪个控制事件触发了动作处理程序。这与Swift无关;这只是 Cocoa.
的一个功能
这是一个奇怪的设计决定,但事实就是如此。参见,例如,my book,它抱怨它:
Curiously, none of the action selector parameters provide any way to learn which control event triggered the current action selector call! Thus, for example, to distinguish a Touch Up Inside control event from a Touch Up Outside control event, their corresponding target–action pairs must specify two different action handlers; if you dispatch them to the same action handler, that handler cannot discover which control event occurred.
正如马特所说,这是不可能的(而且确实很烦人!)。我一直在使用这个小帮手 class 来减轻自己的一些打字负担。
每个UIControl.Event
都有对应的可选Selector
。您可以只设置需要的选择器,忽略不需要的选择器。
class TargetActionMaker<T: UIControl> {
var touchDown: Selector?
var touchDownRepeat: Selector?
var touchDragInside: Selector?
var touchDragOutside: Selector?
var touchDragEnter: Selector?
var touchDragExit: Selector?
var touchUpInside: Selector?
var touchUpOutside: Selector?
var touchCancel: Selector?
var valueChanged: Selector?
var primaryActionTriggered: Selector?
var editingDidBegin: Selector?
var editingChanged: Selector?
var editingDidEnd: Selector?
var editingDidEndOnExit: Selector?
var allTouchEvents: Selector?
var allEditingEvents: Selector?
var applicationReserved: Selector?
var systemReserved: Selector?
var allEvents: Selector?
func addActions(_ sender: T, target: Any?) {
for selectorAndEvent in self.selectorsAndEvents() {
if let action = selectorAndEvent.0 {
sender.addTarget(target, action: action, for: selectorAndEvent.1)
}
}
}
private func selectorsAndEvents() -> [(Selector?, UIControl.Event)] {
return [
(self.touchDown, .touchDown),
(self.touchDownRepeat, .touchDownRepeat),
(self.touchDragInside, .touchDragInside),
(self.touchDragOutside, .touchDragOutside),
(self.touchDragEnter, .touchDragEnter),
(self.touchDragExit, .touchDragExit),
(self.touchUpInside, .touchUpInside),
(self.touchUpOutside, .touchUpOutside),
(self.touchCancel, .touchCancel),
(self.valueChanged, .valueChanged),
(self.primaryActionTriggered, .primaryActionTriggered),
(self.editingDidBegin, .editingDidBegin),
(self.editingChanged, .editingChanged),
(self.editingDidEnd, .editingDidEnd),
(self.editingDidEndOnExit, .editingDidEndOnExit),
(self.allTouchEvents, .allTouchEvents),
(self.allEditingEvents, .allEditingEvents),
(self.applicationReserved, .applicationReserved),
(self.systemReserved, .systemReserved),
(self.allEvents, .allEvents)
]
}
}
像这样使用它:
class MyControl: UIControl {
func setupSelectors() {
let targetActionMaker = TargetActionMaker<MyControl>()
targetActionMaker.touchDown = #selector(self.handleTouchDown(_:))
targetActionMaker.touchUpInside = #selector(self.handleTouchUpInside(_:))
targetActionMaker.touchUpOutside = #selector(self.handleTouchUpOutside(_:))
targetActionMaker.addActions(self, target: self)
}
@objc func handleTouchDown(_ sender: MyControl) {
print("handleTouchDown")
}
@objc func handleTouchUpInside(_ sender: MyControl) {
print("handleTouchUpInside")
}
@objc func handleTouchUpOutside(_ sender: MyControl) {
print("handleTouchUpOutside")
}
}
不过,老实说,最终它并没有让你省去那么多打字。
或者,您可以使用这个小助手将 UIEvent
(或 UITouch
)转换为 UIControl.Event
。它的工作原理是检查触摸的 Phase
,获取它在发送视图中的位置,并将它与之前的位置进行比较。如果您使用 UIEvent
它将使用第一次触摸。
但是请注意:它不能很好地处理 .touchDownRepeat
。 UIEvent
的 tapCount
属性 比正常触发的 .touchDownRepeat
的定时持续时间更长。此外,它似乎在 .touchDownRepeat
上发送多个操作。
而且,当然,它不会处理其他 UIControl.Event
,例如 .editingDidBegin
等
public extension UIEvent {
func firstTouchToControlEvent() -> UIControl.Event? {
guard let touch = self.allTouches?.first else {
print("firstTouchToControlEvent() Error: couldn't get the first touch. \(self)")
return nil
}
return touch.toControlEvent()
}
}
public extension UITouch {
func toControlEvent() -> UIControl.Event? {
guard let view = self.view else {
print("UITouch.toControlEvent() Error: couldn't get the containing view. \(self)")
return nil
}
let isInside = view.bounds.contains(self.location(in: view))
let wasInside = view.bounds.contains(self.previousLocation(in: view))
switch self.phase {
case .began:
if isInside {
if self.tapCount > 1 {
return .touchDownRepeat
}
return .touchDown
}
print("UITouch.toControlEvent() Error: unexpected touch began outs1ide of view. \(self)")
return nil
case .moved:
if isInside && wasInside {
return .touchDragInside
} else if isInside && !wasInside {
return .touchDragEnter
} else if !isInside && wasInside {
return .touchDragExit
} else if !isInside && !wasInside {
return .touchDragOutside
} else {
print("UITouch.toControlEvent() Error: couldn't determine touch moved boundary. \(self)")
return nil
}
case .ended:
if isInside {
return .touchUpInside
} else {
return.touchUpOutside
}
case .cancelled:
return .touchCancel
default:
print("UITouch.toControlEvent() Warning: couldn't handle touch event. \(self)")
return nil
}
}
}
像这样使用它:
class TestControl: UIControl {
func setupTouchEvent() {
self.addTarget(self, action: #selector(handleTouchEvent(_:forEvent:)), for: .allTouchEvents)
}
@objc func handleTouchEvent(_ sender: TestControl, forEvent event: UIEvent) {
guard let controlEvent = event.firstTouchToControlEvent() else {
print("Error: couldn't convert event to control event: \(event)")
return
}
switch controlEvent {
case .touchDown:
print("touchDown")
case .touchDownRepeat:
print("touchDownRepeat")
case .touchUpInside:
print("touchUpInside")
case .touchUpOutside:
print("touchUpOutside")
case .touchDragEnter:
print("touchDragEnter")
case .touchDragExit:
print("touchDragExit")
case .touchDragInside:
print("touchDragInside")
case .touchDragOutside:
print("touchDragOutside")
default:
print("Error: couldn't convert event to control event, or unhandled event case: \(event)")
}
}
}
要实现 .touchDownRepeat
,您可以将此方法包装在一点点 class 中,并节省每次触地的时间,或者只需将点击时间保存在您的控件中。
我目前有 4 个 UITextField
@IBOutlet weak var fNameTextField: UITextField!
@IBOutlet weak var lNameTextField: UITextField!
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var phoneTextField: UITextField!
我想跟踪他们的各种活动:
[UIControlEvents.EditingChanged, UIControlEvents.EditingDidBegin, UIControlEvents.EditingDidEnd ]
但我不想有 3 个独立的事件处理程序,所以我创建了一个这样的函数。这个函数很好地告诉我哪个 UITextField 触发了一个事件,但它没有告诉我触发了哪个事件。:
fNameTextField.addTarget(self, action: "onChangeTextField:", forControlEvents: UIControlEvents.AllTouchEvents)
lNameTextField.addTarget(self, action: "onChangeTextField:", forControlEvents: UIControlEvents.AllTouchEvents)
emailTextField.addTarget(self, action: "onChangeTextField:", forControlEvents: UIControlEvents.AllTouchEvents)
phoneTextField.addTarget(self, action: "onChangeTextField:", forControlEvents: UIControlEvents.AllTouchEvents)
func onChangeTextField(sender:UITextField){
switch(sender){
case fNameTextField:
print("First Name")
case lNameTextField:
print("Last Name")
case emailTextField:
print("E-mail")
case phoneTextField:
print("Phone")
default: break
}
}
如何打印发件人姓名和触发事件的名称(例如:.EditingDidEnd、.EditingDidEnd、.EditingDidEnd)?
理想情况下,我不想编写多个事件处理程序,我更喜欢一个函数。
像这样:
func onChangeTextField(sender:UITextField){
switch(sender.eventTriggerd){
case UIControlEvents.EditingChanged:
println("EditingChanged")
case UIControlEvents.EditingDidBegin:
println("EditingDidBegin")
case UIControlEvents.EditingDidEnd:
println("EditingDidEnd")
default: break
}
}
遗憾的是,您无法区分哪个控制事件触发了动作处理程序。这与Swift无关;这只是 Cocoa.
的一个功能这是一个奇怪的设计决定,但事实就是如此。参见,例如,my book,它抱怨它:
Curiously, none of the action selector parameters provide any way to learn which control event triggered the current action selector call! Thus, for example, to distinguish a Touch Up Inside control event from a Touch Up Outside control event, their corresponding target–action pairs must specify two different action handlers; if you dispatch them to the same action handler, that handler cannot discover which control event occurred.
正如马特所说,这是不可能的(而且确实很烦人!)。我一直在使用这个小帮手 class 来减轻自己的一些打字负担。
每个UIControl.Event
都有对应的可选Selector
。您可以只设置需要的选择器,忽略不需要的选择器。
class TargetActionMaker<T: UIControl> {
var touchDown: Selector?
var touchDownRepeat: Selector?
var touchDragInside: Selector?
var touchDragOutside: Selector?
var touchDragEnter: Selector?
var touchDragExit: Selector?
var touchUpInside: Selector?
var touchUpOutside: Selector?
var touchCancel: Selector?
var valueChanged: Selector?
var primaryActionTriggered: Selector?
var editingDidBegin: Selector?
var editingChanged: Selector?
var editingDidEnd: Selector?
var editingDidEndOnExit: Selector?
var allTouchEvents: Selector?
var allEditingEvents: Selector?
var applicationReserved: Selector?
var systemReserved: Selector?
var allEvents: Selector?
func addActions(_ sender: T, target: Any?) {
for selectorAndEvent in self.selectorsAndEvents() {
if let action = selectorAndEvent.0 {
sender.addTarget(target, action: action, for: selectorAndEvent.1)
}
}
}
private func selectorsAndEvents() -> [(Selector?, UIControl.Event)] {
return [
(self.touchDown, .touchDown),
(self.touchDownRepeat, .touchDownRepeat),
(self.touchDragInside, .touchDragInside),
(self.touchDragOutside, .touchDragOutside),
(self.touchDragEnter, .touchDragEnter),
(self.touchDragExit, .touchDragExit),
(self.touchUpInside, .touchUpInside),
(self.touchUpOutside, .touchUpOutside),
(self.touchCancel, .touchCancel),
(self.valueChanged, .valueChanged),
(self.primaryActionTriggered, .primaryActionTriggered),
(self.editingDidBegin, .editingDidBegin),
(self.editingChanged, .editingChanged),
(self.editingDidEnd, .editingDidEnd),
(self.editingDidEndOnExit, .editingDidEndOnExit),
(self.allTouchEvents, .allTouchEvents),
(self.allEditingEvents, .allEditingEvents),
(self.applicationReserved, .applicationReserved),
(self.systemReserved, .systemReserved),
(self.allEvents, .allEvents)
]
}
}
像这样使用它:
class MyControl: UIControl {
func setupSelectors() {
let targetActionMaker = TargetActionMaker<MyControl>()
targetActionMaker.touchDown = #selector(self.handleTouchDown(_:))
targetActionMaker.touchUpInside = #selector(self.handleTouchUpInside(_:))
targetActionMaker.touchUpOutside = #selector(self.handleTouchUpOutside(_:))
targetActionMaker.addActions(self, target: self)
}
@objc func handleTouchDown(_ sender: MyControl) {
print("handleTouchDown")
}
@objc func handleTouchUpInside(_ sender: MyControl) {
print("handleTouchUpInside")
}
@objc func handleTouchUpOutside(_ sender: MyControl) {
print("handleTouchUpOutside")
}
}
不过,老实说,最终它并没有让你省去那么多打字。
或者,您可以使用这个小助手将 UIEvent
(或 UITouch
)转换为 UIControl.Event
。它的工作原理是检查触摸的 Phase
,获取它在发送视图中的位置,并将它与之前的位置进行比较。如果您使用 UIEvent
它将使用第一次触摸。
但是请注意:它不能很好地处理 .touchDownRepeat
。 UIEvent
的 tapCount
属性 比正常触发的 .touchDownRepeat
的定时持续时间更长。此外,它似乎在 .touchDownRepeat
上发送多个操作。
而且,当然,它不会处理其他 UIControl.Event
,例如 .editingDidBegin
等
public extension UIEvent {
func firstTouchToControlEvent() -> UIControl.Event? {
guard let touch = self.allTouches?.first else {
print("firstTouchToControlEvent() Error: couldn't get the first touch. \(self)")
return nil
}
return touch.toControlEvent()
}
}
public extension UITouch {
func toControlEvent() -> UIControl.Event? {
guard let view = self.view else {
print("UITouch.toControlEvent() Error: couldn't get the containing view. \(self)")
return nil
}
let isInside = view.bounds.contains(self.location(in: view))
let wasInside = view.bounds.contains(self.previousLocation(in: view))
switch self.phase {
case .began:
if isInside {
if self.tapCount > 1 {
return .touchDownRepeat
}
return .touchDown
}
print("UITouch.toControlEvent() Error: unexpected touch began outs1ide of view. \(self)")
return nil
case .moved:
if isInside && wasInside {
return .touchDragInside
} else if isInside && !wasInside {
return .touchDragEnter
} else if !isInside && wasInside {
return .touchDragExit
} else if !isInside && !wasInside {
return .touchDragOutside
} else {
print("UITouch.toControlEvent() Error: couldn't determine touch moved boundary. \(self)")
return nil
}
case .ended:
if isInside {
return .touchUpInside
} else {
return.touchUpOutside
}
case .cancelled:
return .touchCancel
default:
print("UITouch.toControlEvent() Warning: couldn't handle touch event. \(self)")
return nil
}
}
}
像这样使用它:
class TestControl: UIControl {
func setupTouchEvent() {
self.addTarget(self, action: #selector(handleTouchEvent(_:forEvent:)), for: .allTouchEvents)
}
@objc func handleTouchEvent(_ sender: TestControl, forEvent event: UIEvent) {
guard let controlEvent = event.firstTouchToControlEvent() else {
print("Error: couldn't convert event to control event: \(event)")
return
}
switch controlEvent {
case .touchDown:
print("touchDown")
case .touchDownRepeat:
print("touchDownRepeat")
case .touchUpInside:
print("touchUpInside")
case .touchUpOutside:
print("touchUpOutside")
case .touchDragEnter:
print("touchDragEnter")
case .touchDragExit:
print("touchDragExit")
case .touchDragInside:
print("touchDragInside")
case .touchDragOutside:
print("touchDragOutside")
default:
print("Error: couldn't convert event to control event, or unhandled event case: \(event)")
}
}
}
要实现 .touchDownRepeat
,您可以将此方法包装在一点点 class 中,并节省每次触地的时间,或者只需将点击时间保存在您的控件中。