NotificationCenter observer with generic class - ios

I have a generic class:
open class GenericClass<T:MyClass>: NSObject {
public init(_ myParam:Int) {
NotificationCenter.default.addObserver(self, selector: #selector(self.someFunc), name: .MyName, object: nil)
}
func someFunc() {
}
}
But i wonder that those code doesn't work. I get error:
'self' used before super.init call

You just have to call the initialiser of NSObject (The class you're subclassing):
open class GenericClass<T:MyClass>: NSObject {
public init(_ myParam:Int) {
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(self.someFunc), name: .MyName, object: nil)
}
func someFunc() {
}
}

Related

Same protocol extension for different classes (but same baseclass)?

I'm having a hard time explaining the issue, so I'll try my best.
I have this code:
protocol MyListener
{
func setupMyViewControllers() //code I would like to share with any UIViewController that conforms to this protocol
func receiveUpdateA() //each protocol-conforming class should have its own implementation
func receiveUpdateB() //each protocol-conforming class should have its own implementation
}
//my classes hierarchy
class A: UIViewController {}
class B: UIViewController {}
class b: B {}
So, instead of writing duplicate code like the following:
extension A: MyListener
{
func setupMyViewControllers()
{
NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateA(_:)), name: "receiveUpdateA", object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateB(_:)), name: "receiveUpdateB", object: nil)
}
//rest of protocol implementations
func receiveUpdateA() {/*...*/}
func receiveUpdateB() {/*...*/}
}
extension B: MyListener
{
func setupMyViewControllers()
{
NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateA(_:)), name: "receiveUpdateA", object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateB(_:)), name: "receiveUpdateB", object: nil)
}
//rest of protocol implementations
func receiveUpdateA() {/*...*/}
func receiveUpdateB() {/*...*/}
}
I tried the following (with no success):
extension MyListener where Self: UIViewController
{
func setupMyViewControllers()
{
NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateA(_:)), name: "receiveUpdateA", object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateB(_:)), name: "receiveUpdateB", object: nil)
}
}
extension A: MyListener
{
//rest of protocol implementations
func receiveUpdateA() {/*...*/}
func receiveUpdateB() {/*...*/}
}
extension B: MyListener
{
//rest of protocol implementations
func receiveUpdateA() {/*...*/}
func receiveUpdateB() {/*...*/}
}
I thought that this way I could share the setup code, but still be able to extend the remaining "receiveUpdate" functions in separate extensions.
But I get stuck in a loop of errors. I get "Cannot use 'receiveUpdateA' as a selector because protocol 'MyListener' is not exposed to Objective-C. However, exposing it to obj-c then gives me the error that protocol extensions can't have obj-c functions.
Is there any way of achieving this without extending the entirety of UIViewController to MyListener? (I'm not even sure that would work. It seems like the core issue is the use of Selectors)
Don't know what Swift/XCode Version you are using right now, but the following appears to work just fine with Swift 5.7.1.
#objc protocol MyListener {
func receiveUpdateA()
func receiveUpdateB()
}
extension MyListener {
func setupMyViewControllers() {
NotificationCenter.default.addObserver(
self,
selector: #selector(receiveUpdateA),
name: NSNotification.Name("receiveUpdateA"),
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(receiveUpdateB),
name: NSNotification.Name("receiveUpdateB"),
object: nil
)
}
}
//my classes hierarchy
class A: UIViewController {}
class B: UIViewController {}
extension A: MyListener {
//rest of protocol implementations
#objc func receiveUpdateA() {/*...*/}
#objc func receiveUpdateB() {/*...*/}
}
extension B: MyListener {
//rest of protocol implementations
#objc func receiveUpdateA() {/*...*/}
#objc func receiveUpdateB() {/*...*/}
}
Even though I'd say that you shouldn't consider DRY as a religious rule, here's an alternative implementation using Apple's own equivalent to Rx:
import UIKit
import Combine
protocol ObservesNotificiationCenter {
}
extension ObservesNotificiationCenter {
func onNotification(withName name: String) -> AnyPublisher<Notification, Never> {
return NotificationCenter.default.publisher(
for: NSNotification.Name(name)
).eraseToAnyPublisher()
}
}
#objc protocol ReceivesUpdates {
func receiveUpdateA(notificiation: Notification)
func receiveUpdateB(notificiation: Notification)
}
extension ReceivesUpdates where Self: ObservesNotificiationCenter {
func setupNotifications() -> Set<AnyCancellable> {
var cancellables: Set<AnyCancellable> = []
onNotification(withName: "receiveUpdateA")
.receive(on: DispatchQueue.main)
.sink(receiveValue: receiveUpdateA(notificiation:))
.store(in: &cancellables)
onNotification(withName: "receiveUpdateB")
.receive(on: DispatchQueue.main)
.sink(receiveValue: receiveUpdateB(notificiation:))
.store(in: &cancellables)
return cancellables
}
}
class ViewController: UIViewController, ObservesNotificiationCenter, ReceivesUpdates {
private var cancellables: Set<AnyCancellable>!
override func viewDidLoad() {
super.viewDidLoad()
cancellables = setupNotifications()
}
func receiveUpdateA(notificiation: Notification) {
}
func receiveUpdateB(notificiation: Notification) {
}
}

