在Swift中,如何检测哪个UIControlEvents触发了动作?

6

我目前有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
    }
}

根据这个Stack Overflow答案,你需要实现多个事件处理程序。 - ndmeiri
它叫做Swift。不是SWIFT。 - matt
3个回答

7
很遗憾,你不能区分哪个控件事件触发了操作处理器。这与Swift无关,只是Cocoa的一个特性。虽然这是一个奇怪的设计决定,但事实就是如此。例如,我的书中就抱怨了它:“有趣的是,所有的动作选择器参数都没有提供任何方法来学习当前动作选择器调用触发了哪个控件事件! 因此,为了区分Touch Up Inside控件事件和Touch Up Outside控件事件,它们对应的目标-动作对必须指定两个不同的操作处理器; 如果将它们分派到相同的操作处理器,则该处理器无法发现发生了哪个控件事件。”

1

正如Matt所说,这是不可能的(而且非常令人恼火!)。我一直在使用这个小助手类来减轻一些打字的负担。

每个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")
    }
}

虽然,说实话,最后它真的并没有节省你太多的打字时间。


1

或者您可以使用这个小助手,将 UIEvent(或 UITouch)转换为 UIControl.Event。它通过检查触摸的 Phase、获取其在发送视图中的位置并将其与先前位置进行比较来工作。如果使用 UIEvent,它将使用第一个触摸。

但是,请注意:它无法很好地处理 .touchDownRepeatUIEventtapCount 属性的时间持续时间比通常触发的 .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,您可以将此方法包装在一个小类中,并在每次触摸按下时保存时间,或者只需在控件中保存点击时间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接