watchOS2 app and iPhone app communication - iphone

In the watchOS1, we had a method “openParentApplication”. This method communicated with the phone application even when it wasn’t running in foreground or background and fetched a reply immediately. I need something similar for watchOS2. I want my watch application to communicate immediately with the phone app even if my iPhone application is not running. Methods like updateApplicationContext:error:, sendMessage:replyHandler:errorHandler: and transferUserInfo: are not helpful in this scenario.
Please can someone suggest me a better approach to achieve this?

Actually sendMessage:replyHandler:errorHandler: is doing exactly what you are asking for. As long as your watch is connected to your phone it immediately gets a response to the message. This is working when the app is in the foreground, in the background or not running at all.
Here is how you set it up:
In the WatchExtension:
Setup the session. Typically in your ExtensionDelegate:
func applicationDidFinishLaunching() {
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
And then send the message when you need something from the app:
if WCSession.defaultSession().reachable {
let messageDict = ["message": "hello iPhone!"]
WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in
print(replyDict)
}, errorHandler: { (error) -> Void in
print(error)
}
}
In the iPhone App:
Same session setup, but this time also set the delegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
...
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
And then implement the delegate method to send the reply to the watch:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
replyHandler(["message": "Hello Watch!"])
}
This works whenever there is a connection between the Watch and the iPhone. If the app is not running, the system starts it in the background. So, basically it just works like openParentApplication(_:reply:)

Related

Firebase Notification - Firebase Cloud Messaging

In my application I'm using Firebase Messaging and I'm testing to receive notification.
I'm using Postman as Rest service to configure the notification's body like:
{
"to": "/topics/test",
"priority": "high",
"notification": {
"title": "Test",
"body": "New",
"badge": "0"
},
"data": {
"foo": "bar"
}
}
Certificate is ok. I don't understand how to start programmatically a ViewController looking at the data passed..For example if data contains:
"data": {
"foo": "viewcontroller1"
}
I'd like to start ViewController1 when user clicks on the notification.
I can only print data in AppDelegate? How can I use values passed?
This is my AppDelegate.swift:
import UIKit
import Firebase
import FirebaseMessaging
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
let notificationTypes : UIUserNotificationType = [UIUserNotificationType.Alert, UIUserNotificationType.Badge, UIUserNotificationType.Sound]
let notificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
application.registerForRemoteNotifications()
application.registerUserNotificationSettings(notificationSettings)
return true
}
// [START refresh_token]
func tokenRefreshNotification(notification: NSNotification) {
let refreshedToken = FIRInstanceID.instanceID().token()!
print("InstanceID token: \(refreshedToken)")
// Connect to FCM since connection may have failed when attempted before having a token.
connectToFcm()
}
// [START connect_to_fcm]
func connectToFcm() {
FIRMessaging.messaging().connectWithCompletion { (error) in
if (error != nil) {
print("Unable to connect with FCM. \(error)")
} else {
print("Connected to FCM.")
}
}
}
//Receive and handle messages
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// Print message ID.
print("Value for foo -> \(userInfo["foo"])")
//start viewcontroller programmatically
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
Can someone please explain me please?
Lets handle the code in didReceiveRemoteNotification First we extract which view controller should we present:
let type = userInfo["foo"] as! String
if type == "viewcontroller1" {
// here we go to start the view controller
}
You will need to use helping method to find the top most view controller to present on top of it.
func getTopViewController()->UIViewController{
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
// topController should now be your topmost view controller
}
return UIViewController()
}
To start a ViewController you should make an identifier for that in Storyboard. lets say its also called : viewcontroller1 then :
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("viewcontroller1") as! viewcontroller1
self.getTopViewController().presentViewController(vc, animated: true, completion: nil)
Note: When receiving the notification you'll need to check if the app was in background or it was in app or it was outside the app . For each one has different handling of how and when you'll need to show or present your view controller.

Updating Glance data in watchOS2.2

