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.
Related
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.
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"];
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.
I want to implement push notification on my iPad app but availability of app is out of Apple Store.
Can we implement push notification on ipad app for non Apple Store?
what is exact process to implement push notification in case of if your app will not go to apple store?
Your app does not need to be on the app store. All you need is to integrate the Parse iOS SDK into your project:
https://www.parse.com/docs/ios_guide
This is how you register for notifications:
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)])
{
UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
UIUserNotificationTypeBadge |
UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes
categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
}
This is how you send a push:
NSMutableArray *channels = [[NSMutableArray alloc] init];
PFPush *push = [[PFPush alloc] init];
[push setChannels:channels]; // Set your channels somewhere
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
NSDictionary *data = #{#"alert":[NSString stringWithFormat:#"You have a new service request from %#", appDelegate.user[#"name"]],#"badge":#1,#"push_type":#"Request",#"sound":#"default"};
[push setData:data];
[push sendPushInBackground];
I want to implement a custom screen that informs my users why I'm about to ask for push notification permissions. After they press a button in that custom screen I present the iOS push notification permission dialog with [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
I only want to show this custom screen once if the user hasn't already seen the push notification permission dialog. I cannot use [[UIApplication sharedApplication] enabledRemoteNotificationTypes] == UIRemoteNotificationTypeNone as this will also return 'none' if the user decided to not allow push notifications.
Any ideas anyone?
You could use NSUserDefaults :
#define kPushNotificationRequestAlreadySeen #"PushNotificationRequestAlreadySeen"
if(![[NSUserDefaults standardUserDefaults] boolForKey:kPushNotificationRequestAlreadySeen]) {
// Notify the user why you want to have push notifications
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:... delegate:self ...];
[alertView show];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kPushNotificationRequestAlreadySeen];
}
else {
// Already allowed -> register without notifying the user
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
}
And
- (void)alertView:(UIAlertView*)alertView didDismissWithButtonIndex:(NSInteger)index {
// Actually registering to push notifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
}
This is just a work-around but will work in the majority of cases.
When iOS prompts the user to allow push notifications, applicationWillResignActive is called.
After calling registerForRemoteNotificationTypes...start a timer (or dispatch_after) that will show a new alert to the user explaining that they'll need to use the Settings app to enable notifications. Then in applicationWillResignActive cancel the timer. This way, the explanation alert will only show if applicationWillResignActive is never called.
The only problem I see with this is if the app resigns active for other reasons at the exact time that the timer is going...but this puts you in no worse a situation that you're already in and has the advantage of working in the majority of cases.
The solution that I've found is a bit of a hack, but it works.
You need to call registerUserNotificationSettings for two different notificationSettings - one without a notificationCategory and one with a notificationCategory:
//Request notification permission
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
//Request notification permission again, but with a category with no actions
UIMutableUserNotificationCategory *category = [[UIMutableUserNotificationCategory alloc] init];
category.identifier = #"com.xyz.markNotificationPopupShownCategoryIdentifier";
UIUserNotificationSettings *notificationSettingsWithCategory = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:[NSSet setWithObject:category]];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettingsWithCategory];
The didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings method in the app delegate will be called two times, and irrespective of the user's answer in the permission notification, after the second call, the current notification settings will contain the category. As long as the category count is greater than 0, you can know for sure that the notifications permission dialog has been shown:
if ([UIApplication sharedApplication].currentUserNotificationSettings.categories.count > 0) {
NSLog(#"Notifications permission has been asked");
} else {
NSLog(#"Notifications permission hasn't been asked");
}