Observing Notification in Today Widget which is posted from main app - swift

I have declared notification name in a swift file which has target membership of both main app and today widget:
let SpecialKey = Notification.Name("howLongNotif")
Then in my main app view controller, I am posting notification when location is updated (background mode for location updates is on):
NotificationCenter.default.post(name: SpecialKey, object: nil, userInfo: nil)
In my today widget viewDidLoad, I am observing it like this:
NotificationCenter.default.addObserver(self, selector: #selector(TodayViewController.dataReceived), name: SpecialKey, object: nil)
and have:
func dataReceived(_notification: NSNotification) {
print("data received")
}
But dataReceived function is never invoked.
I tested by moving the observer and dataReceived function to main app and it works fine there.

Your main app and today is extension are run as separate processes on the phone. NSNotificationCenter only works within a single process.
To pass information between your extension and main app, you can use NSUserDefaults or a file in the shared container.

Related

Swift: updating diffable data source with animatingDifference crash app

i am trying to update dataSource on iphone SE/ iOS14 device and i got app crash:
var snapshot = NSDiffableDataSourceSnapshot<Section, SettingsProfileMenuModel>()
snapshot.appendSections(Section.allCases)
snapshot.appendItems(menuList, toSection: Section.settingsProfileSection)
self.dataSource.apply(snapshot, animatingDifferences: false)
Error is on line "self.dataSource.appl....": Deadlock detected: calling this method on the main queue with outstanding async updates is not permitted and will deadlock. Please always submit updates either always on the main queue or always off the main queue
If i try to do it like this, but it doesn't even update
DispatchQueue.main.async {
self.dataSource.apply(snapshot, animatingDifferences: false)
}
What can be wrong? Also i have one more crash when i try to close keyboard by tapping on "close" button.
as I understand the reason for this subscription to the keyboard
NotificationCenter.default.addObserver(self,
selector: #selector(presentKeyboard),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(hideKeyboard),
name: UIResponder.keyboardWillHideNotification,
object: nil)
I mean, maybe it's important. My screen has a tableView from the top of the screen to the bottom. If i set animatingDifference = true, all fine besides crash of keyboard

Selector function not being entered when observer is added

In my first view controller, I post the notification with the following code:
NotificationCenter.default.post(name: Notification.Name("date"), object: formattedDate)
I then "receive" the notification in a second view controller with the following code:
func receiveNotification () {
NotificationCenter.default.addObserver(self, selector: #selector(self.didGetTheDate(_:)), name: NSNotification.Name("date"), object: nil)
}
#objc
func didGetTheDate(_ notification: Notification) {
print("In did get date")
date = notification.object as! String
}
However, the function "didGetTheDate" never gets called.
I have triple checked that the function "receiveNotification" gets called as I have added print statements to check this.
Can somebody please help me with this.
NSNotificacionCenter is a variation of the Observer Pattern, you can read about it here
This means that you will have to register an Observer before posting any notification. If you post anything before that, the NSNotificationCenter will look at the observer for name and will see that nobody is listening for it, thus nothing will happen.

NSNotification observer not being removed unless it has been triggered once

In summary
If a user takes a photo with an overlay on VC1 it triggers a notification. The corresponding observer gets removed by my method as a I present VC2.
If the user doesn't take a photo on VC1, the notification observer isn't trigger. The same removal method is called as VC2 is presented but the observer stays active and causes unwanted behaviours with VC2 photo capture.
Full explanation
I have a registration form where a user can take an optional photo of their face. I'm using a simple UIImagePickerController and presenting a 'passport' style overlay.
if sourceType == .camera {
imagePickerController.cameraDevice = .front
let overlay = PassportOverlayView(frame: imagePickerController.view.frame)
imagePickerController.cameraOverlayView = overlay
}
The overlay covers the 'retake' and 'choose' buttons of the UIImagePickerController so I observe the following NSNotifications to remove the overlay when a photo taken, and re-add it if required should the user wish to retake their photo.
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object:nil, queue:nil, using: { note in
self.imagePickerController.cameraOverlayView = nil
})
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidRejectItem"), object:nil, queue:nil, using: { note in
self.imagePickerController.cameraOverlayView = PassportOverlayView(frame: self.imagePickerController.view.frame)
})
Everything works as intended. I remove the notifications before the next UIViewController is pushed onto the stack.
func removeObservers(){
print("remove Observers")
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidRejectItem"), object: nil)
}
If the user has taken a photo and therefore called the observer adding a photo on the next UIViewController, which doesn't require an overlay, works fine.
If however, the user doesn't take a photo on the first UIViewController [which is perfectly fine for the purposes of my app] they run into an issue on the second UIViewController.
In both scenerios the removeObservers() function is called. "remove Observers" is printed to the console on both occasions. Yet when the users tries to take a photo from within the second UIViewController the app crashes as it Unexpectedly found nil while implicitly unwrapping an Optional value and points me to the self.imagePickerController.cameraOverlayView = nil line of the _UIImagePickerControllerUserDidCaptureItem notification.
I understand the error, it's trying to remove a cameraOverlayView that isn't there. What I can't understand is why the observer is still there when I believe it's already been removed. If the user takes the first photo with the cameraOverlayView and triggers the _UIImagePickerControllerUserDidCaptureItem notification there isn't an issue. The observer is removed and subsequent UIImagePickerControllers don't have the same issue.
Any help would be greatly appreciated.
I think I know what your problem is. A retain cycle is causing the observer to not be removed successfully. The reason why you have a retain cycle is because you are using a strong reference to self in the closure where you set your notifications. Here is how you fix it:
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object:nil, queue:nil, using: { [unowned self] note in
self.imagePickerController.cameraOverlayView = nil
})
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidRejectItem"), object:nil, queue:nil, using: { [unowned self] note in
self.imagePickerController.cameraOverlayView = PassportOverlayView(frame: self.imagePickerController.view.frame)
})
Again, you shouldn't have to worry about removing the observers, so try to run your code without removeObserver and it should work.
Hope this helps.

