NSUserNotification only display once if invoked in application:continueUserActivity:restorationHandler - macos

I am developing a simple spotlight extension on macOs. My app will index some content into spotlight. When I type some keywords, spotlight will trigger app's application:continueUserActivity:restorationHandler. And the application() will send a local notification. The code looks like this:
func application(_ application: NSApplication, continue userActivity: NSUserActivity, restorationHandler: ([NSUserActivityRestoring]) -> Void) -> Bool {
let notification = NSUserNotification()
notification.title = "title"
notification.subtitle = "subtitle"
notification.soundName = NSUserNotificationDefaultSoundName
NSUserNotificationCenter.default
.deliver(notification)
}
And I use macOs 10.14.3.
If my app does not running, and I type the keyword in spotlight, the spotlight will start my app and run the application(). And I will see the notification correctly.
However, if I type keyword again(my app is running background), the spotlight still tigger the application() method but the notification does not appeared. Even worse, I could see the notification in the notification center.
Anyone can make help? Thanks.

To be displayed separately, each notification mush have a unique identifier.
notification.identifier = "different_kind"
From the documentation (emphasis mine):
The identifier is unique to a notification. A notification delivered
with the same identifier as an existing notification replaces the
existing notification rather than causing the display of a new
notification.
There are also limitations on how quickly you can post new notifications, so be aware of that.
Finally, if your'e developing for Mojave, you should probably be looking into UNUserNotificationCenter, as NSUserNotificationCenter is deprecated.

Related

How to validate a deep link when opening a new window in SwiftUI on macOS?

TLDR: How to distinguish between a link that the app sends internally and a link that originates from outside of the app?
Details:
If a SwiftUI app has multiple windows (scenes), a window can be opened programmatically via a deep link as explained here.
Contextual data can be included in the link. For example a book id in a library app.
fancyLibraryApp:viewer/book/42
Add the following modifier to your view to process this url and to view a book.
var body: some Scene
{
Text("Title of \(book)").onOpenURL
{
self.id = $0.description.lastPathComponent //parse the url as appropriate
}
}
Book is a value or object that you retrieve from the id passed through the url.
The id should be a binding so that the view gets updated.
A link can be send from elsewhere within your app but it can also be send from an external source such as safari or the Terminal app (open fancyLibraryApp:viewer/book/42).
Therefore the url must be validated. Is the user allowed to read this book or not? Is the user logged in or not?
Apple explains how to validate an incoming url here, but only for iOS.
NSApplicationDelegate has a function application(_ application: NSApplication, open urls: [URL])
However that function is called after the view modifier onOpenURL.
So how can the url be verified?

Xamarin Forms - How to open specific page after clicking on notification when the app is closed?

I'm actually working on a Xamarin Forms application that need push notifications. I use the Plugin.PushNotification plugin.
When the app is running in the foreground or is sleeping (OnSleep), I have no problem to open a specific page when I click on a notification that I receive. But I was wondering how can I do that when the app is closed. Thanks!
I finally found the answer by myself and I want to share it in case someone needs it.
Nota bene: according to the official documentation of the plugin, it's Xam.Plugin.PushNotification that is deprecated. I use the new version of this plugin, Plugin.PushNotification which uses FCM for Android and APS for iOS.
There is no significant differences to open a notif when the app is running, is sleeping or is closed. Just add the next callback method in the OnCreate method (MyProject.Droid > MainApplication > OnCreate) and FinishedLaunching method (MyProject.iOS > AppDelegate > FinishedLaunching):
CrossPushNotification.Current.OnNotificationOpened += (s, p) =>
{
// manage your notification here with p.Data
App.NotifManager.ManageNotif(p.Data);
};
Common part
App.xaml.cs
// Static fields
// *************************************
public static NotifManager NotifManager;
// Constructor
// *************************************
public App()
{
...
NotifManager = new NotifManager();
...
}
NotifManager.cs
public class NotifManager
{
// Methods
// *************************************
public void ManageNotif(IDictionary<string, object> data)
{
// 1) switch between the different data[key] you have in your project and parse the data you need
// 2) pass data to the view with a MessagingCenter or an event
}
}
Unfortunately there is no succinct answer for either platform. Generally speaking, you need to tell the OS what to do when it starts the app as a result of the push notification. On both platforms, you should also consider what API level you are targeting, otherwise it won't work or even crash the app.
On iOS, you will need to implement this method in AppDelegate appropriately: FinishedLaunching(UIApplication application, NSDictionary launchOptions). The launchOptions will have the payload from the push notification for you to determine what to do with it (e.g. what page to open). For more information on iOS, Xamarin's documentation is a good place to start.
Android has a more complicated topology in terms of more drastic differences between API levels, whether you are using GCM/FCM, as well as requiring more code components. However, to answer the question directly, you will need to handle this in OnCreate(Bundle savedInstanceState) of your main Activity. If you are using Firebase, the push notification payload is available in Intent.Extras. Again, Xamarin's documentation has a good walkthrough.
Finally, note that the Plugin.PushNotification library you are using has been deprecated. I suggest you either change your library and/or your implementation soon. Part of the reason that library has been deprecated is because Google has deprecated the underlying Google Cloud Messaging (GCM) service, which will be decommissioned on April 11, 2019.

