Objective C- Push Notifications not working with Cloudkit - ios

I have an app that I'm building, and I'm trying to keep track of a subscription using Cloudkit. I have a RecordType called Notifications with one field of type String called NotificationText. For some reason, when I add a new record, the app does not receive it. Here's what I have done so far:
Registered for Cloudkit in the "Capabilities" section of the app.
Added the Required background modes key to the info.plist file for remote-notifications
Saved the Subscription to the database using:
CKSubscription *subscription = [[CKSubscription alloc]
initWithRecordType:#"Notifications"
predicate:[NSPredicate predicateWithValue:YES]
options:CKSubscriptionOptionsFiresOnRecordCreation];
CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
notificationInfo.alertLocalizationKey = #"NotificationText";
notificationInfo.shouldBadge = YES;
notificationInfo.soundName = #"";
subscription.notificationInfo = notificationInfo;
[publicDB saveSubscription:subscription
completionHandler:^(CKSubscription *subscription, NSError *error) {
if (error)
[self handleError:error];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"sub"];
}
];
Requested permission from the user to send push notifications using:
UIApplication *application = [UIApplication sharedApplication];
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:nil];
[application registerUserNotificationSettings:notificationSettings];
[application registerForRemoteNotifications];
Implemented the -(void)application:(UIApplication *)application didReceiveRemoteNotification:(non null NSDictionary *)userInfo fetchCompletionHandler:(non null void (^)(UIBackgroundFetchResult))completionHandler; in the AppDelegate.m file.
Now I go into the Cloudkit Dashboard and create a new record of RecordType Notifications, and nothing happens. Am I doing something wrong? Am I missing something?

After much searching and banging my head against a wall, I found my problem.
PUSH NOTIFICATIONS DO NOT WORK ON SIMULATORS!
I hooked up an iPod to the app and, bang! Notification was received.

Related

iOS) Push Notification using parse when app is background