Using #objc in Swift 5 protocols

I have an issue with using #objc code in swift protocols and was wondering if there is a workaround for that.
Currently, I have code:
import UIKit
#objc
protocol TrackScreenshot {
func registerObserver()
func removeObservers()
}
extension TrackScreenshot where Self: ScreenTracking {
func registerObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(trackScreenshot), name: UIApplication.userDidTakeScreenshotNotification, object: nil)
}
func removeObservers() {
NotificationCenter.default.removeObserver(self, name: UIApplication.userDidTakeScreenshotNotification, object: nil )
}
func trackScreenshot() {
print(screenName.rawValue)
}
}
So I want to inherit the TrackScreenshot protocol and make screens easily trackable.
BUT there is an issue.
registerObserver() method on #selecor asks to add #objc to trackScreenshot method, but if I do so, Xcode complains on trackScreenshot() line and telling: #objc can only be used with members of classes, #objc protocols, and concrete extensions of classes
Is there a way to fix this?
Also tried:
NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: nil) { _ in
print(self.screenName.rawValue)
}
but it's not working, and the observer can't be removed and remains in the circle, so prints all the previous screen names when a new screen is opened.
Any help is more then welcome! Thanks in advance!
I would use the closure form of notification observation rather than a selector/method:
protocol TrackScreenshot {
func registerObserver(handler: (()->Void)?)
func removeObservers()
}
extension TrackScreenshot where Self: ScreenTracking {
func registerObserver(handler: (()->Void)?) {
NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: nil) { (notification) in
handler?()
}
}
func removeObservers() {
NotificationCenter.default.removeObserver(self, name: UIApplication.userDidTakeScreenshotNotification, object: nil )
}
}
Then your use is something like:
self.registerObserver { [weak self] in
guard let self = self else {
return
}
print("Screen shot")'
}
You can track screenshot notification using delegates too, feel free to refactor as per your need:
public protocol ScreenshotDelegate: AnyObject {
func screenshotDetected()
}
open class ScreenshotTracker: NSObject {
private weak var delegate: ScreenshotDelegate?
public init(delegate: ScreenshotDelegate) {
self.delegate = delegate
NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: OperationQueue.main) { notification in
delegate.screenshotDetected()
print("Screenshot notification")
}
}
}
ViewController Setup:
override func viewDidLoad() {
super.viewDidLoad()
let _ = ScreenshotTracker(delegate: self)
}
extension ViewController: ScreenshotDelegate {
func screenshotDetected() {
print("screenshot taken!!!")
}
}

Notification Center not working. The observer is not being called

