使用协议扩展来解除外部点击键盘

时间:2016-04-18 08:03:23

标签: swift protocols uitapgesturerecognizer

在我的项目中,我有几个视图控制器,它们是UITableViewController,UIViewController的子类,每个我想实现这个行为:

  

当用户点击文本字段之外时,它应该关闭用户在其中轻敲时可见的键盘。

我可以通过定义点击手势识别器并关联选择器来关闭键盘来轻松实现它:

FileObserver

由于我必须在多个视图控制器中实现相同的行为,我试图找出一种避免在多个类中使用重复代码的方法。

对我来说,一个选项是定义一个class MyViewController { override func viewDidLoad() { super.viewDidLoad() configureToDismissKeyboard() } private func configureToDismissKeyboard() { let tapGesture = UITapGestureRecognizer(target: self, action: "hideKeyboard") tapGesture.cancelsTouchesInView = true form.addGestureRecognizer(tapGesture) } func hideKeyboard() { form.endEditing(true) } } ,它是BaseViewController的子类,并在其中定义了所有上述方法,然后将我的每个视图控制器子类化为UIViewController。这种方法的问题在于,我需要为BaseViewController定义两个BaseViewController,为UIViewController定义一个,因为我使用的是两者的子类。

我尝试使用的另一个选项是 - UITableViewController。所以我定义了一个协议:

Protocol-Oriented Programming

然后定义了它的扩展名:

protocol DismissKeyboardOnOutsideTap {
    var backgroundView: UIView! { get }
    func configureToDismissKeyboard()
    func hideKeyboard()
}

在我的视图控制器中,我向协议确认:

extension DismissKeyboardOnOutsideTap {
    func configureToDismissKeyboard() {
        if let this = self as? AnyObject {
            let tapGesture = UITapGestureRecognizer(target: this, action: "hideKeyboard")
            tapGesture.cancelsTouchesInView = true
            backgroundView.addGestureRecognizer(tapGesture)
        }

    }

    func hideKeyboard() {
        backgroundView.endEditing(true)
    }

}

问题是 - 上面的代码崩溃,但异常:

class MyViewController: UITableViewController, DismissKeyboardOnOutsideTap {

    var backgroundView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // configuring background view to dismiss keyboard on outside tap
        backgroundView = self.tableView
        configureToDismissKeyboard()
    }
}

为了避免这种崩溃,我需要在Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyProject.MyViewController hideKeyboard]: unrecognized selector sent to instance 0x7f88c1e5d700' 类中重新定义hideKeyboard函数,这违背了我避免重复代码的目的:(

如果我在这里做错了,或者有更好的方法来实现我的要求,请建议。

2 个答案:

答案 0 :(得分:3)

我认为有两个可能的问题:将Self投射到AnyObject,而不使用新的#selector语法。

不是将Self转换为AnyObject,而是将协议定义为仅限类的协议:

protocol DismissKeyboardOnOutsideTap: class {
    // protocol definitions...
}

然后使用类型约束将扩展仅应用于UIViewController的子类,并在代码中直接使用Self,而不是转换为AnyObject

extension DismissKeyboardOnOutsideTap where Self: UIViewController {

    func configureToDismissKeyboard() {
        let gesture = UITapGestureRecognizer(target: self,
            action: #selector(Self.hideKeyboard()))
        gesture.cancelsTouchesInView = true
        backgroundView.addGestureRecognizer(gesture)
    }

}

编辑:我记得我在做这件事时遇到的另一个问题。 action的{​​{1}}参数是Objective-C选择器,但类的Swift扩展不是Objective-C。所以我将协议更改为UITapGestureRecognizer协议,但这是一个问题,因为我的协议包含了一些Swift选项,当我尝试在VC中实现协议时,它还引入了新的崩溃。

最终,我发现了一种不需要Objective-C选择器作为参数的替代方法;就我而言,我正在设立一个NSNotification中心观察员。

在你的情况下,你可能最好只是扩展@objc,因为UIViewController是一个子类,而子类继承扩展(我认为)。

答案 1 :(得分:1)

正如用户@objc所指出的那样,即使面向协议的方法开始时很好,但由于编译器强制您使用关键字UIViewController,因此它变得非Swifty。

因此,延长UIViewController+DismissKeyboard.swift是一种更好的方法;至少在我看来。

为了维护一个干净的项目结构,请创建一个名为import UIKit extension UIViewController { func configureKeyboardDismissOnTap() { let keyboardDismissGesture = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard)) view.addGestureRecognizer(keyboardDismissGesture) } func dismissKeyboard() { // to be implemented inside your view controller(s) wanting to be able to dismiss the keyboard via tap gesture } } 的文件并将以下内容粘贴到其中:

UIViewController

之后,您的任何一个视图控制器或继承自UITableViewController的Apple等其他基类(例如configureKeyboardDismissOnSwipeDown()等)都可以访问方法configureKeyboardDismissOnSwipeDown()

因此,仅在每个视图控制器中调用viewDidLoad内的configureKeyboardDismissOnSwipeDown()将自动注入向下滑动手势以关闭键盘。

还有一点需要注意的是,每个视图控制器都需要单独调用viewDidLoad()。不幸的是,这是一个无赖,因为您不能简单地覆盖您的扩展程序中的dismissKeyboard()。此外,对于我来说,为什么Apple没有直接在键盘中实现这一点,以便我们的开发人员不需要围绕它进行编码,这仍然是一个谜。

无论如何,这个问题可以通过一种名为Method Swizzling的技术来解决。基本上,它是Apple提供的重写方法,以便它们的行为在运行时发生变化。我不会再详细介绍方法调整,只是说它可能是非常危险的,因为你会修改由Apple提供并经过系统使用的经过实战检验的可靠代码。

之后,当您在视图控制器中实现上述提供的{{1}}方法时,您希望能够关闭键盘,您将能够这样做。