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

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) {
}
}

Related

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!!!")
}
}

Add a generic delegate to a base class in Swift

Ideally, I want to create a BaseViewController class that takes in a protocol type (of a delegate) and have a weak variable as the delegate. Something like this:
class BaseViewController<Delegate: AnyObject> {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
}
}
And then inherit from a view controller like so:
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class SomeViewController: BaseViewController<MyDelegate> {
func doSomething() {
delegate?.funcA()
}
}
This doesn't work as the compiler complains:
'BaseViewController' requires that 'MyDelegate' be a class type
How can I work this around to achieve what I need?
Thanks in advance :)
Thats because in swift protocols doesn't confirm to them selves, you can't use "MyProtocol" as concrete type confirming to protocol "MyDelegate"
What you can rather do is
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class BaseViewController<Delegate: MyDelegate> {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
//keeping OPs code as is
}
}
class SomeOtherDelegateClass: MyDelegate {
func funcA() {
//some code here
}
func funcB() {
//some code here
}
}
class SomeViewController: BaseViewController<SomeOtherDelegateClass> {
func doSomething() {
self.delegate?.funcA()
}
}
EDIT 1:
As OP mentioned in comment, he is trying to introduce a generic property in BaseViewController that will simply hold a weak reference to any instance whose class is decided/declared by Child classes of BaseViewController using generics, I am simplifying the above answer a bit
Try this
protocol MyDelegate {
func funcA()
func funcB()
}
class BaseViewController<Delegate> where Delegate: AnyObject {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
//keeping OPs code as is
}
}
class SomeOtherDelegateClass: MyDelegate {
func funcA() {
//some code here
}
func funcB() {
//some code here
}
}
class SomeViewController: BaseViewController<SomeOtherDelegateClass> {
func doSomething() {
self.delegate?.funcA()
}
}
protocol MyDelegate2 {
func funcABCD()
}
class SomeOtherDelegateClass2: MyDelegate2 {
func funcABCD() {
//some code here
}
}
class SomeViewController2: BaseViewController<SomeOtherDelegateClass2> {
func doSomething() {
self.delegate?.funcABCD()
}
}
TBH, I really dont see much of benefit of this design! Probably you need to revisit the code structure and see if you can come up with better code structure :)
You should set your delegate as a constraint for the generic type T in BaseViewController:
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class Delegated1: MyDelegate {
func funcA() { print("A1") }
func funcB() {}
}
class Delegated2: MyDelegate {
func funcA() { print("A2") }
func funcB() {}
}
class BaseViewController<T: MyDelegate>: UIViewController {
var delegate: T?
func doSomething() {
delegate?.funcA()
}
}
class SomeViewController1: BaseViewController<Delegated1> {}
class SomeViewController2: BaseViewController<Delegated2> {}
class TestClass {
let viewController1: SomeViewController1 = {
let viewController = SomeViewController1(nibName: nil, bundle: nil)
viewController.delegate = .init()
return viewController
}()
let viewController2: SomeViewController2 = {
let viewController = SomeViewController2(nibName: nil, bundle: nil)
viewController.delegate = .init()
return viewController
}()
// prints:
// A1
// A2
func myFunc() {
viewController1.doSomething()
viewController2.doSomething()
}
}

NotificationCenter observer with generic class

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() {
}
}

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()
}

NotificationCenter to pass data in Swift

