27

Let's consider the following code:

protocol A {
    func doA()
}

extension A {
  func registerForNotification() {
      NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardDidShow:"), name: UIKeyboardDidShowNotification, object: nil)
  }

  func keyboardDidShow(notification: NSNotification) {

  }
}

Now look at a UIViewController subclass that implements A:

class AController: UIViewController, A {
   override func viewDidLoad() {
      super.viewDidLoad()
      self.registerForNotification()
      triggerKeyboard()
   }

   func triggerKeyboard() {
      // Some code that make key board appear
   }

   func doA() {
   }
}

But surprisingly this crashes with an error:

keyboardDidShow:]: unrecognized selector sent to instance 0x7fc97adc3c60

So should I implement the observer in the view controller itself? Can't it stay in the extension?

Following things already tried.

making A a class protocol. Adding keyboardDidShow to protocol itself as signature.

protocol A:class {
   func doA()
   func keyboardDidShow(notification: NSNotification)
}
Michael Rael
  • 71
  • 1
  • 7
Swift Hipster
  • 1,604
  • 3
  • 16
  • 24
  • I've tried something similar too in the past, but I found out that Swift's protocol extensions don't work with Objective-C protocols and classes, [but apparently they somehow do](http://stackoverflow.com/questions/27097688/can-objective-c-code-call-swift-extension-on-class), I'm confused – Kametrixom Oct 08 '15 at 17:57
  • `extension A{}` ??? Are you talking about `extension Controller{}` – Midhun MP Oct 08 '15 at 18:01
  • You just need to add the parameter to the method or delete : from the end of the selector's name – Leo Dabus Oct 08 '15 at 18:30
  • @MidhunMP . Yes it is `extension A{}`. New feature in Swift 2 onwards. Which is called protocol extensions. Which enables even adding default functionality to protocol methods. – Swift Hipster Oct 09 '15 at 00:45
  • 1
    The method is `func keyboardDidShow(notification: NSNotification)` which makes a match with `Selector("keyboardDidShow:")` – Swift Hipster Oct 09 '15 at 00:48
  • did you file a bug or feature request for this? – Pradip Vaghasiya Oct 21 '15 at 11:49
  • got the same problem,does anyone have a solution ? – netbe Dec 14 '15 at 19:19

5 Answers5

36

I solved a similar problem by implementing the newer - addObserverForName:object:queue:usingBlock: method of NSNotificationCenter and calling the method directly.

extension A where Self: UIViewController  {
    func registerForNotification() {
        NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardDidShowNotification, object: nil, queue: nil) { [unowned self] notification in
            self.keyboardDidShow(notification)
        }
    }

    func keyboardDidShow(notification: NSNotification) {
        print("This will get called in protocol extension.")
    }
}

This example will cause keyboardDidShow to be called in the protocol extension.

James Paolantonio
  • 2,194
  • 1
  • 15
  • 32
3

In addition to James Paolantonio's answer. A unregisterForNotification method can be implemented using associated objects.

var pointer: UInt8 = 0

extension NSObject {
    var userInfo: [String: Any] {
        get {
            if let userInfo = objc_getAssociatedObject(self, &pointer) as? [String: Any] {
                return userInfo
            }
            self.userInfo = [String: Any]()
            return self.userInfo
        }
        set(newValue) {
            objc_setAssociatedObject(self, &pointer, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }
}

protocol A {}
extension A where Self: UIViewController {

    var defaults: NotificationCenter {
        get {
            return NotificationCenter.default
        }
    }

    func keyboardDidShow(notification: Notification) {
        // Keyboard did show
    }

    func registerForNotification() {
        userInfo["didShowObserver"] = defaults.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil, using: keyboardDidShow)
    }

    func unregisterForNotification() {
        if let didShowObserver = userInfo["didShowObserver"] as? NSObjectProtocol {
            defaults.removeObserver(didShowObserver, name: .UIKeyboardDidShow, object: nil)
        }
    }
}
andershqst
  • 1,454
  • 1
  • 14
  • 24
1

To avoid the crash, implement the observer method in the Swift class that uses the protocol.

The implementation has to be in the Swift class itself, not just the protocol extension, because a selector always refers to an Objective-C method, and a function within a protocol extension is not available as an Objective-C selector. Yet methods from a Swift class are available as Objective-C selectors if the Swift class inherits from an Objective-C class

“If your Swift class inherits from an Objective-C class, all of the methods and properties in the class are available as Objective-C selectors.”

Also, in Xcode 7.1, self has to be downcast to AnyObject when specifying it as the observer in the addObserver call.

protocol A {
    func doA()
}

extension A {
    func registerForNotification() {
        NSNotificationCenter.defaultCenter().addObserver(self as! AnyObject,
            selector: Selector("keyboardDidShow:"),
            name: UIKeyboardDidShowNotification,
            object: nil)
    }

    func keyboardDidShow(notification: NSNotification) {
        print("will not appear")
    }
}

class ViewController: UIViewController, A {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.registerForNotification()
        triggerKeyboard()
    }

    func triggerKeyboard(){
        // Some code that makes the keyboard appear
    }

    func doA(){
    }

    func keyboardDidShow(notification: NSNotification) {
        print("got the notification in the class")
    }
}
Michael Rael
  • 71
  • 1
  • 7
1

Using selectors in Swift requires that your concrete class must inherit from NSObject. To enforce this in a protocol extension, you should use where. For example:

protocol A {
    func doA()
}

extension A where Self: NSObject {
  func registerForNotification() {
      NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardDidShow:"), name: UIKeyboardDidShowNotification, object: nil)
  }

  func keyboardDidShow(notification: NSNotification) {

  }
}
Michael
  • 8,891
  • 3
  • 29
  • 42
0

I solved it using NSObjectProtocol as below,

@objc protocol KeyboardNotificaitonDelegate: NSObjectProtocol {
func keyboardWillBeShown(notification: NSNotification)
func keyboardWillBeHidden(notification: NSNotification)
}

extension KeyboardNotificaitonDelegate {

func registerForKeyboardNotifications() {
    //Adding notifies on keyboard appearing
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeShown(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}

func deregisterFromKeyboardNotifications() {
    //Removing notifies on keyboard appearing
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
}
Vignesh
  • 3,571
  • 6
  • 28
  • 44