如何使用另一个 class 的选择器?

How to use a selector from another class?

我有一个名为 FooFramework 的 Cocoa 触控框架。

在其中,我想管理键盘显示时所选视图在 Y 轴上的向上移动。我创建了一个 KeyboardManager class。外观如下:

import UIKit

public class KeyboardManager {
    var notifyFromObject: Any?
    var observer: Any
    public var viewsToPushUp: [UIView] = []

    public init(observer: Any, viewsToPushUp: [UIView], notifyFromObject: Any? = nil) {
        self.observer = observer
        self.notifyFromObject = notifyFromObject
        self.viewsToPushUp = viewsToPushUp
    }

    public func pushViewsUpWhenKeyboardWillShow(){
        let notificationCenter = NotificationCenter.default
        print(self)
        notificationCenter.addObserver(self.observer, selector: #selector(FooFramework.KeyboardManager.pushViewsUp), name: NSNotification.Name.UIKeyboardWillShow, object: notifyFromObject)
    }

    @objc public func pushViewsUp(notification: NSNotification) {
        if let keyboardRectValue = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            let keyboardHeight = keyboardRectValue.height

            for view in viewsToPushUp {
                view.frame.origin.y -= keyboardHeight
            }
        }
    }
}

然后,我将这个 FooFramework 导入到一个名为 Bar 的 iOS 应用程序中。为了测试FooFramework,我想推一个UITextField。这是代码:

import UIKit
import FooFramework

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let kb = KeyboardManager(observer: self, viewsToPushUp: [textField], notifyFromObject: nil)
        kb.pushViewsUpWhenKeyboardWillShow()
    }

    func pushViewsUp(notification: NSNotification) {
        print("This should not be printed")
    }
}

我的问题是 This should not be printed 出现在控制台中,而 KeyboardManager 中的 pushViewsUp 方法从未被调用。尽管我为选择器使用了完全限定的名称,但它坚持使用 ViewController 中的 pushViewsUp。这让我抓狂。

如果我从 ViewController 中删除 pushViewsUp,我会收到以下错误:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Bar.ViewController pushViewsUpWithNotification:]: unrecognized selector sent to instance 0x7fc540702d80'

我需要做什么才能使选择器正确指向 FooFramework.KeyboardManager.pushViewsUp

我认为您需要在 addObserver 函数中为观察者使用 self 而不是 self.observer

您还需要在函数范围之外声明 kb 变量,以便管理器检测到通知。

示例:

import UIKit
import FooFramework

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    var kb: KeyboardManager?

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        kb = KeyboardManager(observer: self, viewsToPushUp: [textField], notifyFromObject: nil)
        kb?.pushViewsUpWhenKeyboardWillShow()
    }
}

KeyboardManager 变化:

public func pushViewsUpWhenKeyboardWillShow() {
    let notificationCenter = NotificationCenter.default
    print(self)
    notificationCenter.addObserver(self, 
                                   selector: #selector(FooFramework.KeyboardManager.pushViewsUp), 
                                   name: NSNotification.Name.UIKeyboardWillShow, 
                                   object: notifyFromObject)
}

除了 Justin 提出的完全正确的建议之外,在您完全解决问题之前还有一些事情需要考虑。

  1. KeyBoardManager class 实例本身将观察 keyboardWillMoveUp 通知,因此您的

var observer: Any

里面没有必要。你应该删除它。

  1. 我还会将 addObserver 部分放在 KeyBoardManager class 本身的初始化中,这样就可以避免这个额外的调用 pushViewsUpWhenKeyboardWillShow() ,它似乎只做那个。由于 KeyBoardManager class 应该只做这个,我不明白为什么添加观察者应该是另一个函数调用。

这就是您的 KeyboardManager class 的外观:

import UIKit

public class KeyboardManager {

    var notifyFromObject: Any?
    public var viewsToPushUp: [UIView] = []

    public init(viewsToPushUp: [UIView], notifyFromObject: Any? = nil){
        self.notifyFromObject = notifyFromObject
        self.viewsToPushUp = viewsToPushUp

//remember KeyboardManager itself is observing the notifications and moving the views it received from the ViewController. Hence we use self.
        NotificationCenter.default.addObserver(self, selector: #selector(FooFramework.KeyboardManager.pushViewsUp), name: NSNotification.Name.UIKeyboardWillShow, object: notifyFromObject)
    }

    @objc public func pushViewsUp(notification: NSNotification) {
        if let keyboardRectValue = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            let keyboardHeight = keyboardRectValue.height

            for view in viewsToPushUp {
                view.frame.origin.y -= keyboardHeight
            }
        }
    }
}
  1. 在获得正确的行为之前,您还需要正确使用框架。

  2. 您应该延长 KeyboardManagerInstance 的生命周期,使其与具有 textField 的 ViewController 存在的时间一样长。正如贾斯汀所建议的那样,您可以通过将其声明为 ViewController 内的实例变量来实现。你这样做的方式,你的 KeyboardManager 实例是一个局部变量,一旦函数超出范围就会创建并立即释放。要验证这一点,您可以将其添加到您的 KeyboardManager class 并检查:

      deinit {
        print("KeyboardManager is perhaps dying an untimely death.")
      }
    

最后你的 ViewController class 应该做到这一点

  import UIKit
  import FooFramework

  class ViewController: UIViewController {

@IBOutlet weak var textField: UITextField!

var kb: KeyboardManager?

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    kb = KeyboardManager(viewsToPushUp: [textField], notifyFromObject: nil)
   //observe that there is no observer param in the initializer and also no "pushViewsUpWhenKeyboardWillShow" call as that behavior has already been moved to init itself.
}

}