I looked a lot questions and answers for the same issue.
And What I know now is that
'didReceiveRemoteNotification' is not called when the app is background.
didReceiveRemoteNotification is just called when app is foreground or when user comes to app by tapping notification if its in background.
application:didFinishLaunchingWithOptions: is called when user taps on notification to open the app if its killed.
My situation is below:
I'm using parse to push and get notification.
I'm successful getting notification when app is foreground and I'm handling data and show alert and list notification at notification list.
But I can not get notification when app is background or killed.
But I check my app can get notification when my app is background or killed when using 'custom audience' of Parse as I want. (Like other famous applications)
What I want to is I want to get notification when app is background or killed like when I use 'custom audience' of Parse.
But when I use just API of Parse , it doesn't work as i want.
Is there anything I'm missing now?
My registering code is below:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSLog(#"didReceiveRemoteNotification executed : %#",userInfo);
NSInteger bettingIDX = [[userInfo objectForKey:#"betting_idx"] integerValue];
NSString *message = [userInfo objectForKey:#"message"];
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = [NSString stringWithFormat:#"%#",message];
NSUUID *uuid = [NSUUID UUID];
NSString *notificationKey = [uuid UUIDString];
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
NSLog(#"DeviceToken : %#", deviceToken );
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation setDeviceTokenFromData:deviceToken];
[currentInstallation saveInBackground];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIUserNotificationSettings* notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
[Parse setApplicationId:#"applicationID"
clientKey:#"clientID"];
[PFAnalytics trackAppOpenedWithLaunchOptions:launchOptions];
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}
Use method didFinishLaunchingWithOptions:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
if let launchOptions = launchOptions as? [String: AnyObject],
let userInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] as? [String: String] {
}
return true
}
The official documentation says the following:
The user taps the default button in the alert or taps (or clicks) the
app icon. If the default action button is tapped (on a device running
iOS), the system launches the app and the app calls its delegate’s
application:didFinishLaunchingWithOptions: method, passing in the
notification payload (for remote notifications) or the
local-notification object (for local notifications). Although
application:didFinishLaunchingWithOptions: isn’t the best place to
handle the notification, getting the payload at this point gives you
the opportunity to start the update process before your handler method
is called.

Parse Push Notification Get Notification Channel - iOS

i recently set up push notifications for my app using Parse. I have been able to get the notifications and the data sent but now i want to get the channel to which the notification has been sent. Is there any way to do so in ios?
Have you checked this guide? It's really helpful.
https://www.parse.com/tutorials/ios-push-notifications
Here's a quick summary. Create development and production certificates and then attach them to your developer account for the app that you want to be able to send pushes to. After attaching these certificates redownload them, change them to .p12 files w Keychain Access and then upload them to Parse.com. In Xcode make sure to go into your account via preferences and refresh it so you'll have an updated provisioning profile. I'd do this in the stable version of Xcode and not the beta.
Finally after doing all that you can start coding. In your app delegate attach this code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
UIUserNotificationTypeBadge |
UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes
categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
...
}
Please note that the following code demonstrates how to add a user to a channel... This is Parse's default code and I actually suggest tweaking it a bit. They set it up so that your channels will be reset to global every time the application is launched. I'm not sure that you even need to invoke the method "registerUserNotificationSetting" at all after attaching the device's token to the backend. Take not this method will generate an API request...
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// Store the deviceToken in the current installation and save it to Parse.
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation setDeviceTokenFromData:deviceToken];
// This is Parse's default code
currentInstallation.channels = #[ #"global" ];
// I would change it to something like this
if (currentInstallation.channels.count == 0) {
currentInstallation.channels = #[ #"global" ];
}
[currentInstallation saveInBackground];
}
Finally the last bit of code just deals with what the application does if it receives a notification while in the foreground:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
[PFPush handlePush:userInfo];
}
Good luck!
When you send the notification just add the channel name to the notifications data.
For example:
PFPush *push = [[PFPush alloc] init];
[push setChannel:#"my_channel"];
NSDictionary *data = #{#"alert":#"Hi there.",#"channel":#"my_channel"};
[push setData:data];
[push sendPushInBackground];
When you receive the notification you can simple get the channel from the payload/userInfo:
NSString *channel = payload[#"channel"];

Check if user allowed local notifications or not. iOS 8. Obj-C

Is there no simple way of testing if the user has allowed local notifications? I noticed a warning in my console after I denied the sending of local notifications. When the relevant event occurred it said that the app tried to send a notifcation even though the user didn't allow it. I want to check if it's allowed before attempting to display a notification. See the comment in my condition, how do I do that?
My code is:
app = [UIApplication sharedApplication];
-(void)showBackgroundNotification:(NSString *) message {
//check if app is in background and check if local notifications are allowed.
if (app.applicationState == UIApplicationStateBackground /*&& LocalNotificationsAreAllowed*/){
UILocalNotification *note = [[UILocalNotification alloc]init];
note.alertBody = message;
note.fireDate = [NSDate dateWithTimeIntervalSinceNow:0.0];
[app scheduleLocalNotification :note];
}
}
I get prompt the user for permission like so:
UIUserNotificationSettings *settings;
if ([app respondsToSelector:#selector(registerUserNotificationSettings:)])
{
settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeAlert|UIUserNotification TypeSound) categories:nil];
[app registerUserNotificationSettings:settings];
}
couldn't I use settings object?
EDIT: I think I have solved it. This seems to work.
-(void)showBackgroundNotification:(NSString *) message {
if (app.applicationState == UIApplicationStateBackground && [app currentUserNotificationSettings].types != UIUserNotificationTypeNone){
UILocalNotification *note = [[UILocalNotification alloc]init];
note.alertBody = message;
note.fireDate = [NSDate dateWithTimeIntervalSinceNow:0.0];
[app scheduleLocalNotification :note];
}
}
Here's what I use for less specific situations:
+ (BOOL)notificationsEnabled {
UIUserNotificationSettings *settings = [[UIApplication sharedApplication] currentUserNotificationSettings];
return settings.types != UIUserNotificationTypeNone;
}
I usually keep a set of these types of methods in a notification manager.

CloudKit CKSubscription Notification Basics

I have been fiddling about with CKSubscription today. I have a few queries regarding it.
I setup my subscription like so:
-(void)addSubscriptions
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"TRUEPREDICATE"];
CKSubscription *newRecordSub = [[CKSubscription alloc] initWithRecordType:_recordTypeName predicate:predicate options:CKSubscriptionOptionsFiresOnRecordCreation | CKSubscriptionOptionsFiresOnRecordUpdate | CKSubscriptionOptionsFiresOnRecordDeletion];
CKNotificationInfo *noteInfo = [CKNotificationInfo new];
noteInfo.alertBody = #"Update!";
noteInfo.shouldBadge = YES;
noteInfo.soundName = UILocalNotificationDefaultSoundName;
newRecordSub.notificationInfo = noteInfo;
CKContainer *container = [CKContainer defaultContainer];
CKDatabase *privateDatabase = [container privateCloudDatabase];
[privateDatabase saveSubscription:newRecordSub completionHandler:^(CKSubscription *subscription, NSError *error) {
}];
}
QUESTION 1:
When creating my subscription, surely I only need to save this to my container once only? As this is called overtime the app runs, I get an error simply saying, duplicate subscription, so the app works as expected, but should I be approaching this differently and not running every time?
Next I setup to receive notifications with my AppID and registered for them in my AppDelegate.
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
Lastly, I handle these notification as below:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
CKNotification *note = [CKNotification notificationFromRemoteNotificationDictionary:userInfo];
if (note.notificationType == CKNotificationTypeQuery)
{
CKQueryNotification *queryNote = (CKQueryNotification *)note;
CKRecordID *recordID = [queryNote recordID];
[[NSNotificationCenter defaultCenter] postNotificationName:kCloudKitNewRecordFlightLog object:recordID];
}
}
QUESTION 2:
Why would I not be receiving any notifications at this point? All my code runs and my app is updated when new records are added/deleted, updated. However, no sound, badge or banner is displayed to notify the user.
QUESTION 3:
Is it a sensible approach to when receiving the cloud kit notification to then have a local notification send a message to the relevant view to update the records?
Yes, you only need to set them once. You could query the subscriptions (using fetchAllSubscriptionsWithCompletionHandler) to see if you should create it. Or just ignore the error.
That could work, or you could setup a delegate meganism.

