I have already set up push notifications through Parse and Apple, and I can't seem to find a straight forward way to send push notifications to another user.
For my case, I want to send a user a notification when a user sends a friend request to them. here is the code I am using to save to request to the server:
//get current user
PFQuery *query2 = [PFUser query];
[query2 whereKey:#"username" equalTo:pendingFriendName];
PFUser *userTo = (PFUser *)[query2 getFirstObject];
PFQuery *query = [PFQuery queryWithClassName:#"Follow"];
[query whereKey:#"from" equalTo:[PFUser currentUser]];
// execute the query
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
PFObject *follow = [PFObject objectWithClassName:#"Follow"];
[follow setObject:[PFUser currentUser] forKey:#"from"];
[follow setObject:userTo forKey:#"to"];
[follow saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(succeeded)
{
NSLog(#"success!");
[friendAddedLabel setText:#"Friend Added!"];
}
else
{
NSLog(#"error");
}
}];
}
}];
In addition, is there a way to send a direct reference of the user who sent the request to the user who received the friend request so that it can quickly have the information accessible when the user taps the notifcation instead of going through another query?
First, you have to store the current user into Installation class then send push notification using PFInstallation Query and PFPush.
//save the user into installtion class.
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
if ([PFUser currentUser].objectId)
{
currentInstallation[#"user"] = [PFUser currentUser];
currentInstallation.channels = #[[NSString stringWithFormat:#"user_%#",[PFUser currentUser].objectId]];
NSLog(#"Saving Installation channel = %#",currentInstallation.channels);
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
{
NSLog(#"Current installation updated: Error: %#",error);
}];
}
//Then send push notification to particular user.
PFQuery *queryInstallation = [PFInstallation query];
[queryInstallation whereKey:#"user" equalTo:user];
PFPush *push = [[PFPush alloc] init];
[push setQuery:queryInstallation];
NSDictionary * dic = #{#"alert" : text,#"badge" : #"Increment" , #"sender" : [PFUser currentUser].objectId ,#"MediaId" : mediaObject.objectId};
[push setData:dic];
[push sendPushInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
{
if (error != nil && !succeeded)
{
NSLog(#"SendPushNotification send error.");
}
else
{
NSLog(#"SendPushNotification send success.");
}
}];
Related
I am using the following CKNotification Info and this seems to work fine:
CKNotificationInfo *note = [[CKNotificationInfo alloc] init];
note.alertBody = #"Something Happened";
note.shouldBadge = NO;
note.shouldSendContentAvailable = NO;
When something changes on an iOS device, my Mac app receives a Push notification based on a subscription with this notification. However, didReceiveRemoteNotification is never called so I can't process the event. I need to be able to refresh and fetch new changes. How do I do that?
Calling registerForRemoteNotificationTypes: and implementing didRegisterForRemoteNotificationsWithDeviceToken:
should be enough code, and the App ID should include the Push Notifications service.
I'm using CloudKit in a cross-platform (iOS/OS X) app to synchronize favorites between devices like so:
// OS X specific code
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp registerForRemoteNotificationTypes:NSRemoteNotificationTypeNone];// silent push notification!
}
- (void)application:(NSApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[self.favCon handleCloudKitNotificationWithUserInfo:userInfo];
}
Note the usage of NSRemoteNotificationTypeNone which means silent push notification! This is how I set up CloudKit in the FavController class:
- (void)getOrCreateFavZoneWithCompletionHandler:(successCompletionHandler)handler {
// check if FavZone exists op
__block int createZone = 0;
CKFetchRecordZonesOperation *fetchRecZonesOp = [[CKFetchRecordZonesOperation alloc] initWithRecordZoneIDs:#[[FavController favRecordZoneID]]];
CKModifyRecordZonesOperation *saveRecZoneOp = [[CKModifyRecordZonesOperation alloc] initWithRecordZonesToSave:nil recordZoneIDsToDelete:nil];
fetchRecZonesOp.fetchRecordZonesCompletionBlock = ^(NSDictionary *recordZonesByZoneID, NSError *operationError) {
if (recordZonesByZoneID.count == 0) {// zone doesn't exist
createZone = 1;
CKRecordZone *favZone = [[CKRecordZone alloc] initWithZoneName:UTXAFavZoneName];
saveRecZoneOp.recordZonesToSave = #[favZone];
NSLog(#"Creating new Zone %#", favZone.zoneID.zoneName);
} else {
NSLog(#"Zone %# already exists.", [FavController favRecordZoneID].zoneName);
}
};
// create FavZone op
saveRecZoneOp.modifyRecordZonesCompletionBlock = ^(NSArray *savedRecordZones, NSArray *deletedRecordZoneIDs, NSError *operationError) {
[self successCompletionHandler:(savedRecordZones.count == createZone) error:operationError informDelegate:YES handler:handler];
};
[saveRecZoneOp addDependency:fetchRecZonesOp];
[[FavController favDatabase] addOperation:fetchRecZonesOp];
[[FavController favDatabase] addOperation:saveRecZoneOp];
}
- (void)subscribeToFavChanges:(successCompletionHandler)handler {
// get current subscription
[[FavController favDatabase] fetchSubscriptionWithID:UTXAFavConCKSubscriptionID completionHandler:^(CKSubscription *subscription, NSError *error) {
if (subscription) {
NSLog(#"using existing subscription: %#", subscription);
[self successCompletionHandler:YES error:nil informDelegate:NO handler:handler];
} else {
CKSubscription *sub = [[CKSubscription alloc] initWithZoneID:[FavController favRecordZoneID]
subscriptionID:UTXAFavConCKSubscriptionID
options:0];// "You must specify 0 for this parameter. Zone subscriptions currently do not support any options."
[[FavController favDatabase] saveSubscription:sub completionHandler:^(CKSubscription *subscription, NSError *error) {
NSLog(#"created new subscription: %# %#", subscription, error);
[self successCompletionHandler:(error == nil) error:error informDelegate:YES handler:handler];
}];
}
}];
}
As soon as I add or remove a record on one device, I'll get a notification on all other device, which I handle like so in the FavController class:
/// #abstract Handle push notifications sent by iCloud.
/// #discussion App delegates call this method when they receive a push notification through didReceiveRemoteNotification.
/// Currently, only airport favorites produce a PN, it is of type CKNotificationTypeRecordZone.
/// #param userInfo The userInfo dict tied to each push notification.
- (void)handleCloudKitNotificationWithUserInfo:(NSDictionary *)userInfo {
[self recursivelyCheckForPreviousCloudKitNotifications];
}
- (void)recursivelyCheckForPreviousCloudKitNotifications {
CKFetchNotificationChangesOperation *fetchOp = [[CKFetchNotificationChangesOperation alloc] initWithPreviousServerChangeToken:_defCon.notificationChangeToken];
__weak CKFetchNotificationChangesOperation *weakOp = fetchOp;
fetchOp.notificationChangedBlock = ^(CKNotification *notification) {
[self handleNotification:notification];
};
fetchOp.fetchNotificationChangesCompletionBlock = ^( CKServerChangeToken *serverChangeToken, NSError *operationError) {
NSLog(#"new notification change token: %#", serverChangeToken);
_defCon.notificationChangeToken = serverChangeToken;
if (weakOp.moreComing) {
NSLog(#"more coming!!");
[self recursivelyCheckForPreviousCloudKitNotifications];
} else {
NSLog(#"done handling notification changes.");
}
};
[[FavController favContainer] addOperation:fetchOp];
}
- (void)handleNotification:(CKNotification *)notification {// withCompletionHandler:(successCompletionHandler)handler {
if (notification.notificationType == CKNotificationTypeRecordZone) {// make sure we handle only zone changes
CKRecordZoneNotification *noti = (CKRecordZoneNotification *)notification;
if ([noti.recordZoneID.zoneName isEqualToString:[FavController favRecordZoneID].zoneName]) {
// received an update for the fav zone
[self queuedFavUpdateFromCloud];
} else {
// received an update for an unknown zone
NSLog(#"WARNING: received an update for an unknown zone: %#", noti.recordZoneID.zoneName);
}
} else {
NSLog(#"WARNING: received unknown notification: %#", notification);
}
}
Okay I've finally figured it out. If you use a CKNotificationInfo for your alerts, didReceiveRemoteNotification will NOT be called on the Mac until and unless you set CKNotificationInfo.soundName to an empty string! This looks like a bug only in OS X (10.10 & 10.11 so far) but can be worked around by this simple change.
I have the user's unique ID, but I'm not sure how to retrieve the username from the Parse Data Base with this info. I'm storing the ID's in an array, and I've tried to loop through it but i don't know where to go from there. Any help would be appreciated!
Its simple here you go!
NSArray *arrayOfUsersObjectIDs = ...;
PFQuery *queryForUsers = [PFQuery queryWithClassName:#"Your_Class_Name"];
[queryForUsers whereKey:#"objectId" containedIn:arrayOfUsersObjectIDs];
[queryForUsers findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(!error) {
for(PFObject *objUser in objects) {
NSLog(#"User name: %#", objects[#"Key_For_Name"]);
}
}
}];
I'm trying to retrieve data from the current User's row and display it in their profile. (Data such as firstName, lastName, email, postalCode etc - Which are all in separate columns). I can retrieve all the data by using:
PFUser *currentUser = [PFUser currentUser];
if (currentUser != nil) {
NSLog(#"Current user: %#", currentUser.username);
PFQuery *query = [PFUser query];
[query whereKey:#"username" equalTo:currentUser.username];
NSArray *data = [query getObjects];
NSLog(#"%#", data);
}
but I don't think I can separate the data by this method. It only displays everything at once. I would like it to assign to separate labels to display firstName, lastName etc.
Whatever method you use to query for the currentUser, you (hopefully) will be returning one PFObject. Since the PFObject is essentially a Dictionary, you just than have all of the data for that user with access to object's Key-Value pairs.
I prefect KVC rather than just calling methods on the currentUser class because it's possible you have custom fields that can't easily be queried for.
Below is my solution to query for currentUser and set up their profile.
ProfileVC.h
#property (nonatomic, strong) PFUser *profileToSet;
ProfileVC.m
-(void)setProfile{
PFQuery *query = [PFUser query];
[query whereKey:#"objectId" equalTo:[[PFUser currentUser]objectId]];
[query findObjectsInBackgroundWithBlock:^(NSArray * objects, NSError * _Nullable error) {
if (error) {
NSLog(#"error: %#",error);
} else {
self.profileToSet = [objects firstObject];
// Do the rest of the setup with the profileToSet PFUser PFObject.
}
}];
What you're doing there is printing out the full array of results from your query, which is why it looks like all the fields are being put together (when they're not really). If you want to retrieve/refresh the data from Parse for the current user there's a better way. Use fetch.
PFUser *currentUser = [PFUser currentUser];
if (currentUser != nil) {
NSLog(#"Current user: %#", currentUser.username);
[currentUser fetch];
// Now your currentUser will be refreshed...
NSLog(#"First: %#, postcode: %#", currentUser[#"firstName"], currentUser[#"postalCode"]); // Assuming those keys exist.
}
It'll ensure that only the current user is returned, in a simpler way than running a PFQuery directly. You don't need to check the array length, or grab the first object out.
As noted in comments, you should look at using fetchInBackgroundWithBlock: as it'll (1) not block and (2) give you an error if something goes wrong.
I would like to know how I can get an array with all the name of record type in my private database in Cloud Kit for read all my data ?
I save my data :
CKRecord* fav1 = [[CKRecord alloc] initWithRecordType:#"Favoris1"];
[fav1 setObject:#"Favoris 1 name"forKey:#"name"];
[fav1 setObject:#"2003 year"forKey:#"year"];
[self.privateDatabase saveRecord:fav1
completionHandler:^(CKRecord *savedState, NSError *error) {
if (error) {
NSLog(#"ERROR SAVING: %#", error);
}
else{
NSLog(#"SAVE OK");
}
}];
I read my data :
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Favoris1" predicate:[NSPredicate predicateWithFormat:#"TRUEPREDICATE"]];
[self.privateDatabase performQuery:query
inZoneWithID:nil
completionHandler:^(NSArray *results, NSError *error)
{
if (!error)
{
NSLog(#"results query %#", results);
NSLog(#"--> %#",[[results objectAtIndex:0] objectForKey:#"name"]);
NSLog(#"--> %#",[[results objectAtIndex:0] objectForKey:#"year"]);
}
else
{
NSLog(#"FETCH ERROR: %#", error);
}
}];
I would like to save an other record with another properties like :
CKRecord* fav2 = [[CKRecord alloc] initWithRecordType:#"Favoris2"];
[fav2 setObject:#"Favoris 2 name"forKey:#"name"];
[fav2 setObject:#"2005 year"forKey:#"year"];
How can I have an array with Favoris1 and Favoris2 for read all my record after I have saved ?
in CloudKit there is no function that will return all the available recordType's from a database. You can also not query multiple recordTypes at the same time. You have to perform 2 queries. What you could do is join the resulting arrays together if you need something like that.
Since the objects are the same, it would be better if you just used 1 recordType and add an extra field that would specify your custom type (Favoris1 or Favoris2) Then you could query just the recordType and see in the returned field what custom type it is.
I am trying to update username of [PFUser currentUser].
PFUser *user = [PFUser currentUser]; user.username = #"New Username";
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (completion) {
completion(succeeded, error);
}
}];
In case, if the username already exist, save method throws an error that "Username already exists". After that, I'm trying to get the original username from [PFUser currentUser] but it returns the new username(which is already taken).
And then I'm tried to refresh the object with refreshObject
[[PFUser currentUser] refreshInBackgroundWithBlock:^(PFObject *object, NSError *error) {
NSLog(#"%#",object);
}];
This method also returns the new username, not the original username.
How to do this? Which is best way to do this?