I am trying to call a function from another class in Swift and NotificationCenter is an option to do that so I started with the addObserver.
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(toggleSideMenu), name: NSNotification.Name("callToggleSideMenu"), object: nil)
}
#objc func toggleSideMenu(){
if isMenuOpen {
sideContainer.constant = -260
} else {
sideContainer.constant = 0
}
}
And in the other class I have added the (post):
#objc func clickOnButton(button: UIButton) {
NotificationCenter.default.post(name: NSNotification.Name("callToggleSideMenu"), object: nil)
}
Everything seems ok but I do not know why it is not working. I have seen a lot of the same issue here in stackoverflow but no answer solved my issue.
Function definition is not correct. It should be :
#objc func toggleSideMenu(_ notification: Notification){
if isMenuOpen {
sideContainer.constant = -260
} else {
sideContainer.constant = 0
}
}
Call it using :
NotificationCenter.default.addObserver(self, selector: #selector(toggleSideMenu(_:)), name: NSNotification.Name("callToggleSideMenu"), object: nil)

How to unregister an NSNotification in Swift iOS

I have two controllers
class CtrlA: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(CtrlB.self, selector: #selector(CtrlB.badge(notification:)), name: NSNotification.Name(rawValue: "badge"), object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(CtrlB.self, name: NSNotification.Name(rawValue: "badge"), object: nil)
}
}
class CtrlB: UIViewController {
static func badge (notification: NSNotification) {
// blah blah
}
}
Whats the correct way to unregister the notification listener above?
I'm not certain this is correct:
NotificationCenter.default.removeObserver(CtrlB.self, name: NSNotification.Name(rawValue: "badge"), object: nil)
I don't think I can use self either, since it was registered on CtrlB.self
So the best way to implement the notification in your project is create one class called NotificationManager inside that declare one dictionary in which you can always update the observers
class NotificationManager {
var observers = [String: AnyObject]()
}
Create addObserver method, post notification method and remove
observer method inside the same class.
func postNotification(_ name: String, userInfo: [AnyHashable: Any]? = nil) {
NotificationCenter.default.post(name: name, object: nil, userInfo: userInfo)
}
func addObserver(_ name: String, block: #escaping (Notification) -> Void) {
//if observer is already in place for this name, remove it
removeObserverForName(name)
let observer = NotificationCenter.default.addObserver(forName: name), object: nil, queue: OperationQueue.main, using: block)
self.observers[name] = observer
}
func removeObserver(_ name: name) {
guard let observer = self.observers[name] else { return }
NotificationCenter.default.removeObserver(observer)
self.observers.removeValue(forKey: name)
}
//Removes all observers
func removeAllObservers() {
for observer in self.observers.values {
NotificationCenter.default.removeObserver(observer)
}self.observers = [:]
}
So access the above method in any of your class wherever its required and it will take care of everything. This will also prevent crash in your code. If try to remove the same observer more than one time.
I am not sure why you are registering/unregistering to notifications with a class and not an instance. 'CtrlB.self' - will not give you an instance of the CtrlB class, in fact it will return a class itself.
Instead you should use something like this:
class CtrlA {
let ctrlBInstance = CtrlB()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(ctrlBInstance, selector: #selector(CtrlB.badge(notification:)), name: NSNotification.Name(rawValue: "badge"), object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(ctrlBInstance, name: NSNotification.Name(rawValue: "badge"), object: nil)
}
}
And your ClassB should look like this in this case:
class CtrlB {
func badge (notification: NSNotification) {
// blah blah
}
}
You need to get the instance of the observer,which you haven't declared...
for instance you need to set class variable secondA...
class CtrlA: UIViewController {
var secondController: CtrlB?
override func viewDidLoad()
{
super.viewDidLoad()
if let unwrappedController = storyboard.instantiateViewController(withIdentifier: "someViewController") as? CtrlB
{
secondController = unwrappedController
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let secondController = secondController
{
NotificationCenter.default.addObserver(CtrlB.self, selector: #selector(CtrlB.badge(notification:)), name: NSNotification.Name(rawValue: "badge"), object: nil)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let secondController = secondController
{
NotificationCenter.default.removeObserver(CtrlB.self, name: NSNotification.Name(rawValue: "badge"), object: nil)
}
}
//Also don't forget to remove listening on deinit
deinit
{
if let secondController = secondController
{
NotificationCenter.default.removeObserver(secondController, name: NSNotification.Name(rawValue: "badge"), object: nil)
}
}
}
class CtrlB: UIViewController {
//Here you go with notification...
static func badge (notification: NSNotification) {
// blah blah
}
}

NotificationCenter inside my own class

I'm trying to set an observer for notifications inside my own class.
Let us say we have something like below for example,
public class MyClass {
var center: NotificationCenter
public init() {
center = NotificationCenter.default
}
public run() {
center.addObserver(self, selector: #selector(test), name: .UIKeyboardDidShow, object: nil)
}
func test() {
Print("TESTED!")
}
}
and in my ViewController,
override func viewDidLoad() {
super.viewDidLoad()
let myClass = MyClass()
myClass.run()
}
then this actually won't work if I tap textField or something to make the keyboard up.
The NotificationCenter certainly works if I do this without using MyClass, or if I change the object registering as an observer like below:
center.addObserver(ViewController.self, selector: #selector(test), name: .UIKeyboardDidShow, object: nil)
and then of course I should implement my test function inside the ViewController as a class function.
But this isn't the way that I want. Any suggestions or advices that would explain why this isn't working?
The myClass will be destroyed at the end of viewDidLoad. Because there is nothing references to it. You should create a property in ViewController:
var myClass: MyClass?
override func viewDidLoad() {
super.viewDidLoad()
myClass = MyClass()
myClass?.run()
}

Resources