Parse.com - Set pointer from user Class to Installation class

Hi I have an app that I have set up with push notifications and they are working. My last step is that when a user opens the app and is asked if they want to allow push notification, I want to set a pointer from that user to the installation id associated with that phone. I can do that using this
PFInstallation *currentInstalation = [PFInstallation currentInstallation];
NSString *installationObjectId = currentInstalation.objectId;
[currentUser setObject:installationObjectId forKey:#"installationString"];
[currentUser setObject:currentInstalation forKey:#"installation"];
Here is a pic of part of my user class to clarify
But I don't want to make this save every time the user opens the app, I just want to do it the once if it has not been set yet. So I was going to use this if statement to check if it had been set yet
if (![currentUser[#"installationString"] isEqual:installationObjectId]) {
//save it here
}
But the problem comes if the user taps, "don't allow push notifications" because then there is no installation object set, so the installation object for that phone/ user is null and the above if statement gives the error below
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: 'Can't use nil for keys or values on PFObject. Use NSNull for values.'
And the app fails. Is there another/ better way to check if the pointer has been set, so that if the user taps "don't allow" and then reopens the app it won't quit out.
Thanks for the help in advance I really appreciate it!!!
EDIT
APP DELAGTE CODE
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)]) {
UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
UIUserNotificationTypeBadge |
UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes
categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
} else {
// Register for Push Notifications before iOS 8
[application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeSound)];
}
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// Store the deviceToken in the current installation and save it to Parse.
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation setDeviceTokenFromData:deviceToken];
[currentInstallation saveInBackground];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
[PFPush handlePush:userInfo];
}
And I have it set this way because I have a cloud function that send pushes to user and and get a pointer from the user to the installation id to send the push, maybe I could consider flipping that?
And if the user clicks don't allow. Doesn't Parse.com not save an installation id for that device?
Thanks
This should help PFInstallation *currentInstalation = [PFInstallation currentInstallation];
NSString *installationObjectId = currentInstalation.objectId;
[currentUser setObject:installationObjectId forKey:#"installationString"];
[currentUser setObject:currentInstalation forKey:#"installation"];
SWIFT:
var newUser = PFUser()
var currentInstallation = PFInstallation.currentInstallation()
let installationObjectId: NSString = currentInstallation.installationId
newUser.setObject(installationObjectId, forKey: "installationString")
newUser.setObject(currentInstallation, forKey: "installation")
newUser.saveInBackgroundWithBlock { (success, error) -> Void in
if error == nil {
print("success")
} else {
print(error)
}
}

Resources