I'm hoping you smart people can help me as most of the data online is out of date.
I have an iPhone app that displays financial information.
I would like to present this on a watch glance screen.
I can get the app to send the dictionary of the latest information and the glance does update live if both the Glance screen and phone app are open.
I would like to know how to use the Glance screen to ask the phone app for the latest information.
The phone app will probably be closed so it would need waking up and then asked for the current information.
I'm using swift 7 and WatchOS 2.2 and IOS 9.3
A lot of information here on Stackoverflow refers to watchOS 1 so no longer works.
I look forward to your solutions.
Look into WCSession as there are different methods for sending different types of data. This implementation is sending a dictionary.
Must setup a WCSession on both watch and phone devices. AppDelegate in didFinishLaunchingWithOptions: and I use the ExtensionDelegate in its init method. Be sure to import WatchConnectivity when using WCSession. Using the AppDelegate as a WCSessionDelegate below.
// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Setup session on phone
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
return true
}
// WCSessionDelegate method receiving message from watch and using callback
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
// Reply with a dictionary of information based on the "message"
replyHandler(["Key": "Value"])
}
}
Setup WCSession on the watch:
// ExtensionDelegate.swift
override init() {
let session = WCSession.defaultSession()
session.activateSession()
}
Send message, consisting of a dictionary, to the phone in order to receive information in the callback:
// GlanceController.swift
WCSession.defaultSession().sendMessage(["Please give Glance data": "Value"], replyHandler:{ (response) in
// Extract data from response dictionary
}) { (error) in
// Handle error
}

Download in background in Swift

I'm trying to make my app download images in background. But when I press [Home] button, the app stop download. Is there any way to make it continue download even when I use another app? I have seen some apps can do like that but I don't know how.
This is what I've tried so far.
//
// AppDelegate.swift
// Swift-TableView-Example
//
// Created by Bilal ARSLAN on 11/10/14.
// Copyright (c) 2014 Bilal ARSLAN. All rights reserved.
//
import UIKit
import WebKit
protocol DownloadInBackgroundDelegate {
func downloadInBackgroundDidFinish(chapterid:Int, chaptername:String, storyid:Int, progressPercent:Float)
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var shareCache = NSURLCache()
var downloadDelegate:DownloadInBackgroundDelegate? = nil
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
var navigationBarAppearace = UINavigationBar.appearance()
application.setStatusBarOrientation(UIInterfaceOrientation.PortraitUpsideDown, animated: false)
self.startDownload()
return true
}
func application(application: UIApplication,
didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
FBAppEvents.activateApp()
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
}
func applicationDidReceiveMemoryWarning(application: UIApplication) {
NSURLCache.sharedURLCache().removeAllCachedResponses()
}
func application(application: UIApplication, willChangeStatusBarOrientation newStatusBarOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
application.windows
}
func startDownload(){
var filesPath = [String]()
filesPath.append("https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/iphoneappprogrammingguide.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/MobileHIG.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/NetworkingOverview.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/AVFoundationPG.pdf")
filesPath.append("http://manuals.info.apple.com/MANUALS/1000/MA1565/en_US/iphone_user_guide.pdf")
downloadFiles(0, filesPath: filesPath)
}
func downloadFiles(index: Int, filesPath: [String]) -> Void {
var imgURL: NSURL = NSURL(string: filesPath[index].stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
var fileCacheName = String(format: "%04d", index)
dispatch_async(dispatch_get_main_queue(), {
var fileExt = (data != nil && error == nil) ? Utility.checkImageType(data) : ""
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let imagePath = paths.stringByAppendingPathComponent("\(fileCacheName).png")
if data.writeToFile(imagePath, atomically: false)
{
println("saved")
}
if index < filesPath.count - 1
{
var nextIndex:Int = index + 1
self.downloadFiles(nextIndex, filesPath: filesPath)
}
})
})
}
}
Answer
Based on the comment below, I found this thread : objective c - Proper use of beginBackgroundTaskWithExpirationHandler . I can solve my problem with it.
For downloading and storing of the images, instead of writing the logic yourself, I suggest you use some well known libraries, like:
https://github.com/Haneke/Haneke
https://github.com/rs/SDWebImage
Reason behind that is those libraries are well tested, quite robust and mainly very easy to use for basic tasks.
Now for the background download, there is beginBackgroundTaskWithExpirationHandler: that is specifically designed to do that. When you use it, you will get few more minutes to execute whatever you need (after that limit, your application will get terminated no matter what).
You can write following methods:
func beginBackgroundTask() -> UIBackgroundTaskIdentifier {
return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})
}
func endBackgroundTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
When you want to use it, you just simple begin / end the task when starting / finishing the download call:
// Start task
let task = self.beginBackgroundTask()
// Do whatever you need
self.someBackgroundTask()
// End task
self.endBackgroundTask(task)
Hope it helps!
Use beginBackgroundTaskWithExpirationHandler: from the UIApplication to start a background task when the app enters the background
See Apple's document on multitasking background execution for details. See download in background in iphone its a similar question.

WCSession Failing to Activate

