在我的项目中,我很少有视图控制器,它们是uiTableViewController、uiViewController的子类,在每个我想要实现此行为的对象上:
当用户点击文本字段外时,应关闭当用户点击文本字段内时可见的键盘。
我可以通过定义tap笔势识别器并关联选择器来消除键盘,从而轻松实现它:

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。这种方法的问题在于我需要定义两个BaseViewControllers,一个用于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类中重新定义MyViewController函数,这违背了我避免重复代码的目的:(
如果我在这里做了什么错事,或者有更好的方法来实现我的要求,请提出建议。

最佳答案

我认为有两个可能的问题:将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的参数是一个objective-c选择器,但是类的swift扩展不是objective-c。因此我将协议更改为一个UITapGestureRecognizer协议,但这是一个问题,因为我的协议包含了一些swift选项,并且在我尝试实现protoc时它也引入了新的崩溃。在我的风投里。
最后,我发现了一个不需要使用Objective-C选择器作为参数的替代方法;在我的例子中,我设置了一个NSNotificationCenter观察者。
在您的情况下,您最好简单地扩展@objc,因为UIViewController是一个子类,而子类继承扩展(我认为)。

09-19 11:55