Will iOS Notification Service Extension delete attached file from device? - ios

I got a strange problem. iOS Notification Service Extension will delete the attachment from device.
I use SDWebImage to display and cache image, and I implemented a Notification Service Extension to display a image in the notification alert view.
In my case, the image was already cached locally. Then, I click home button, my app was running in background, app scheduled a local notification with the cached image attach into the notification content.
See the code bellow:
1.Schedule a local notification
+ (void)postLocalNotificationGreaterThanOrEqualToiOS10:(LNotification)module body:(NSDictionary *)body {
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.sound = [UNNotificationSound defaultSound];
content.body = #"body";
content.userInfo = #{};
//get the image in device to attach into notification
NSError *error;
NSString* imgURL = [body valueForKey:kLocalNotification_Image];
NSString *filePath = [[SDImageCache sharedImageCache] defaultCachePathForKey:imgURL];
NSURL *url = [NSURL URLWithString:[#"file://" stringByAppendingString:filePath]];
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:#"Image" URL:url options:nil error:&error];
if (attachment) {
content.attachments = #[attachment];
}
UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:#"FiveSecond"
content:content trigger:trigger];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(#"error: %#", error.localizedDescription);
}
}];
}
2.Notification Service Extension (In Fact, for local notification, only didReceiveNotificationRequest:withContentHandler: was called and did nothing.)
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
NSDictionary *aps = [self.bestAttemptContent.userInfo objectForKey:#"aps"];
if (aps) {
....//For remote notification, modify the notification content here
}
else {
//For local notification, do nothing
}
self.contentHandler(self.bestAttemptContent);
}
- (void)serviceExtensionTimeWillExpire {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
self.contentHandler(self.bestAttemptContent);
}
- (NSString *)downloadImageWithURL:(NSString *)imageURLString imageName:(NSString *)imageName {
....//code will not execute for local notification
}
I found that, the image I attached into the local notification will be deleted from device. I mean, after I click the notification alert to launch app from background to foreground, I try to display the image, unfortunately, SDWebImageCache did not find the cache from neither disk nor memory.
I read the preference of iOS API, and didn't find that the attachment will be deleted. Does anybody know where could I find any clue of this issue? Maybe I just ignored something important refer to Notification Service Extension.
Now, I made a work around to fix this issue temporarily, while scheduling local notification, copy the cached image and save as another name, then attach it into local notification. Even Notification Service Extension will delete the attachment, it will just deleted the copy file, and the app will find the image from cache.
But, I really wanna know why this problem happened. Thanks in advance.

Just found in documentation
UNNotificationAttachment:
The system validates the content of attached files before scheduling
the corresponding notification request. If an attached file is
corrupted, invalid, or of an unsupported file type, the notification
request is not scheduled for delivery. Once validated, attached files
are moved into the attachment data store so that they can be accessed
by the appropriate processes. Attachments located inside an app’s
bundle are copied instead of moved.

Related

How to launch the app when I get Remote message from pusher in background state

