在Swift中,如何检测哪些UIControlEvents触发了操作?

时间:2015-06-29 17:56:10

标签: ios swift events uitextfield

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

3 个答案:

答案 0 :(得分:5)

不幸的是,你无法区分触发动作处理程序的控件事件。这与Swift无关;它只是Cocoa的一个特色。

这是一个奇怪的设计决定,但事实恰恰如此。例如,请参阅my book,抱怨它:

  

奇怪的是,没有任何动作选择器参数提供任何方式来了解哪个控制事件触发了当前动作选择器调用!因此,例如,为了区分Touch Up Inside控制事件和Touch Up Outside控制事件,它们相应的目标 - 动作对必须指定两个不同的动作处理程序;如果将它们分派给同一个动作处理程序,则该处理程序无法发现发生了哪个控制事件。

答案 1 :(得分:0)

正如马特所说,这是不可能的(而且确实很烦人!)。我一直在使用这个小助手类来减轻自己的打字负担。

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

尽管,说实话,最终它并没有为您节省那么多打字。

答案 2 :(得分:0)

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

但是请注意:它不能很好地处理.touchDownRepeattapCount的{​​{1}}属性比正常触发的UIEvent具有更长的计时持续时间。此外,它似乎在.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)") } } } ,您可以将此方法包装在一个小类中,并节省每次触地的时间,也可以只在控件中保存点击时间。