I have implemented PushKit. I've followed these steps:
1.) https://stackoverflow.com/a/38184769/4970453
2.) https://stackoverflow.com/a/28562124
I am able to get didUpdatePushCredentials device token.
working in --> iPhone 5s , iPhone6 Plus
didUpdatePushCredentials Not working in --> iPhone6 and iPhone7
I am using same cerificates and Build for all devices. Don't know the exact issue.
If anyone have faced this kind of problem, please share work-arounds.
My code and Certificate Link
code -> https://www.dropbox.com/sh/x2615t7xn8mavs3/AADbX5nBuF5_08YNPX8wI59ga?dl=0
cer -> https://www.dropbox.com/sh/70l4htj1c46emog/AABxBalaoN1JP22dQp8-mNXGa?dl=0
Solution -----> I have changed Bundle identifier And create New certificate with New BundleId.
Make sure that in the certificates page you have the push notifications enabled in the App ID.
Also try using a 3G/4G connection. Network configuration may be not letting the device to obtain the token.
It should work in all devices when integrated properly. there is nothing changed for any device specific.
You can cross verify your steps.
Swift
import UIKit
import PushKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,PKPushRegistryDelegate {
var window: UIWindow?
var isUserHasLoggedInWithApp: Bool = true
var checkForIncomingCall: Bool = true
var userIsHolding: Bool = true
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if #available(iOS 8.0, *){
let viewAccept = UIMutableUserNotificationAction()
viewAccept.identifier = "VIEW_ACCEPT"
viewAccept.title = "Accept"
viewAccept.activationMode = .Foreground
viewAccept.destructive = false
viewAccept.authenticationRequired = false
let viewDecline = UIMutableUserNotificationAction()
viewDecline.identifier = "VIEW_DECLINE"
viewDecline.title = "Decline"
viewDecline.activationMode = .Background
viewDecline.destructive = true
viewDecline.authenticationRequired = false
let INCOMINGCALL_CATEGORY = UIMutableUserNotificationCategory()
INCOMINGCALL_CATEGORY.identifier = "INCOMINGCALL_CATEGORY"
INCOMINGCALL_CATEGORY.setActions([viewAccept,viewDecline], forContext: .Default)
if application.respondsToSelector("isRegisteredForRemoteNotifications")
{
let categories = NSSet(array: [INCOMINGCALL_CATEGORY])
let types:UIUserNotificationType = ([.Alert, .Sound, .Badge])
let settings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: types, categories: categories as? Set<UIUserNotificationCategory>)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
}
}
else{
let types: UIRemoteNotificationType = [.Alert, .Badge, .Sound]
application.registerForRemoteNotificationTypes(types)
}
self.PushKitRegistration()
return true
}
//MARK: - PushKitRegistration
func PushKitRegistration()
{
let mainQueue = dispatch_get_main_queue()
// Create a push registry object
if #available(iOS 8.0, *) {
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
// Set the registry's delegate to self
voipRegistry.delegate = self
// Set the push type to VoIP
voipRegistry.desiredPushTypes = [PKPushTypeVoIP]
} else {
// Fallback on earlier versions
}
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) {
// Register VoIP push token (a property of PKPushCredentials) with server
let hexString : String = UnsafeBufferPointer<UInt8>(start: UnsafePointer(credentials.token.bytes),
count: credentials.token.length).map { String(format: "%02x", $0) }.joinWithSeparator("")
print(hexString)
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) {
// Process the received push
// Below process is specific to schedule local notification once pushkit payload received
var arrTemp = [NSObject : AnyObject]()
arrTemp = payload.dictionaryPayload
let dict : Dictionary <String, AnyObject> = arrTemp["aps"] as! Dictionary<String, AnyObject>
if isUserHasLoggedInWithApp // Check this flag then only proceed
{
if UIApplication.sharedApplication().applicationState == UIApplicationState.Background || UIApplication.sharedApplication().applicationState == UIApplicationState.Inactive
{
if checkForIncomingCall // Check this flag to know incoming call or something else
{
var strTitle : String = dict["alertTitle"] as? String ?? ""
let strBody : String = dict["alertBody"] as? String ?? ""
strTitle = strTitle + "\n" + strBody
let notificationIncomingCall = UILocalNotification()
notificationIncomingCall.fireDate = NSDate(timeIntervalSinceNow: 1)
notificationIncomingCall.alertBody = strTitle
notificationIncomingCall.alertAction = "Open"
notificationIncomingCall.soundName = "SoundFile.mp3"
notificationIncomingCall.category = dict["category"] as? String ?? ""
//"As per payload you receive"
notificationIncomingCall.userInfo = ["key1": "Value1" ,"key2": "Value2" ]
UIApplication.sharedApplication().scheduleLocalNotification(notificationIncomingCall)
}
else
{
// something else
}
}
}
}
//MARK: - Local Notification Methods
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification){
// Your interactive local notification events will be called at this place
}
}
Objective C
#import <UIKit/UIKit.h>
#import <PushKit/PushKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate,PKPushRegistryDelegate>
{
PKPushRegistry *pushRegistry;
}
#property (strong, nonatomic) UIWindow *window;
#end
#import "AppDelegate.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
pushRegistry.delegate = self;
pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
return YES;
}
#define PushKit Delegate Methods
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type{
if([credentials.token length] == 0) {
NSLog(#"voip token NULL");
return;
}
NSLog(#"PushCredentials: %#", credentials.token);
}
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type
{
NSLog(#"didReceiveIncomingPushWithPayload");
}
https://github.com/hasyapanchasara/PushKit_SilentPushNotification
Updated answer
https://drive.google.com/file/d/0B7ooURy3zGWKYW5PZE1aN2pObW8/view?usp=sharing
Updated answer
Below are some trouble shooting option.
(1) Change com identifier and try again
(2) Keep puhkit code in app delegeate
I guess, you can change your bundle identifier and try again, it has to be work with all devices.
For PushKit to work properly, here are steps we need to follow:
Enable Push Notifications in project Capabilities
Enable Remote Notifications in Background Modes
Connect device to internet
Allow app to receive push notifications
Please refer to this step-by-step tutorial.
As it is happening for some devices only, may 3rd and 4th steps are responsible.
In my case I wasn't receiving the VoIP token in some of my devices. A HARD RESET of those devices fixed my issue.
Related
I'm trying to make pushkit voip messages work when the application is closed. The calls work and get displayed when app is in the foreground or in the background. But after the user force kills the app, when the notification gets recieved, the app terminates with signal 9 (killed by user/ios).
How can I fix this issue?
I've got background fetch, voip, audio and push notifications enabled in my app.
Also tried removing all the Unity methods, putting the Callkit call in the PushRegistry method, creating a new provider when recieving a notification, even subscribing to the UIApplicationDidFinishLaunchingNotification event, but nothing worked.
I've made it so the app is compliant to showing a call when recieving a voip notification. Here's my code:
#objcMembers class CallPlugin: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, CXProviderDelegate {
static var Instance: CallPlugin!
var provider: CXProvider!
var registry:PKPushRegistry!
var uuid:UUID!
var callController: CXCallController!
//class entry point
public static func registerVoIPPush(_ message: String) {
Instance = CallPlugin()
//Pushkit
Instance.registry = PKPushRegistry(queue: DispatchQueue.main)
Instance.registry.delegate = Instance
Instance.registry.desiredPushTypes = [PKPushType.voIP]
//Callkit
let providerConfiguration = CXProviderConfiguration(localizedName: "testing")
providerConfiguration.supportsVideo = true
providerConfiguration.supportedHandleTypes = [.generic]
Instance.provider = CXProvider(configuration: providerConfiguration)
Instance.provider.setDelegate(Instance, queue: nil)
UnitySendMessage("ResponseHandler", "LogNative", "registration success")
}
//Get token
func pushRegistry( _ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
if type == PKPushType.voIP {
let deviceTokenString = credentials.token.map { String(format: "%02.2hhx", $0) }.joined()
UnitySendMessage("ResponseHandler", "CredentialsRecieved",deviceTokenString)
}
}
//Get notification
func pushRegistry( _ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type:PKPushType, completion: #escaping () -> Void) {
//UnitySendMessage("ResponseHandler", "LogNative", "Got something push")
reportInComingCallWith(uuidString: "111", handle: "Paul", isVideo: false)
completion()
}
//show the call
func reportInComingCallWith(uuidString:String,handle:String,isVideo:Bool) {
//UnitySendMessage("ResponseHandler", "LogNative", "attempting call")
let callUpdate = CXCallUpdate()
callUpdate.remoteHandle = CXHandle(type: .generic, value: handle)
callUpdate.hasVideo = false
uuid = NSUUID() as UUID
provider.reportNewIncomingCall(with: uuid as UUID, update: callUpdate){ (error) in
if let error = error {
UnitySendMessage("ResponseHandler", "LogNative", "error in starting call"+error.localizedDescription)
}
}
}
The issue was that my app was not creating the delegate and pushregistry objects in time for the notification to get fully handled.
I solved it by overriding the app delegate and putting the notification initializations in WillFinishLaunching, like so:
-(BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions{
[CallPlugin registerVoIPPush:#"hmm"];
[super application:application willFinishLaunchingWithOptions:launchOptions];
return true;
}
That way everything is ready for the notification to be handled. I tried to put this in DidFinishLaunching first, but it was already too late for the notification and IOS was killing my application by then.
I'm struggling to get push notifications to work with Swift with iOS 10. Registering seems to be going through successfully, but creating a notificaiton does nothing on the device and returns a nil error. Any ideas what I'm missing?
import Foundation
import UIKit
import UserNotifications
class SPKPushNotifications
{
class func register(application:UIApplication){
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
application.registerForRemoteNotifications()
}
} else {
let notificationTypes: UIUserNotificationType = [UIUserNotificationType.alert, UIUserNotificationType.badge, UIUserNotificationType.sound]
let pushNotificationSettings = UIUserNotificationSettings(types: notificationTypes, categories: nil)
application.registerUserNotificationSettings(pushNotificationSettings)
application.registerForRemoteNotifications()
}
}
class func unregister(application:UIApplication){
application.unregisterForRemoteNotifications()
}
class func create(title:String, body:String, delay:Double, repeats:Bool){
if #available(iOS 10.0, *) {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = UNNotificationSound.default() //idk if we're gonna want something else
content.badge = NSNumber(value:UIApplication.shared.applicationIconBadgeNumber+1)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval:delay, repeats:repeats)
let request = UNNotificationRequest(identifier:title, content:content, trigger:trigger)
let center = UNUserNotificationCenter.current()
center.add(request){ (error) in
print(error)
}
} else {
// Fallback on earlier versions
}
}
class func delete(){
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.removeAllDeliveredNotifications()
} else {
// Fallback on earlier versions
}
}
}
You won't see the notification if the application is in the foreground. Try adding the request to the notification center when the application is in the background.
You can do (for this test only) that by adding a few second sleep and moving your application to the background. Or scheduling the notification to a later time when the application is not running in the foreground.
I am making a "texting app" you can call it and it uses cloudkit and I have been looking everywhere to add notifications that work with cloudkit... Would someone be able to tell me the code to add push notifications for cloudkit in detail because I am very lost... Also I wan't the notifications to go to different "texting rooms" (in cloudkit it would be record types...) For instance I have one record type called "text" and another one called "text 2" I don't want notifications from "text" to get to people who use "text2" and vise versa.
Using Swift 2.0 with El Captain & Xcode 7.2.1
Elia, You need to add this to your app delegate. Which will arrive in a userInfo packet of data, which you can then parse to see which database/app sent it.
UIApplicationDelegate to the class
application.registerForRemoteNotifications() to the
func application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
Than this method
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
let notification = CKQueryNotification(fromRemoteNotificationDictionary: userInfo as! [String : NSObject])
let container = CKContainer(identifier: "iCloud.com")
let publicDB = container.publicCloudDatabase
if notification.notificationType == .Query {
let queryNotification = notification as! CKQueryNotification
if queryNotification.queryNotificationReason == .RecordUpdated {
print("queryNotification.recordID \(queryNotification.recordID)")
// Your notification
}
}
print("userInfo \(userInfo["ck"])")
NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: self, userInfo:dataDict)
}
}
}
}
}
That'll get you started.
You can use this method to check your subscriptions programmatically, of course while your developing you can use the dashboard.
func fetchSubsInPlace() {
let container = CKContainer(identifier: "iCloud.com")
let publicDB = container.publicCloudDatabase
publicDB.fetchAllSubscriptionsWithCompletionHandler({subscriptions, error in
for subscriptionObject in subscriptions! {
let subscription: CKSubscription = subscriptionObject as CKSubscription
print("subscription \(subscription)")
}
})
}
And finally when you got it; you can this routine to ensure you capture any subscriptions you missed while your app was sleeping and make sure that subscriptions don't go to all your devices, once you treated them too.
func fetchNotificationChanges() {
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: nil)
var notificationIDsToMarkRead = [CKNotificationID]()
operation.notificationChangedBlock = { (notification: CKNotification) -> Void in
// Process each notification received
if notification.notificationType == .Query {
let queryNotification = notification as! CKQueryNotification
let reason = queryNotification.queryNotificationReason
let recordID = queryNotification.recordID
print("reason \(reason)")
print("recordID \(recordID)")
// Do your process here depending on the reason of the change
// Add the notification id to the array of processed notifications to mark them as read
notificationIDsToMarkRead.append(queryNotification.notificationID!)
}
}
operation.fetchNotificationChangesCompletionBlock = { (serverChangeToken: CKServerChangeToken?, operationError: NSError?) -> Void in
guard operationError == nil else {
// Handle the error here
return
}
// Mark the notifications as read to avoid processing them again
let markOperation = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIDsToMarkRead)
markOperation.markNotificationsReadCompletionBlock = { (notificationIDsMarkedRead: [CKNotificationID]?, operationError: NSError?) -> Void in
guard operationError == nil else {
// Handle the error here
return
}
}
let operationQueue = NSOperationQueue()
operationQueue.addOperation(markOperation)
}
let operationQueue = NSOperationQueue()
operationQueue.addOperation(operation)
}
}
I'm developing a chat app. I'm using apple push notification service to notify user when he receives new messages. There are two scenarios.
The first when user is chatting and receiving a message, the user shouldn't be notified (meaning that notification shouldn't be shown) and when the app is in background i want to alert user for the messages. Everything is ok except that when app is on background the notification shows the whole JSON object the client is receiving.
The idea is ignore visually notification and if its on background show a local Notification.
This is how i have implemented the notification settings
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
let types: UIUserNotificationType = [UIUserNotificationType.None]
let settings: UIUserNotificationSettings = UIUserNotificationSettings(forTypes: types, categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
return true
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject])
{
//App handle notifications in background state
if application.applicationState == UIApplicationState.Background {
var login_user = LoginUser();
login_user.loadData();
var username:String!;
var message:String!;
if let msg = userInfo["aps"]as? Dictionary<String,AnyObject>
{
if let alert = msg["alert"] as? String{
if let data = alert.dataUsingEncoding(NSUTF8StringEncoding)
{
do
{
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data,options: [])
username = jsonObject["senderUserName"] as! String;
message = jsonObject["content"] as! String!;
DatabaseOperations().insert(DatabaseOperations().STRING_VALUE_CHATING_USERNAME, value: username);
NSNotificationCenter.defaultCenter().postNotificationName("push_notification", object: self)
}
catch
{
}
}
}
}
let localNotification: UILocalNotification = UILocalNotification()
switch(login_user.privacyLevelId)
{
case 1:
localNotification.alertBody = username + ":" + message;
break;
case 2:
localNotification.alertBody = username;
break;
case 3:
localNotification.alertBody = "New Message";
break;
default:
localNotification.alertBody = "New Message";
break;
}
localNotification.alertAction = "Message"
localNotification.fireDate = NSDate(timeIntervalSinceNow: 5)
localNotification.soundName = UILocalNotificationDefaultSoundName
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
}
//App is shown and active
else
{
if let msg = userInfo["aps"]as? Dictionary<String,AnyObject>
{
if let alert = msg["alert"] as? String
{
if let data = alert.dataUsingEncoding(NSUTF8StringEncoding)
{
do
{
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data,options: [])
let sender:String = jsonObject["senderUserName"] as! String;
DatabaseOperations().insert(DatabaseOperations().STRING_VALUE_CHATING_USERNAME, value: sender);
NSNotificationCenter.defaultCenter().postNotificationName("push_notification", object: self)
}
catch
{
}
}
}
}
}
}
I set UIUserNotificationType to NONE. Shouldn't by default the notification shows nothing?
I also have read some other posts, but i couldn't find anything to solve the problem.
Why does UIUserNotificationType.None return true in the current settings when user permission is given?
Hide, do not display remote notification from code (swift)
Any help would be appreciated.
application didReceiveRemoteNotification won't be called if the app is closed or in the background state, so you won't be able to create a local notification. So you need to pass the text you want to display in the aps dictionnary, associated with the alert key.
If you want to pass more information for the active state case, you should add them with a custom key to the push dictionnary.
For example :
{"aps": {
"badge": 1,
"alert": "Hello World!",
"sound": "sound.caf"},
"task_id": 1}
I'm trying to implemented a Switch in my App's local SettingsVC to toggle Notification On/Off. I've decided for now to leave the user registered with the server (both APNS and AppBoy), and just toggle on/off the local presentation of Notifications.
(That's if the user has registered previously. If they haven't ever registered, this code should register the first time they flip to "on")
Here's my code when the user flips the switch:
func didFlipNotificationSwitch() {
let isEnabled = isEnabledRemoteNotificationTypes()
if (isEnabled) {
if #available(iOS 8.0, *) {
let settings = UIUserNotificationSettings(forTypes: [.None], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
} else {
UIApplication.sharedApplication().registerForRemoteNotificationTypes([.None])
}
} else {
if #available(iOS 8.0, *) {
let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
} else {
UIApplication.sharedApplication().registerForRemoteNotificationTypes([.Alert, .Badge, .Sound])
}
}
print("isEnabled \(isEnabled)")
}
isEnabledRemoteNotificationTypes() is a convenience method that checks for registered based on OS - it's works fine. Here it is:
func isEnabledRemoteNotificationTypes() -> Bool {
if #available(iOS 8.0, *) {
let types = UIApplication.sharedApplication().currentUserNotificationSettings()?.types
if (types == UIUserNotificationType.None) {
return false
} else {
return true
}
} else {
let types = UIApplication.sharedApplication().enabledRemoteNotificationTypes()
if (types == UIRemoteNotificationType.None) {
return false
} else {
return true
}
}
}
The problem is that once registered (isEnabled returns true) I'm trying to deregister locally by settings types to .None as you can see in the method above. This doesn't seem to be working. After the line setting them to .None is called, I print out notificationSettings in my appDel didRegisterUserNotificationSettings callback right after setting to .None:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
}
And get this:
Printing description of notificationSettings:
<UIUserNotificationSettings: 0x12684c840; types: (UIUserNotificationTypeAlert UIUserNotificationTypeBadge UIUserNotificationTypeSound);>
Why isn't it registering .None -OR- Is there a better way to turn off Notification presentation while still maintaining server registration?