通过协议扩展将选择器与协议方法的默认实现一起使用

Using selectors with default implementations of protocol methods via protocol extensions

我正在尝试制定一个 ViewController 可以实施的协议来调整其视图以适应键盘 show/hide。

protocol KeyboardAdaptable {
    func keyboardWillShow(notification: NSNotification)
    func keyboardWillHide(notification: NSNotification)
    func addKeyboardNotificationObservers()
}

extension KeyboardAdaptable where Self: UIViewController, Self: NSObject {
    func keyboardWillShow(notification: NSNotification) {
        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
            if self.view.frame.origin.y == 0{
                self.view.frame.origin.y -= keyboardSize.height
            }
        }
    }

    func keyboardWillHide(notification: NSNotification) {
        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
            if self.view.frame.origin.y != 0{
                self.view.frame.origin.y += keyboardSize.height
            }
        }
    }

    func addKeyboardNotificationObservers() {
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }
}

错误:"Argument of #selector refers to instance methods 'keyboardWillShow' that is not exposed to Objective-C."

我知道选择器是 Objective-C 的一个特性并且引用的函数必须兼容。我试图通过标记协议本身以及使用 @objc 注释的方法来解决这个问题,但是编译器坚持我也使用 @objc 标记协议扩展中的默认实现。当我这样做时,它对我大喊大叫,要求我删除 @objc 注释,因为 "@objc 只能与 类 的成员、@objc 协议和 类 的具体扩展一起使用"(即不在协议扩展中?)

有谁知道实现这个目标的方法吗?我知道乍一看似乎没有办法解决它,但我也知道 UIViewController 是 NSObject 的子项,通常允许 UIViewController 上的实例方法成为选择器的目标。我想通过对我的协议扩展施加约束,要求它是 UIViewController 的子类,我可以将其中包含的默认实现作为选择器的目标。

想法?

无法完成。

每个人首先都认为这是一个很酷的想法,(包括我自己)但事实并非如此。

Swift 协议扩展对 Objective-C 不可见。对此无能为力。即使使用 #selector 也不起作用,因为:

您的协议功能在协议扩展中定义。

人们不断尝试通过协议扩展将 Objective-C 可调用功能(选择器、委托方法等)注入 class。对不起。

我完全同意之前的回答——这是做不到的。但是,我决定对您的代码进行一些愚弄,并创建我能找到的最可接受的解决方案。也许它会派上用场:

class KeyboardAdapter {

   private weak var view: UIView!

   init(view: UIView) {
      self.view = view
      addKeyboardNotificationObservers()
   }

   @objc
   func keyboardWillShow(notification: NSNotification) {
      if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
         if self.view.frame.origin.y == 0 {
            self.view.frame.origin.y -= keyboardSize.height
         }
      }
   }

   @objc
   func keyboardWillHide(notification: NSNotification) {
      if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
         if self.view.frame.origin.y != 0 {
            self.view.frame.origin.y += keyboardSize.height
         }
      }
   }

   private func addKeyboardNotificationObservers() {
      NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
      NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
   }
}

protocol KeyboardAdaptable: class {
   var keyboardAdapter: KeyboardAdapter! { get set }
   func configureAdapter()
}

extension KeyboardAdaptable where Self: UIViewController {
   func configureAdapter() {
      keyboardAdapter = KeyboardAdapter(view: self.view)
   }
}