I am working on a test project in Swift 3. I am trying to pass textField string from one class to another class using NotificationCenter. I am trying to workout the answer from this link: pass NSString variable to other class with NSNotification and how to pass multiple values with a notification in swift
I tried few answers from the above link but nothing worked.
My code:
//First VC
import UIKit
extension Notification.Name {
public static let myNotificationKey = Notification.Name(rawValue: "myNotificationKey")
}
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func sendData(_ sender: AnyObject) {
let userInfo = [ "text" : textView.text ]
NotificationCenter.default.post(name: .myNotificationKey, object: nil, userInfo: userInfo)
}
}
//SecondVC
import Foundation
import UIKit
class viewTwo: UIViewController {
#IBOutlet weak var result: UILabel!
override func viewDidLoad() {
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived(_:)), name: .myNotificationKey, object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self, name: .myNotificationKey, object: nil)
}
func notificationReceived(_ notification: Notification) {
guard let text = notification.userInfo?["text"] as? String else { return }
print ("text: \(text)")
result.text = text
}
}
I am not sure whats wrong with the code. Above, code originally marked as a answered which, I found from the first link. Code been converted to Swift.
Don't use object parameter to pass data. It is meant to filter notifications with the same name, but from a particular object. So if you pass some object when you post a notification and another object when you addObserver, you won't receive it. If you pass nil, you basically turn off this filter.
You should use userInfo parameter instead.
First, it is better to define notification's name as extension for Notification.Name. This approach is much safer and more readable:
extension Notification.Name {
public static let myNotificationKey = Notification.Name(rawValue: "myNotificationKey")
}
Post notification:
let userInfo = [ "text" : text ]
NotificationCenter.default.post(name: .myNotificationKey, object: nil, userInfo: userInfo)
Subscribe:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived(_:)), name: .myNotificationKey, object: nil)
}
Unsubscribe:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self, name: .myNotificationKey, object: nil)
}
Method to be called:
func notificationReceived(_ notification: Notification) {
guard let text = notification.userInfo?["text"] as? String else { return }
print ("text: \(text)")
}
Pass text using userInfo which is a optional Dictionary of type [AnyHashable:Any]? in Swift 3.0 and it is [NSObject : AnyObject]? in swift 2.0
#IBAction func sendData(_ sender: UIButton) {
// post a notification
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notificationName"), object: nil, userInfo: ["text": textValue.text])
print(textValue) // textValue printing
}
in viewDidLoad
// Register to receive notification
NotificationCenter.default.addObserver(self, selector: #selector(self. incomingNotification(_:)), name: NSNotification.Name(rawValue: "notificationName"), object: nil)
and in incomingNotification
func incomingNotification(_ notification: Notification) {
if let text = notification.userInfo?["text"] as? String {
print(text)
// do something with your text
}
}
In your sendData method pass textField.text into Notification object and in your incomingNotification do this:
guard let theString = notification.object as? String else {
print("something went wrong")
return
}
resultLabel.text = theString
You can also use blocks to pass data between controllers.
Use dispatchQueue because your notification is posting before your view load. Therefore just give delay in your notification Post.
#IBAction func sendData(_ sender: AnyObject) {
let userInfo = [ "text" : textView.text ]
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
NotificationCenter.default.post(name: .myNotificationKey, object: nil, userInfo: userInfo) }
}
Just use NotificationCenter to send and receive the notification that state changed. Pass the data through some a data model such as an ObservableObject (particularly if you're bridging between SwiftUI and UIKit). Here's are a couple of extension that make it pretty simple for lightweight inter-component signaling without the cumbersome forgettable semantics of NotificationCenter. (Of course you define your own Notification.Name constants to be meaningful to your purpose).
extension Notification.Name {
static let startEditingTitle = Notification.Name("startEditingTitle")
static let stopEditingTitle = Notification.Name("stopEditingTitle")
}
extension NotificationCenter {
static func wait(_ name : Notification.Name) async {
for await _ in NotificationCenter.default.notifications(named: name) {
break;
}
}
static func post(_ name : Notification.Name) {
NotificationCenter.default.post(name: name, object: nil)
}
#discardableResult static func postProcessing(_ name: Notification.Name, using block: #escaping (Notification) -> Void) -> NSObjectProtocol {
NotificationCenter.default.addObserver(forName: name, object: nil, queue: OperationQueue.main, using: block)
}
}
To post a notification is as simple as:
NotificationCenter.post(.startEditingTitle)
And to receive the notification elsewhere:
NotificationCenter.postProcessing(.startEditingTitle) (_ in {
print("Started editing title")
}
Or to just wait for the notification instead of asynchronously handling it:
NotificationCenter.wait(.startEditingTitle)

Resources