I am having a problem with the WatchKit Connectivity Session failing to activate when I call the session.activateSession() method. This is the code I am using to set up the session.
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self // conforms to WCSessionDelegate
session.activateSession()
print("Session has been activated")
}
However, I have placed a breakpoint on the print line and when I inspect the session object, it says the sessionActivated property is still false, even after calling activateSession. I don't appear to be getting any sort of bug when I call activate session, so I assume it should have worked, but this does not seem to be the case.
Furthermore, if I try and use the sendMessage method on the session object later in my code like this -
let message = ["request": "fireLocalNotification"]
session.sendMessage(
message, replyHandler: { (replyMessage) -> Void in }) { (error) -> Void in
print(error.localizedDescription)
}
I receive an error code "The operation couldn’t be completed. (WCErrorDomain error 7004.)" which I looked up which means "WCErrorCodeSessionNotActivated." This is yet another reason why I think the activateSession method isn't calling correctly. I have even tried running the activateSession method the line directly before I send the message, but I still receive the error. If anyone could help explain what is going on, that would be wonderful, thank you! :)
You should activate the WatchConnectivity session on both the WatchKit Extension and the iOS app target. For example you might do it in the InterfaceController's
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
if WCSession.isSupported() {
let wcsession = WCSession.defaultSession()
wcsession.delegate = self
wcsession.activateSession()
wcsession.sendMessage(["update": "list"], replyHandler: { (dict) -> Void in
print("InterfaceController session response: \(dict)")
}, errorHandler: { (error) -> Void in
print("InterfaceController session error: \(error)")
})
}
}
and in the AppDelegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if WCSession.isSupported() {
let wcsession = WCSession.defaultSession()
wcsession.delegate = self
wcsession.activateSession()
}
return true
}
What I have noticed in several examples is that people tend to set a delegate only in the class which handles requests, e.g. if the watch was to send a message to the iOS app a delegate would only be set in the iOS app. This is WRONG. As the WatchConnectivity clearly states, you MUST set the delegate in both circumstances, otherwise you'll get the 7004 error.
Since "activateSession()" changed to "activate()" in Xcode 8 ,You need to add and extension for your class to delegate the session ( WCSessionDelegate ), and extend it with the function:
func session(_ session: WCSession, activationDidCompleteWith
activationState: WCSessionActivationState, error: Error?)
In order to ensure that the asynchronous method "activate" finishes successfully.
In your case:
extension YourInterfaceControllerClass : WCSessionDelegate {
func session(_ session: WCSession,
didReceiveMessage message: [String : Any],
replyHandler: #escaping ([String : Any]) -> Void)
{
//this function is mandatory and can be empty
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?)
{
// Your code to be executed after asynchronous activation is completed:
let message = ["request": "fireLocalNotification"]
session.sendMessage(
message, replyHandler: { (replyMessage) -> Void in }) { (error) -> Void in
print(error.localizedDescription)
}
//....
}
}
Are you using big number values?
NSDictionary *userInfo = #{
#"a1":#(1000000000), // 1000000000
#"a2":#(10000000000), // 1e+10
#"a3":#(100000000000), // crash!
};
[[WCSession defaultSession] transferUserInfo:userInfo];
On the above code, the value of key "a3" is dangerous, it causes Apple Watch crash.
Once you send the list, it remains in Apple Watch until reinstall the watch app.
(This crash occurs on a device, not on a simulator)

Not able to set Interactive Push Notifications on iOS8

