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()
}
Related
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!!!")
}
}
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() {
}
}
I want to call method in a class as soon as observer get Notification in another one. The problem is that I cannot call one class from another, because I will get recursion call then.
1) Controller class with Player instance:
// PlayerController.swift
// player
import UIKit
import MediaPlayer
class NowPlayingController: NSObject {
var musicPlayer: MPMusicPlayerController {
if musicPlayer_Lazy == nil {
musicPlayer_Lazy = MPMusicPlayerController.systemMusicPlayer()
let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(self.playingItemDidChange),
name: NSNotification.Name.MPMusicPlayerControllerNowPlayingItemDidChange,
object: musicPlayer_Lazy)
musicPlayer_Lazy!.beginGeneratingPlaybackNotifications()
}
return musicPlayer_Lazy!
}
//If song changes
func playingItemDidChange(notification: NSNotification) {
//somehow call updateSongInfo() method from 2nd class
}
//Get song metadata
func getSongData() -> (UIImage, String?, String?) {
let nowPlaying = musicPlayer.nowPlayingItem
//...some code
return (albumImage, songName, artistAlbum)
}
func updateProgressBar() -> (Int?, Float?){
let nowPlaying = musicPlayer.nowPlayingItem
var songDuration: Int?
var elapsedTime: Float?
songDuration = nowPlaying?.value(forProperty: MPMediaItemPropertyPlaybackDuration) as? Int
elapsedTime = Float(musicPlayer.currentPlaybackTime)
return(songDuration, elapsedTime)
}
}
2) View controller which should be updated when Player Controller get notification
// MainViewController.swift
// player
import UIKit
class MainViewController: UIViewController {
let playerController = PlayerController()
#IBOutlet weak var albumView: UIImageView!
#IBOutlet weak var songLabel: UILabel!
#IBOutlet weak var artistAlbum: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Start updating progress bar
Timer.scheduledTimer(timeInterval: 0.5,
target: self,
selector: #selector(MainViewController.updateProgressBar),
userInfo: nil,
repeats: true)
}
private func updateSongInfo(){
(albumView.image!, songLabel.text, artistAlbum.text) = playerController.getSongData()
}
private func updateProgressBar(){
(progressBar.maximumValue, progressBar.value) = playerController.playingItemProgress()
}
}
Solution for Swift 3:
In NowPlayingController:
let newSongNotifications = NSNotification(name:NSNotification.Name(rawValue: "updateSongNotification"), object: nil, userInfo: nil)
func playingItemDidChange(notification: NSNotification) {
NotificationCenter.default.post(newSongNotifications as Notification)
}
And in other controller:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.updateSongInfo), name: NSNotification.Name(rawValue: "updateSongNotification"), object: nil)
}
You can post a notification from within your custom object where you need it:
let notification = NSNotification(name:"doSomethingNotification", object: nil, userInfo: nil)
NotificationCenter.defaultCenter.postNotification(notification)
And then in your other view controller in which you want to execute something in response to this notification, you tell it to observe the notification in viewDidLoad(). The selector you pass in is the method you want to be executed when the notification is received.
override func viewDidLoad(){
super.viewDidLoad()
NotificationCenter.addObserver(self, selector: #selector(self.doSomething), name: "doSomethingNotification", object: nil)
}
You can use delegate method to update MainViewController
I want to create chat using apple notification and I want when I get the notification pass the data to my Table View to update it
I am trying to do a sample example to test how to pass data form app delegate to viewController and it not working can anyone help please
My ViewController Code
class ViewController: UIViewController {
var fileDirectory: String = String()
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.refreshView(_:)), name: "refreshView", object: nil)
}
func refreshView(notification: NSNotification) {
fileDirectory = delegate.filePath
print("tester")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I want to access refreshView function from appDelegate
You wrote below line is right in ViewController in viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.refreshView), name: "AnyThingYouWant", object: nil)
}
But Add this line in appDelegate where you want to call ViewController.refreshView method
NotificationCenter.default.post(name: "AnyThingYouWant", object: self)
}
if you want to call refreshView method when got notification then write above line in didReceiveLocal/RemoteNotification Method
I have a viewcontroller class ViewController with collectionView. Also I have singleton class FacebookManager for fetching data from facebook.
What I want to do is to run a method in facebook class and then call a method in ViewController to reload collectionView.
I tried to make a reference to ViewController in Facebook manager by setting
class FacebookManager {
static let sharedInstance = FacebookManager()
var vc:ViewController?
}
Then setting in ViewController
class ViewController: {
func viewDidLoad() {
FacebookManager.sharedInstance.vc = self
}
}
And then calling in FacebookManager
func myMethod() {
vc.collectionView.reloadData()
}
But this doesn't work.
How to do this properly?
To provide communication between two or multiple classes there are two method that are recommended.
1) Delegation
2) Notification
In your given code to implement delegation method we have to create protocol and delegate property in FacebookManager. And assign view controller as a delegate to FacebookManger
Example:
protocol FacebookManagerDelegate: class {
func refreshData()
}
class FacebookManager {
var weak delegate:FacebookManagerDelegate?
func myMethod() {
delegate?.refreshData()
}
}
class ViewController: FacebookManagerDelegate {
ViewDidLoad() {
FacebookManager.sharedInstance.delegate = self
}
func refreshData() {
self.collectionView.reloadData()
}
}
But you are using singleton class therefore in future multiple class would be using this class and if you want to notify multiple classes use Notification method, which pretty easy to implement and should be use in singleton class
Post notification in FacebookManger whenever you want to notify:
class FacebookManager {
func myMethod() {
NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: nil)
}
}
class ViewController {
ViewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector:#selector(reloadData(_:)), name: NotificationName, object: nil)
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func reloadData(notification: NSNotification) {
self.collectionView.reloadData()
}
}