首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用协议扩展关闭外部点击键盘

使用协议扩展关闭外部点击键盘
EN

Stack Overflow用户
提问于 2016-04-18 08:03:22
回答 3查看 951关注 0票数 2

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

当用户点击文本字段的外部时,它应该关闭键盘,当用户在键盘内点击时,键盘是可见的。

我可以通过定义一个点击手势识别器和关联一个选择器来拒绝键盘来实现它:

代码语言:javascript
复制
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。所以我定义了一个协议:

代码语言:javascript
复制
protocol DismissKeyboardOnOutsideTap {
    var backgroundView: UIView! { get }
    func configureToDismissKeyboard()
    func hideKeyboard()
}

然后定义它的扩展:

代码语言:javascript
复制
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)
    }

}

在我看来,控制器确认了协议:

代码语言:javascript
复制
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()
    }
}

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

代码语言:javascript
复制
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyProject.MyViewController hideKeyboard]: unrecognized selector sent to instance 0x7f88c1e5d700'

为了避免这种崩溃,我需要在hideKeyboard类中重新定义MyViewController函数,这违背了我避免重复代码的目的:(

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

EN

回答 3

Stack Overflow用户

发布于 2016-04-18 13:20:01

我认为有两个可能的问题:将Self转换为AnyObject,以及不使用新的#selector语法。

与其将Self转换为AnyObject,不如将协议定义为只使用类的协议:

代码语言:javascript
复制
protocol DismissKeyboardOnOutsideTap: class {
    // protocol definitions...
}

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

代码语言:javascript
复制
extension DismissKeyboardOnOutsideTap where Self: UIViewController {

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

}

编辑:我记得我在做这件事时遇到的另一个问题。action参数的UITapGestureRecognizer是一个目标C选择器,但是Swift对类的扩展并不是目标C。所以我把协议改成了@objc协议,但这是个问题,因为我的协议包含了一些Swift选项,当我试图在VC中实现该协议时,它还引入了新的崩溃。

最终,我发现了一种不需要目标C选择器作为参数的替代方法;在我的例子中,我设置了一个NSNotification中心观察者。

在您的例子中,您最好只是扩展UIViewController,因为UITableViewController是一个子类,子类继承扩展(我认为)。

票数 3
EN

Stack Overflow用户

发布于 2017-04-03 13:22:55

正如用户ConfusedByCode所指出的那样,即使一种面向协议的方法一开始就很好,但当编译器强制您使用关键字@objc时,它就变得不快了。

因此,扩展UIViewController是一种更好的方法;至少在我看来是这样。

为了保持一个干净的项目结构,创建一个名为UIViewController+DismissKeyboard.swift的文件并将以下内容粘贴到其中:

代码语言:javascript
复制
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等,都将访问configureKeyboardDismissOnSwipeDown()方法。

因此,只需在每个视图控制器中调用configureKeyboardDismissOnSwipeDown()内部的viewDidLoad,就会自动注入一个向下滑动的手势来拒绝键盘。

还有一个需要注意的是,每个视图控制器都需要分别调用configureKeyboardDismissOnSwipeDown()。不幸的是,由于您不能简单地在扩展中覆盖viewDidLoad(),这是一个令人遗憾的问题。此外,苹果公司为什么没有直接在键盘上实现这一点,这样美国开发者就不需要对其进行编码了,这对我来说仍然是个谜。

无论如何,这个问题可以通过一种叫做方法摆动的技术来解决。基本上,这是覆盖苹果给出的方法,使他们的行为在运行时发生变化。我不会更详细地介绍方法,就像说玩游戏会非常危险,因为你会修改苹果提供的、由系统使用的经过战斗测试的、可靠的代码。

之后,当您在视图控制器中实现上面提供的dismissKeyboard()方法时,如果您希望能够取消键盘,那么您就可以这样做了。

票数 1
EN

Stack Overflow用户

发布于 2021-10-26 23:06:07

TapGestureDismissable.swift

代码语言:javascript
复制
 @objc protocol TapGestureDismissable where Self: UIViewController {
        func hideKeyboard()
    }
    
    extension TapGestureDismissable {
        func configureTapGestureToDismissKeyboard() {
            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
            tapGesture.cancelsTouchesInView = true
            view.addGestureRecognizer(tapGesture)
        }
    }

在你的ViewController

代码语言:javascript
复制
extension myViewController: TapGestureDismissable {
    func hideKeyboard() {
        view.endEditing(true)
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/36688420

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档