how to detect tap home-button twice in ios9

In my app which am writing to learn swift and iOS9, I'm trying to pause my NStimer when user double click the home button and becomes at app switcher, accoridng to programming ios9 matt neuberg, when The user double-clicks the Home button, The user can now work in the app switcher interface. If your app is frontmost, your app delegate receives this message:
applicationWillResignActive:
But my timer only pauses when I tap home button once and when I tap twice and have the app switcher, I see my timer counting, any ideas?
Try to add this lines in your AppDelegate.swift:
static let kAppDidBecomeActive = "kAppDidBecomeActive"
static let kAppWillResignActive = "kAppWillResignActive"
func applicationDidBecomeActive(application: UIApplication) {
// Your application is now the active one
// Take into account that this method will be called when your application is launched and your timer may not initialized yet
NSNotificationCenter.defaultCenter().postNotificationName("kAppDidBecomeActive", object: nil)
}
func applicationWillResignActive(application: UIApplication) {
// Home button is pressed twice
NSNotificationCenter.defaultCenter().postNotificationName("kAppWillResignActive", object: nil)
}
In addition, set your view controller as the observer to those notifications:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(pauseGame), name: AppDelegate.kAppWillResignActive, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(resumeGame), name: AppDelegate.AppDelegate.kAppDidBecomeActive, object: nil)
}

Call function on app termination in Swift

How can I call a function that is within an SKScene class when my app is terminated by the user?
I need to modify a value and save it to NSUserDefauts when the app is terminated.
You can register to receive a notification when your app is about to terminate. To do this, add an observer to the default notification center by
Swift 5:
NotificationCenter.default.addObserver(self, selector: #selector(saveData), name: UIApplication.willTerminateNotification, object: nil)
Swift 3/4:
// Add this to didMoveToView in your SKScene subclass
NotificationCenter.default.addObserver(self, selector: #selector(saveData), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
Add the following method to your SKScene subclass. The method will be called before the app terminates. It must be "exposed" to Objective-C by adding #objc so the notifier can use #selector().
#objc func saveData(notification:NSNotification) {
// Save your data here
print("Saving data...")
}
In Swift 3 and 4 you have something like that:
in your viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(toDoSomething), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
and than you have that method to be called
func suspending () {
print("toDoSomething...")
}
There are a few methods in UIAppDelegate that will help you. Take a look at applicationWillTerminate(_:) and applicationWillResignActive(_:). From there you see what state your app is in and do perform the appropriate actions.