I was already able to set Interactive LOCAL notifications, but the Remote notifications aren't working. I'm using Parse.com to send the JSON
My AppDelegate.Swift looks like this:
//
// AppDelegate.swift
// SwifferApp
//
// Created by Training on 29/06/14.
// Copyright (c) 2014 Training. All rights reserved.
//
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
UINavigationBar.appearance().barTintColor = UIColor.orangeColor()
UINavigationBar.appearance().tintColor = UIColor.whiteColor()
Parse.setApplicationId("eUEC7O4Jad0Kt3orqRouU0OJhkGuE20n4uSfrLYE", clientKey: "WypmaQ8XyqH26AeWIANttqwUjRJR4CIM55ioXvez")
let notificationTypes:UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
let notificationSettings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
return true
}
func application(application: UIApplication!, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings!) {
UIApplication.sharedApplication().registerForRemoteNotifications()
}
func application(application: UIApplication!, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData!) {
let currentInstallation:PFInstallation = PFInstallation.currentInstallation()
currentInstallation.setDeviceTokenFromData(deviceToken)
currentInstallation.saveInBackground()
}
func application(application: UIApplication!, didFailToRegisterForRemoteNotificationsWithError error: NSError!) {
println(error.localizedDescription)
}
func application(application: UIApplication!, didReceiveRemoteNotification userInfo:NSDictionary!) {
var notification:NSDictionary = userInfo.objectForKey("aps") as NSDictionary
if notification.objectForKey("content-available"){
if notification.objectForKey("content-available").isEqualToNumber(1){
NSNotificationCenter.defaultCenter().postNotificationName("reloadTimeline", object: nil)
}
}else{
PFPush.handlePush(userInfo)
}
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
on Parse, I'm setting the Push payload like this:
{
"alert": "Tune in for the World Series, tonight at 8pm EDT",
"badge": "Increment",
"sound": "chime",
"category": "FIRST_CATEGORY"
}
and I receive the push, but not with the custom buttons I've set.
I'm not sure if my problem is the same to yours (Make sure your problem is not due to Parse). Just post my solution here in case anyone else would encounter the same issue.
My problem is in the notification category.
Make sure you have set the category when registering the notification settings (I'm using Objective-C, no much difference):
UIMutableUserNotificationCategory *notificationCategory = [[UIMutableUserNotificationCategory alloc] init];
notificationCategory.identifier = #"CallNotificationCategory";
[notificationCategory setActions:#[declineAction, answerAction] forContext:UIUserNotificationActionContextDefault];
NSSet *categories = [[NSSet alloc] initWithObjects:notificationCategory, nil];
And, when you send remote notification, make sure you have "category" in the payload and the value is the same as you defined in the client. In my case it's something like:
{
"alert": "Tune in for the World Series, tonight at 8pm EDT",
"badge": "Increment",
"sound": "chime",
"category": "CallNotificationCategory"
}
This is in case if anyone comes across this problem while using Firebase Remote Notifications.
Just ask the backend developer to send:
"notification" : {
"title" : YOUR_TITLE,
"body" : YOUR_BODY,
"click_action" : YOUR_CATEGORY_NAME
}
You need to pass categories while registering for APNS.
Look at my sample :
var replyAction : UIMutableUserNotificationAction = UIMutableUserNotificationAction()
replyAction.identifier = "REPLY_ACTION"
replyAction.title = "Yes, I need!"
replyAction.activationMode = UIUserNotificationActivationMode.Background
replyAction.authenticationRequired = false
var replyCategory : UIMutableUserNotificationCategory = UIMutableUserNotificationCategory()
replyCategory.identifier = "REPLY_CATEGORY"
let replyActions:NSArray = [replyAction]
replyCategory.setActions(replyActions, forContext: UIUserNotificationActionContext.Default)
replyCategory.setActions(replyActions, forContext: UIUserNotificationActionContext.Minimal)
let categories = NSSet(object: replyCategory)
let settings : UIUserNotificationType = UIUserNotificationType.Sound | UIUserNotificationType.Alert | UIUserNotificationType.Badge
UIApplication.sharedApplication().registerUserNotificationSettings(UIUserNotificationSettings(forTypes: settings, categories: categories))
UIApplication.sharedApplication().registerForRemoteNotifications()
This worked for me to get interactive push notifications displaying and working in Swift with Parse. Note that you need to create a UIMutableNotificationAction for each interactive button you want to create. Source below goes into much more detail of configuring options for buttons.
In your app delegate file:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let notificationTypes:UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
var notificationActionAccept :UIMutableUserNotificationAction = UIMutableUserNotificationAction()
notificationActionAccept.identifier = "ACCEPT_IDENTIFIER"
notificationActionAccept.title = "Accept"
notificationActionAccept.destructive = true
notificationActionAccept.authenticationRequired = false
notificationActionAccept.activationMode = UIUserNotificationActivationMode.Background
var notificationCategory:UIMutableUserNotificationCategory = UIMutableUserNotificationCategory()
notificationCategory.identifier = "CallNotificationCategory"
notificationCategory .setActions([notificationActionAccept], forContext: UIUserNotificationActionContext.Default)
let notificationSettings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: NSSet(array:[notificationCategory]))
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
return true
}
And in Parse Cloud when you send the push, you would match the category to the UIMutableUserNotificationCategory like #Xialin mentioned above.
Looks like you weren't setting categories or at least not setting them until after you had registered the UINotificationSettings - you need to set them before or it won't work.
I got most of this info at the link below. It goes into more detail if needed. Hope this helps:
http://thecodeninja.tumblr.com/post/90742435155/notifications-in-ios-8-part-2-using-swift-what-is