Play Push Notification Sound Effect when app is not running (Mac)

I'm adding Push Notifications to my Mac app. The notifications are showing up just fine, but for some reason I can't get the sound effects to play the way I would think they should.
I'm passing in a custom sound to play, "custom.caf" in the apns payload. This works currently for our iOS app. It doesn't play anything when passed into the Mac app. Digging around in the very limited Apple documentation, the PushyMac app shows that you have to play the sound manually based on the userInfo dictionary that comes in for the push. If I play the sound manually while the app is running, I get the sound effect to play.
My problem is when the app is closed and not running. I still get push notifications as expected, but no sounds play. From what I can tell, I'm just screwed. I have other Mac apps that still manage to play sounds when they aren't running. Specifically my email app, Spark. So I know it is possible, but I can't figure out what the secret sauce is to pull it off. Please help!!
It's quite simple to implement the custom sound capability for Apple Push Notifications; the problem is that the documentation does not mention what is required to do this.
The critical component that is required is an extension — (eg. using the Today Extension). Creating a new target in your project including the extension, your custom sound(s), and the standard APN functions are all that's required. The key to getting the custom sounds working though while your application isn't running is simply having the extension (and it's included NotificationCenter.framework) present in your application.
Add New Target > Today Extension:
Today Extension > Build Phases > Copy Bundle Resources > Custom Sound:
The only specific APN functions that need to be in your main application are the following:
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSApplication.shared().
registerForRemoteNotifications(matching:
[.alert, .sound, .badge])
NSApp.dockTile.badgeLabel = nil
}
func application(_ application: NSApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
}
func application(_ application: NSApplication, didReceiveRemoteNotification
userInfo: [String : Any]) {
}
func application(_ application: NSApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("could not register for notfications: \(error)")
}
If you are getting two sounds playing at the same time it's likely that you've got code that is calling AudioServicesPlaySystemSound and can be removed as the NotificationCenter.framework seems to handle all that without adding anything additional.

iOS8 app crashes on Xcode6.0.1 using HealthKit