This is my code for getting a remote message from the pusher.
self.pusher = [PTPusher pusherWithKey:#"pusherKey" delegate:self encrypted:YES cluster:#"ap2"];
PTPusherChannel *channel = [self.pusher subscribeToChannelNamed:#"my-channel"];
[channel bindToEventNamed:#"my-event" handleWithBlock:^(PTPusherEvent *channelEvent) {
NSString *message = [channelEvent.data objectForKey:#"message"];
}];
[self.pusher connect];
No, it is not possible to launch app automatically when in the background.
Performing some short task is available but not launch app.

Send push notification from iOS using Amazon SNS

I set it up Amazon SNS and iOS App to send Push Notification via SNS Console and receive in iOS. It works properly.
Now, I'm trying to send push notification from a device to another device, but I'm getting the following error:
-[AWSServiceInfo initWithInfoDictionary:checkRegion:] | Couldn't read credentials provider configurations from Info.plist. Please check
your Info.plist if you are providing the SDK configuration values
through Info.plist.
There's my code to send push notification
AWSSNS *publishCall = [AWSSNS defaultSNS];
AWSSNSPublishInput *message = [AWSSNSPublishInput new];
message.subject = #"My First Message";
//This is the ending point
message.targetArn = #"arn:aws:sns:us-west-2:895047407854:endpoint/APNS_SANDBOX/XXXXX/XXXX-XXXX-XXX-XXXX";
message.subject =#"hello";
message.message =#"teste from device";
// message.messageAttributes = messageDict;
//
// message.messageStructure = jsonString;
[publishCall publish:message completionHandler:^(AWSSNSPublishResponse * _Nullable response, NSError * _Nullable error) {
if(error) NSLog(#"%#", [error userInfo]);
if(response) NSLog(#"%#", [response description]);
}];
I don't know what I'm missing.
As per the debug log of AWSInfo.m here you will get this error when you have not configured your defaultCognitoCredentialsProvider
As they will check
_cognitoCredentialsProvider = [AWSInfo defaultAWSInfo].defaultCognitoCredentialsProvider;
if they find _cognitoCredentialsProvider nil then you will get this error.
So configure defaultCognitoCredentialsProvider properly.

Firebase Google Cloud messaging from device to device

I couldn't understand how can i send message from iOS device to another iOS device, and trying to understand the difference between
Firebase Notifications and Google Cloud Messaging.
Firebase Notifications say's from the server you can send a message to devices.
Google Cloud Messaging: it sends messages from server to devices(downstream) or device to server(upstream) !!
Example of upstream:
[[FIRMessaging message]sendMessage:(nonnull NSDictionary *)message
to:(nonnull NSString *)receiver
withMessageID:(nonnull NSString *)messageID
timeToLive:(int64_t)ttl;
What about if i need to send a push message from device to device ! Does it means after the device sends a messages to server, i have to program the firebase server to send push to client ? its really confusing !
No you cannot do this on iOS using firebase, what you should do is call a service on your firebase which will send a notification to the other device. APNS and GCM are a little different in terms of the server setup.
For GCM you just need the API key to be added in the POST call you make to https://android.googleapis.com/gcm/send which can be done anywhere server, mobile device, etc. All you need is the target devices device token and the API key.
APNS works differently you need attach the server SSL cert that you create on the Apple developer portal to authenticate yourself and send a push notification to a device. I am not sure how you could achieve this on an iOS device.
This thread clarifies the real difference between GCM and Firebase,
Real-time Push notifications with Firebase
https://firebase.google.com/support/faq/#gcm-not
Firebase and GCM are different but they can be used to achieve the same goals. Hope it helps you.
I don't think Firebase is currently equipped to handle this scenario. You need some sever side code to handle it. Either you could get hosting and make like a php endpoint what can used to incorporate the
[[FIRMessaging message]sendMessage:(nonnull NSDictionary *)message
to:(nonnull NSString *)receiver
withMessageID:(nonnull NSString *)messageID
timeToLive:(int64_t)ttl;
code and make it work, OR you need to find another service that can serve as the backend.
https://techcrunch.com/2016/02/02/batch-now-integrates-with-firebase-to-create-a-parse-alternative/
This Batch.com company seems to be the best solution that I have found so far. I have been able to have a users device send a json payload to an endpoint on their server that then sends Customized push notifications to specific targeted devices. It seems like Batch is specifically a Push Notification company, and it seems like the free basic plan is good enough to handle what you will need, silimar to how Parse worked.
Here is the actual code I wrote for sending the push notifications Objective C. (There is also a Swift 2 and Swift 3 example you can download from Batch.com)
NSURL *theURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://api.batch.com/1.1/(your API code here)/transactional/send"]];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0f];
[theRequest setValue:#"(your other API key here" forHTTPHeaderField:#"X-Authorization"];
NSDictionary *messageDict = [[NSDictionary alloc]initWithObjectsAndKeys:#"Hey This is a Push!", #"title", #"But it's a friendly push. Like the kind of push friends do to each other.",#"body", nil];
NSArray *recipientsArray = [[NSArray alloc]initWithArray:someMutableArrayThatHasUsersFirebaseTokens];
NSDictionary *recipientsDict = [[NSDictionary alloc]initWithObjectsAndKeys:recipientsArray, #"custom_ids", nil];
NSDictionary *gistDict = #{#"group_id":#"just some name you make up for this pushes category that doesn't matter",#"recipients":recipientsDict,#"message":messageDict, #"sandbox":#YES};
NSError *jsonError;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:gistDict options:NSJSONWritingPrettyPrinted error:&jsonError];
[theRequest setHTTPMethod:#"POST"];
[theRequest setHTTPBody:jsonData];
NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:theRequest queue:queue1 completionHandler:^(NSURLResponse *response, NSData *POSTReply, NSError *error){
if ([POSTReply length] >0 && error == nil){
dispatch_async(dispatch_get_main_queue(), ^{
NSString *theReply = [[NSString alloc] initWithBytes:[POSTReply bytes] length:[POSTReply length] encoding:NSASCIIStringEncoding];
NSLog(#"BATCH PUSH FINISHED:::%#", theReply);
});
}else {
NSLog(#"BATCH PUSH ERROR!!!:::%#", error);
}
}];
Batch was pretty easy to install with Cocoa Pods.
I also used this code to get it working:
In app delegate:
#import Batch
In didFinishLaunching:
[Batch startWithAPIKey:#"(your api key)"]; // dev
[BatchPush registerForRemoteNotifications];
then later in app delegate:
- (void)tokenRefreshNotification:(NSNotification *)notification {
// Note that this callback will be fired everytime a new token is generated, including the first
// time. So if you need to retrieve the token as soon as it is available this is where that
// should be done.
NSString *refreshedToken = [[FIRInstanceID instanceID] token];
NSLog(#"InstanceID token: %#", refreshedToken);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:refreshedToken forKey:#"pushKey"];
[defaults synchronize];
BatchUserDataEditor *editor = [BatchUser editor];
[editor setIdentifier:refreshedToken]; // Set to `nil` if you want to remove the identifier.
[editor save];
[self connectToFcm];
}
So thats how you do it, besides the setup and installation stuff which is all explained on the Batch.com website.
Once you get your token from firebase, you basically register it on Batch with
BatchUserDataEditor *editor = [BatchUser editor];
[editor setIdentifier:refreshedToken];
[editor save];
in App Delegate. Then when you want your users device1 to send a Push to another device2, assuming you have sent device1's custom id to device2 somehow, you can use that custom id to send the push notification payload to Batch.com's API, and Batch will handle the server side stuff to Apple APN and your Push notifications appear on device2.

CKFetchNotificationChangesOperation returning old notifications

I'm working on a CloudKit-based app that uses CKSubscription notifications to keep track of changes to a public database. Whenever the app receives a push notification I check the notification queue with CKFetchNotificationChangesOperation and mark each notification read after processing it:
__block NSMutableArray *notificationIds = [NSMutableArray new];
CKFetchNotificationChangesOperation *operation = [[CKFetchNotificationChangesOperation alloc] initWithPreviousServerChangeToken:self.serverChangeToken];
operation.notificationChangedBlock = ^(CKNotification *notification) {
[notificationIds addObject:notification.notificationID];
[self processRemoteNotification:notification withCompletionHandler:completionHandler];
};
__weak CKFetchNotificationChangesOperation *operationLocal = operation;
operation.fetchNotificationChangesCompletionBlock = ^(CKServerChangeToken *serverChangeToken, NSError *operationError) {
if (operationError) {
NSLog(#"Unable to fetch queued notifications: %#", operationError);
}
else {
self.serverChangeToken = serverChangeToken;
completionHandler(UIBackgroundFetchResultNewData);
// Mark the processed notifications as read so they're not delivered again if the token gets reset.
CKMarkNotificationsReadOperation *markReadOperation = [[CKMarkNotificationsReadOperation alloc] initWithNotificationIDsToMarkRead:[notificationIds copy]];
[notificationIds removeAllObjects];
markReadOperation.markNotificationsReadCompletionBlock = ^(NSArray *notificationIDsMarkedRead, NSError *operationError) {
if (operationError) {
NSLog(#"Unable to mark notifications read: %#", operationError);
}
else {
NSLog(#"%lu notifications marked read.", (unsigned long)[notificationIDsMarkedRead count]);
}
};
[[CKContainer defaultContainer] addOperation:markReadOperation];
if (operationLocal.moreComing) {
NSLog(#"Fetching more");
[self checkNotificationQueueWithCompletionHandler:completionHandler];
}
}
};
[[CKContainer defaultContainer] addOperation:operation];
As I understand it marking a notification read will keep it from showing up in future queue fetches, even if the server change token is reset to nil. Instead I'm getting a lot of old notifications in every fetch with a non-nil change token when there should only be 1 or 2 new ones. I can detect the old ones from the notificationType flag, but I'm concerned that they're showing up at all. Am I missing a step somewhere?
I know this is a bit old, but I was running into the same issue. I think I figured it out (at least for my case).
In my code, I was doing the same as you: that is, adding all the notificationIDs to an array and using that in my CKMarkNotificationsReadOperation, and was also getting all the notifications returned each time (although, as you noted, with a type of "ReadNotification").
I changed my code so that I was only adding "new" notifications to my array, and not the "ReadNotification" items, and sending those. That fixed it.
It seems that sending a notification back to the server to be marked as read, even if it already has been marked as such, will cause it to be returned again as "ReadNotification."
I hope this helps someone.
The documentation isn't very clear it should say: "Marking a notification as read prevents it from being returned by subsequent fetch operations"...as a query notification type. Further clarification it should say the notifications will instead be returned as read type.
If it wasn't returned at all then other devices that missed the push wouldn't know that something has changed!

Amazon S3 track downloaded file in Asynchronous method in iOS

During downloading multiple files using following asynchronous method of AWS iOS, I want to keep track which image is downloaded.
Following code is used for asynchronous image download.
S3TransferManager *tm = [S3TransferManager new];
S3TransferOperation *downloadFileOperation = [tm downloadFile:storeFilePath bucket:Bucket key:downloadPath];
Above method is in for loop, so there could be n images to download.
Delegate method which notify image is downloaded
-(void)request:(AmazonServiceRequest *)request didCompleteWithResponse:(AmazonServiceResponse *)response { }
But I did not find anything, using which I can manage that which actual image is downloaded. whether it was the firstID image or second one.
Any idea on where I can put some extra data , which can be received on image download ?
You can set requestTag:
S3PutObjectRequest *putObjectRequest = [ [ S3PutObjectRequest alloc ] initWithKey:keyFile inBucket:self.s3BucketName ];
putObjectRequest.requestTag = urlStringFile;
putObjectRequest.filename = fileName;
and then analyse it:
- (void)request:(AmazonServiceRequest*) request didCompleteWithResponse:(AmazonServiceResponse*) response
{
NSLog(#"Upload finished. RequestTag = %#", request.requestTag);
}
As far as, I have to only download content from Amazon s3, I used ASIHTTPRequest.
(Though this is not maintained since 2011, but I found its very useful and easy to use for my app).
Code Example,
// Initialize network Queue.
ASINetworkQueue *networkQueue = [[ASINetworkQueue alloc] init];
[networkQueue reset];
[networkQueue setRequestDidFinishSelector:#selector(requestDone:)]; //This is where download completion will be notified.
//Initialize Request.
ASIS3ObjectRequest *request = [ASIS3ObjectRequest requestWithBucket:#"Bucket_Name" key:#"/Path/file"];
This is what I was looking, I need all the information about what I am downloading on download completion. This userInfo contains all the data, which is available on download completion.
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"Data", #"Key", nil];
request.userInfo = userInfo;
userInfo = nil;
// End of userInfo set.
[networkQueue addOperation:request]; // add request in ASINetworkQueue object. We can also add multiple request here.
And last,
[networkQueue go]; // This will start downloading.
// Delegate method, where download completion will be notified
- (void)requestDone:(ASIS3Request *)request
{
NSLog(#"UserInfo : %#", request.userInfo); // Request data, to manage which request is complete.
}
Done.

Resources