Im actually trying to convert Apple's sample code to swift.
I created an app and an APPID for it in the devcenter. I checked the entitlement for HealthKit (the ones for IAP & GC are greyed and checked automatically).
When the provisioning profile I create for it is downloaded to Xcode and I go into Preferences in Xcode and look at my account's provisioning profiles I can see the name of the profile plus the expiration date and then there are some icons for entitlements. But the provisioning profile I created with HealthKit doesnt have any icon for it, just the 2 default ones, is this normal:
because for some reason the app crashes upon requesting authorization with this error:
2014-10-02 12:16:13.241 SwimFit[549:8824] -[__NSCFConstantString _allowAuthorizationForSharingWithEntitlements:]: unrecognized selector sent to instance 0x107dc1ce0
2014-10-02 12:16:13.251 SwimFit[549:8824] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFConstantString _allowAuthorizationForSharingWithEntitlements:]: unrecognized selector sent to instance 0x107dc1ce0'
If I try to run it on the device I get this:
I have created:
AppId for my app
Activated that AppID for HealthKit
Created Dev provisioning profile for that AppID
Activated HealthKit Capabilities in General
I see the entitlements.plist is created with com.apple.developer.healthkit = yes
The info.plist does have the healthkit value for required capabilities
The only weird thing I did this time and I used to do differently for other apps is that when I clicked on build/run a some point, I let Xcode create an AppID and I get this from devcenter...i cant upload the image but basically all my previous AppIDs are named after the app. This one because its made by xcode is named: Xcode iOS App ID com santiapps SwimFit but its bundle identifier is correct at: com.santiapps.SwimFit. And so is the dev profile: iOS Team Provisioning Profile: com.santiapps.SwimFit and its the one in my Build Settings. Originally I had SwimFit because that was the name of the app so Xcode created an automatic AppID for it with a ProvProfile for it as well. I then thought maybe I should create the appID and provprofile so I did it manually and tried calling it SwimFit2. Both give the same error.
What else could I be missing?
Here is the code:
//1. Add healthstore property
var healthStore: HKHealthStore? //error if not optionally unwrapped
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//2. Ask for permissions
if (HKHealthStore.isHealthDataAvailable() == true) {
self.healthStore = HKHealthStore() //needs to be var for it to work?
var writeDataTypes = self.dataTypesToWrite()
var readDataTypes = self.dataTypesToRead()
self.healthStore!.requestAuthorizationToShareTypes(writeDataTypes, readTypes: readDataTypes, completion: { (success: Bool, error: NSError!) -> Void in
if (!success) {
NSLog("You didn't allow HealthKit to access these read/write data types. In your app, try to handle this error gracefully when a user decides not to provide access. The error was: %#. If you're using a simulator, try it on a device.", error)
return
} else {
NSLog("success")
}
// Handle success in your app here.
self.setupHealthStoreForTabBarControllers()
})
}
return true
}
Here is a link with a screen capture: http://youtu.be/BBagkNTpfQA
I had the same crash yesterday. The problem is that you put the wrong data type (NSString) for the data types into the method.
The requestAuthorizationToShareTypes method requires you to pass a set of HKBaseType objects, in my case HKQuantityType objects.
So instead of:
[_healthStore requestAuthorizationToShareTypes:[NSSet setWithObject:HKQuantityTypeIdentifierBodyMassIndex]
readTypes:[NSSet setWithArray:inputDataTypes]
completion:
Use this one:
HKQuantityType* type = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMassIndex];
[_healthStore requestAuthorizationToShareTypes:[NSSet setWithObject:type]
readTypes:[NSSet setWithArray:inputDataTypes]
completion:

NSUserNotificationCenter dismiss notification

I'm trying to use the new Mountain Lion NSUserNotificationCenter for my application (which isn't too hard actually). Posting notifications works like a charm via
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
userNotification.title = #"Some title";
userNotification.informativeText = #"Some text";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
However, i'd like to dismiss all notifications that are on the screen once the app gains focus. E.g. like the new Messages app does it. When new messages are received in the background, notifications are shown. When the app becomes active again, these are dismissed automatically and vanish from the screen and from the Notification Center.
To replicate this, I've registered a method to the NSApplicationDidBecomeActiveNotification notification which also gets called succesfully. In there I call [NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications].
This, however, has the effect that notifications that have been collected in the Notification Center are removed while the corresponding "bubbles" that are displayed in the top right corner are still displayed.
Iterating all delivered notifications and removing them each on their own has the exactly same effect, as has using scheduleNotification instead of deliverNotification.
Am I the only one experiencing this, or am I missing something to dismiss the on-screen part and the Notification Center part of a notification programatically?
The Messages app is probably using the private NSUserNotificationCenter _removeAllDisplayedNotifications or _removeDisplayedNotification: method.
You can try to use these methods to test if this is what you are looking for. Just add this category interface to declare the methods:
#interface NSUserNotificationCenter (Private)
- (void)_removeAllDisplayedNotifications;
- (void)_removeDisplayedNotification:(NSUserNotification *)notification;
#end
Unfortunately, since these are undocumented methods, you can not use them in an app distributed through the App Store. If this is indeed what you are looking for, then you should file a bug and ask for these methods to become part of the public API.
As of 10.9, the following methods remove any displayed notifications:
// Clear a delivered notification from the notification center. If the
// notification is not in the delivered list, nothing happens.
- (void)removeDeliveredNotification:(NSUserNotification *)notification;
// Clear all delivered notifications for this application from the
// notification center.
- (void)removeAllDeliveredNotifications;
The behavior seems to have changed since 10.8, as any displayed notifications are removed as well when these methods are called (thanks #0xced for clarification).
removeDeliveredNotification is removing the displayed notification for me (on 10.11), the caveat being the identifier on the notification must be set.

Resources