Rich push notification not working with FCM in IOS - ios

I've added UNNotificationServiceExtension and UNNotificationContentExtension in my project for rich push notification. Please refer the code below which i've added for the same.
Code:
#import "NotificationService.h"
#interface NotificationService ()
#property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
#property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
#end
#implementation NotificationService {
NSURLSession *session;
}
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
NSDictionary *userInfo = request.content.userInfo;
if (userInfo == nil) {
[self contentComplete];
return;
}
if ([userInfo objectForKey:#"pic_url"]) {
[self loadAttachmentForUrlString:[userInfo objectForKey:#"pic_url"]
completionHandler: ^(UNNotificationAttachment *attachment) {
self.bestAttemptContent.attachments = [NSArray arrayWithObjects:attachment, nil];
}];
}
}
- (void)loadAttachmentForUrlString:(NSString *)urlString
completionHandler:(void (^)(UNNotificationAttachment *))completionHandler
{
__block UNNotificationAttachment *attachment = nil;
__block NSURL *attachmentURL = [NSURL URLWithString:urlString];
NSString *fileExt = [#"." stringByAppendingString:[urlString pathExtension]];
session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:attachmentURL
completionHandler: ^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
if (error != nil)
{
NSLog(#"%#", error.localizedDescription);
}
else
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path
stringByAppendingString:fileExt]];
[fileManager moveItemAtURL:temporaryFileLocation
toURL:localURL
error:&error];
NSError *attachmentError = nil;
attachment = [UNNotificationAttachment attachmentWithIdentifier:[attachmentURL lastPathComponent]
URL:localURL
options:nil
error:&attachmentError];
if (attachmentError)
{
NSLog(#"%#", attachmentError.localizedDescription);
}
}
completionHandler(attachment);
}];
[task resume];
}
- (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 contentComplete];
}
- (void)contentComplete
{
[session invalidateAndCancel];
self.contentHandler(self.bestAttemptContent);
}
#end
I'm using the following payload
{
"to": "9yJUWBA",
"mutable_content": true,
"category": "myNotificationCategory",
"notification":
{
"title":"Realtime Custom Push Notifications",
"subtitle":"Now with iOS 10 support!",
"body":"Add multimedia content to your notifications"
}
}
The problem is i'm not getting the notification. I've used the following tutorial for implementing the rich push notification. I've checked different answers available but none of them worked for me. I've also tried to debug the didReceiveNotificationRequest method by attaching the extension process but the breakpoint not triggered.
https://mobisoftinfotech.com/resources/mguide/ios-10-rich-notifications-tutorial/

-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
//Called when a notification is delivered to a foreground app.
NSLog(#"Userinfo willPresentNotification%#",notification.request.content.userInfo);
// Print message ID.
/*
NSDictionary *userInfo = notification.request.content.userInfo;
if (userInfo[kGCMMessageIDKey]) {
NSLog(#"Message ID: %#", userInfo[kGCMMessageIDKey]);
}
*/
completionHandler(UNNotificationPresentationOptionAlert);
}
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler{
//Called to let your app know which action was selected by the user for a given notification.
NSLog(#"Userinfo didReceiveNotificationResponse%#",response.notification.request.content.userInfo);
// Print message ID.
/*
NSDictionary *userInfo = notification.request.content.userInfo;
if (userInfo[kGCMMessageIDKey]) {
NSLog(#"Message ID: %#", userInfo[kGCMMessageIDKey]);
}
*/
}
Use above methods and these method available iOS 10 and onwards.
for debugging notification service extension please visit below link
Debug Notification Extensions
if you give support your application iOS 9 onwards then you have to put both methods below are for above iOS 10 and you also wrote in your question
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo
{
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// [[FIRMessaging messaging] appDidReceiveMessage:userInfo];
/*
// Print message ID.
if (userInfo[kGCMMessageIDKey]) {
NSLog(#"Message ID: %#", userInfo[kGCMMessageIDKey]);
}
*/
// Print full message.
NSLog(#"Userinfo didReceiveRemoteNotification 1 %#",userInfo);
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// [[FIRMessaging messaging] appDidReceiveMessage:userInfo];
/*
// Print message ID.
if (userInfo[kGCMMessageIDKey]) {
NSLog(#"Message ID: %#", userInfo[kGCMMessageIDKey]);
}
*/
// Print full message.
NSLog(#"Userinfo didReceiveRemoteNotification 2 %#",userInfo);
completionHandler(UIBackgroundFetchResultNewData);
}
let me know if you have query.

#interface NotificationService ()
#property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
#property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
#end
#implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
{
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// Modify the notification content here...
self.bestAttemptContent.title = #"";
self.bestAttemptContent.subtitle = #"";
// self.bestAttemptContent.body = [NSString stringWithFormat:#"%#", self.bestAttemptContent.body];
NSDictionary *dictPushNotiData = request.content.userInfo;
NSString *imageURL = #"";
NSString *videoURL = #"";
if(dictPushNotiData[#"xxx_details"])
{
NSString *jsonString = [dictPushNotiData objectForKey:#"xxx_details"];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
// If Instagram Notification Informations then call InstagramViewController
if(jsonData)
{
NSMutableDictionary *dictXXX = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSLog(#"dictXXX : %#",dictXXX);
//self.bestAttemptContent.title = [NSString stringWithFormat:#"%#", [dictXXX objectForKey:#"xxxx"]];
//self.bestAttemptContent.subtitle = [NSString stringWithFormat:#"%#", [dictXXX objectForKey:#"xxxx"]];
NSString *strBody = #"Notification xxxx x xxxx Post";
if([[dictXXX objectForKey:#"xxxx"] length] > 0)
{
strBody = [dictXXX objectForKey:#"xxxx"];
}
self.bestAttemptContent.body = [NSString stringWithFormat:#"%#",strBody];
imageURL = [dictXXX objectForKey:#"xxxx"];
videoURL = [dictXXX objectForKey:#"xxxx"];
}
}
NSString *strAttachment = nil;
// if (videoURL.length > 0)
// { //Prioritize videos over image
// strAttachment = videoURL;
// }
// else
if (imageURL.length > 0)
{
strAttachment = imageURL;
}
else
{
self.contentHandler(self.bestAttemptContent); //Nothing to add to the push, return early.
return;
}
// If there is an image in the payload, this part
// will handle the downloading and displaying of the image.
if (strAttachment) {
NSURL *URL = [NSURL URLWithString:strAttachment];
NSURLSession *LPSession = [NSURLSession sessionWithConfiguration:
[NSURLSessionConfiguration defaultSessionConfiguration]];
[[LPSession downloadTaskWithURL:URL completionHandler: ^(NSURL *temporaryLocation, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"Leanplum: Error with downloading rich push: %#",[error localizedDescription]);
self.contentHandler(self.bestAttemptContent);
return;
}
NSString *fileType = [self determineType:[response MIMEType]];
NSString *fileName = [[temporaryLocation.path lastPathComponent] stringByAppendingString:fileType];
NSString *temporaryDirectory = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] moveItemAtPath:temporaryLocation.path toPath:temporaryDirectory error:&error];
NSError *attachmentError = nil;
UNNotificationAttachment *attachment =
[UNNotificationAttachment attachmentWithIdentifier:#"" URL:[NSURL fileURLWithPath:temporaryDirectory] options:nil error:&attachmentError];
if (attachmentError != NULL) {
NSLog(#"Leanplum: Error with the rich push attachment: %#",
[attachmentError localizedDescription]);
self.contentHandler(self.bestAttemptContent);
return;
}
self.bestAttemptContent.attachments = #[attachment];
NSLog(#"self.bestAttemptContent.attachments : %#",
self.bestAttemptContent.attachments);
self.contentHandler(self.bestAttemptContent);
[[NSFileManager defaultManager] removeItemAtPath:temporaryDirectory error:&error];
}] resume];
}
}
- (NSString*)determineType:(NSString *) fileType {
// Determines the file type of the attachment to append to NSURL.
if ([fileType isEqualToString:#"image/jpeg"]){
return #".jpg";
}
if ([fileType isEqualToString:#"image/gif"]) {
return #".gif";
}
if ([fileType isEqualToString:#"image/png"]) {
return #".png";
} else {
return #".tmp";
}
}

Related

Downloading an image for showing on a local notification not working

I am developing a framework that has the functionality to receive messages from FCM and translate them to local notifications, In one of the keys I receive there is a URL to an image. So doing some research in Apple documentation found that the image should be stored in the device (I am using a simulator) and then it can be used, so, I implement a method for downloading a random image (.png). This method works fine, I test it and the image is in the specified location, the problem is that when I print the userInfo Dictionary from NSError* pointer when calling attachmentWithIdentifier:identifier URL: options: error: method I get this
{NSLocalizedDescription = "Invalid attachment file URL";}
I attach my code:
UNUserNotificationCenter * center = [UNUserNotificationCenter currentNotificationCenter];
if (settings.alertSetting == UNNotificationSettingEnabled)
{
NSDictionary * remoteMessageData = [remoteMessage appData];
NSString * imageHttpUrl = [remoteMessageData objectForKey:#"im"];
NSURL * imageURL = [self getStorageFilePath:imageHttpUrl];
[self downloadImageFromURL:imageHttpUrl withFullPath:imageURL.absoluteString withCompletitionHandler:^(NSHTTPURLResponse *httpResponse) {
UNMutableNotificationContent * content = [[UNMutableNotificationContent alloc] init];
content.title = [remoteMessageData objectForKey:#"ti"];
content.body = [remoteMessageData objectForKey:#"bd"];
if(httpResponse != nil)
{
if (httpResponse.statusCode == 200)
{
NSString * identifier = #"ImageIdentifier";
NSArray<UNNotificationAttachment*> * attachments = [NSArray arrayWithObjects:[self getAttachments:imageURL withIdentifier:identifier], nil] ;
content.attachments = attachments;
}
}
content.userInfo = remoteMessageData;
UNTimeIntervalNotificationTrigger * trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:10 repeats:NO];
//Create and register a request notification
NSString * uuidString = [[NSUUID UUID] UUIDString];
UNNotificationRequest * request = [UNNotificationRequest requestWithIdentifier:uuidString content:content trigger:trigger];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
//Handle error
if(error != nil)
{
NSLog(#"Dictionary: %#",[error userInfo]);
}
}];
- (NSURL *) getStorageFilePath : (NSString *)imageStringURL{
if(imageStringURL == nil)
{
return nil;
}
NSURL * imageURL = [NSURL URLWithString:imageStringURL];
NSString * fileName = [imageURL lastPathComponent];
NSLog(#"fileName %#",fileName);
NSArray * systemPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSLog(#"systemPaths %#",systemPaths);
NSString * tempDirectoryStringPath = [[[NSURL URLWithString:[systemPaths objectAtIndex:0]] absoluteString] stringByAppendingString:#"/"];
NSString * fullPath = [tempDirectoryStringPath stringByAppendingString:fileName];
NSLog(#"Full PATH: %#",fullPath);
return [NSURL URLWithString:fullPath];}
-(void) downloadImageFromURL : (NSString *)httpURL withFullPath:(NSString * )fullPath withCompletitionHandler:(void (^) (NSHTTPURLResponse * httpResponse) )taskResult{
if(httpURL == nil || fullPath == nil )
{
taskResult(nil);
return;
}
NSString *strImgURLAsString = httpURL;
strImgURLAsString = [strImgURLAsString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL * imgURL = [NSURL URLWithString:strImgURLAsString];
NSLog(#"Full PATH inside downloading: %#",fullPath);
NSURLSessionDataTask * downloadPhotoTask = [[NSURLSession sharedSession] dataTaskWithURL:imgURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
if (httpResponse.statusCode == 200)
{
[data writeToFile:fullPath atomically:YES];
NSLog(#"Download run successfully");
}else{
NSLog(#"Download could not be completed");
}
taskResult(httpResponse);
} ];
[downloadPhotoTask resume];
}
- (UNNotificationAttachment *) getAttachments: (NSURL *)attachmentURL withIdentifier : (NSString*)identifier
{
NSError * error;
UNNotificationAttachment * icon = [UNNotificationAttachment attachmentWithIdentifier:identifier URL: attachmentURL options:nil error:&error];
NSLog(#"Icon is : %#",[error userInfo]);
UNNotificationAttachment* attachments = icon;
return (attachments);
}
I could make it work. I was using [NSURL URLWithString:fullPath] NSURL but I should´ve use [NSURL fileURLWithPath:fullPath] but in the method downloadImageFromURL keep using an string URL without the format of fileURLWithPath: method (that cause the image not to be downloaded).

IOS Rich notification didReceiveNotificationRequest is not fired

I'm developing an iPhone application using objective-c. The basic push notification is working properly. Now I want to add rich notification in my app but I cannot get the didReceiveNotificationRequest fired in the NotificationService.
Here is the notification payload I receive on Appdelegate:
https://image.ibb.co/ndA2Qo/grab.png
Here is the NotificationService.m file;
#import "NotificationService.h"
#import "Common.h"
#interface NotificationService ()
#property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
#property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
#end
#implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
NSLog(#"didReceiveNotificationRequest");
// Modify the notification content here...
// load the attachment
NSDictionary *userInfo = request.content.userInfo;
NSString *imageURL = [userInfo valueForKey:#"thumbnail_image"];
NSArray *parts = [imageURL componentsSeparatedByString:#"."];
NSString *extension = [parts lastObject];
[self loadAttachmentForUrlString:imageURL
withExtension:extension
completionHandler:^(UNNotificationAttachment *attachment) {
if (attachment) {
self.bestAttemptContent.attachments = [NSArray arrayWithObject:attachment];
}
//self.bestAttemptContent.title = [NSString stringWithFormat:#"%# [modified]", self.bestAttemptContent.title];
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);
}
- (void)loadAttachmentForUrlString:(NSString *)urlString withExtension:(NSString *)extension completionHandler:(void(^)(UNNotificationAttachment *))completionHandler {
__block UNNotificationAttachment *attachment = nil;
NSURL *attachmentURL = [NSURL URLWithString:urlString];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session downloadTaskWithURL:attachmentURL
completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
if (error != nil) {
NSLog(#"%#", error.localizedDescription);
} else {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:extension]];
[fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
NSError *attachmentError = nil;
attachment = [UNNotificationAttachment attachmentWithIdentifier:#"" URL:localURL options:nil error:&attachmentError];
if (attachmentError) {
NSLog(#"%#", attachmentError.localizedDescription);
}
}
completionHandler(attachment);
}] resume];
}
#end
What am I missing?
Please advice,
Semih
The existing answer states that you should set content-available to 1, that NSLog does not work in extensions and the background thread is an issue. These are not right.
content-available:1 has no relevance to a notification service extension, for that to be fired up you need mutable-content:1.
NSLog works fine in extensions, it is handy to use the OS console when troubleshooting.
There is no problem with downloading an image in a background thread.
Try looking in the OS Console to see what might be happening (launch console.app on your Mac, plug in your device and select it on the left, and see what goes by when you send a push. Even without your NSLog you should see the OS trying to find and launch an extension like:
default 10:38:53.925889 -0400 SpringBoard [com.you.whatever] Remote
notification request 386D-4968 can be modified: 1
default 10:38:53.926227 -0400 SpringBoard [com.you.whatever] Trying to find extension for bundle
info 10:38:53.942366 -0400 pkd Candidate plugin count: 1, info: (
"com.you.whatever.NotificationServiceExtension(1.0.0) 2765CB-8244DD--B83D-20CB148BCEF6")
default 10:38:53.945090 -0400 SpringBoard [com.you.whatever] Extension can modify push notification request 386D-4968? YES
If you don't see success there maybe something is wrong with your project setup, maybe try recreating the target in XCode (do via File -> New -> Target, select Notification Service Extension).
You are downloading the image in background thread which is causing the issue in Rich Push Notification case. If you want you can try with this framework
Also Add "content-available":1 in your aps
OR you can try downloading like this,
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
self.bestAttemptContent.title = [NSString stringWithFormat:#"%#",request.content.title];
self.bestAttemptContent.body = [NSString stringWithFormat:#"%#",request.content.body];
NSString *attachmentUrlString = [NSString stringWithFormat:#"%#",[request.content.userInfo valueForKey:#"thumbnail_image"]];
if (![attachmentUrlString isKindOfClass:[NSString class]]) {
[self failEarly];
return;
}
NSURL *url = [NSURL URLWithString:attachmentUrlString];
if (!url) {
[self failEarly];
return;
}
NSData *data = [NSData dataWithContentsOfURL:url];
if (!data) {
[self failEarly];
return;
}
NSString *identifierName = [self getIdentifierName:attachmentUrlString];
NSString *tmpSubFolderName = [[NSProcessInfo processInfo] globallyUniqueString];
self.bestAttemptContent.attachments = [NSArray arrayWithObject:[self create:identifierName andData:data withOptions:nil andTmpFolderName:tmpSubFolderName]] ;
self.contentHandler(self.bestAttemptContent);
}
-(void)failEarly {
self.contentHandler(self.bestAttemptContent);
}
-(NSString *)getIdentifierName:(NSString *)fileURL
{
NSString *identifierName = #"image.jpg";
if (fileURL) {
identifierName = [NSString stringWithFormat:#"file.%#",fileURL.lastPathComponent];
}
return identifierName;
}
-(UNNotificationAttachment *)create:(NSString *)identifier andData:(NSData *)data withOptions:(NSDictionary *)options andTmpFolderName:(NSString *)tmpSubFolderName {
NSString *fileURLPath = NSTemporaryDirectory();
NSString *tmpSubFolderURL = [fileURLPath stringByAppendingPathComponent:tmpSubFolderName];
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:tmpSubFolderURL withIntermediateDirectories:TRUE attributes:nil error:&error];
if(!error) {
NSString *fileURL = [tmpSubFolderURL stringByAppendingPathComponent:identifier];
[data writeToFile:fileURL atomically:YES];
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:identifier URL:[NSURL fileURLWithPath:fileURL] options:options error:&error];
return attachment;
}
return nil;
}
- (void)serviceExtensionTimeWillExpire {
self.contentHandler(self.bestAttemptContent);
}

Notification Service app extension mandatory to display media in remote notification?

I'm trying to send an iOS push notification with a media attachment (image url) I've OneSignal SDK 2.2.2 for iOS but it doesn't work at all. In the following article it seems you don't have to implement a Service Extension to display the image inside the notification. (iOS 10).
Do I need to create a Notification Service app extension?
Yes it is required to implement a Notification Service Extension to handle the push notification payload before submitting it back to the OS to display to the user.
Example:
if this is the payload received:
{
"aps": {
"alert": {
"title": "My title",
"subtitle": "My title"
},
"mutable-content": 1,
"category": "<your_notification_category>"
},
"data": {
"url": "<img_url>"
}
}
Your service must download the image, then bundle the downloaded local URL as an UNNotificationAttachment before sending it off to the OS
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// Modify the notification content here...
if (request.content.userInfo[#"data"] && [request.content.userInfo[#"data"] isKindOfClass:[NSDictionary class]]) {
NSDictionary *data = request.content.userInfo[#"data"];
NSLog(#"%#", data);
NSURL *dataURL = [NSURL URLWithString:data[#"url"]];
self.task = [[NSURLSession sharedSession] downloadTaskWithURL:dataURL completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (location) {
// Get the location URL and change directory
NSString *tempDirectory = NSTemporaryDirectory();
NSURL *fileURL = [NSURL fileURLWithPath:tempDirectory];
fileURL = [fileURL URLByAppendingPathComponent:[dataURL lastPathComponent]];
NSError *error;
if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
[[NSFileManager defaultManager] removeItemAtPath:[fileURL path] error:&error];
}
[[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
// Create UNNotificationAttachment for the notification
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:#"" URL:fileURL options:nil error:nil];
self.bestAttemptContent.attachments = #[attachment];
}
self.contentHandler(self.bestAttemptContent);
}];
[self.task resume];
} else {
self.contentHandler(self.bestAttemptContent);
}
}
}

Saving to iCloud with saveToURL:forSaveOperation: completionHandler: fails

I am trying to save a Dictionary of objects to iCloud but when I do it method saveToURL:forSaveOperation: completionHandler: fails. I also tried to override:
- (BOOL)writeContents:(id)contents
andAttributes:(NSDictionary *)additionalFileAttributes
safelyToURL:(NSURL *)url
forSaveOperation:(UIDocumentSaveOperation)saveOperation
error:(NSError **)outError
and of course the super call also returns false. Yet, I would have liked to read the error, but when I try to have the localizedError the compiler reports an error claiming it is not a structure or union.
This is the full piece of code:
-(instancetype)initWithSingleton{
NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:
#"Stops"] URLByAppendingPathComponent:kFILENAME];
NSLog(#"file url=%#", ubiquitousPackage);
self=[self initWithFileURL:ubiquitousPackage];
if (self!=nil){
self.favoriteStops=[[NSMutableDictionary alloc] init];
NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSLog(#"iCloud access at %#", ubiq);
[self loadDocument];
} else {
NSLog(#"No iCloud access");
}
}
return self;
}
#define kFILENAME #"favorite.dox"
- (void)loadData:(NSMetadataQuery *)query {
if ([query resultCount] == 1) {
NSMetadataItem *item = [query resultAtIndex:0];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response;
NSData *GETReply= [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
NSMutableDictionary* dict=[NSKeyedUnarchiver unarchiveObjectWithData:GETReply];
[self setFavoriteStops:dict];
NSLog(#"favorites: %#", favoriteStops);
[self openWithCompletionHandler:^(BOOL success) {
if (success) {
NSLog(#"iCloud document opened");
} else {
NSLog(#"failed opening document from iCloud");
}
}];
}
}
- (void)queryDidFinishGathering:(NSNotification *)notification {
NSMetadataQuery *query = [notification object];
[query disableUpdates];
[query stopQuery];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSMetadataQueryDidFinishGatheringNotification
object:query];
_query = nil;
[self loadData:query];
}
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName
error:(NSError **)outError
{
if ([contents length] > 0) {
[self setFavoriteStops:[NSKeyedUnarchiver unarchiveObjectWithData:contents]];
}
return YES;
}
- (BOOL)writeContents:(id)contents
andAttributes:(NSDictionary *)additionalFileAttributes
safelyToURL:(NSURL *)url
forSaveOperation:(UIDocumentSaveOperation)saveOperation
error:(NSError **)outError{
//logging
NSString *str;
str= [[NSString alloc] initWithData:contents encoding:NSUTF8StringEncoding];
NSLog(#"saving data %#", str);
//logging
NSMutableDictionary *dict=[NSKeyedUnarchiver unarchiveObjectWithData:contents];
NSLog(#"dict=%#", dict);
BOOL success= [super writeContents:contents
andAttributes:additionalFileAttributes
safelyToURL:url
forSaveOperation:saveOperation
error:outError];
NSLog(#"error :%#", outError.localizedDescription) //syntax error
return success;
}
-(void) save{
NSLog(#"file url=%#", [self fileURL]);
[self saveToURL:[self fileURL]
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success) {
if (success) { //this returns false
[self openWithCompletionHandler:^(BOOL success) {
NSLog(#"new document saved on iCloud");
}];
} else {
NSLog(#"error in iCloud Saving");
}
}];
}
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError
{
NSLog(#"favorite stops=%# class=%#", self.favoriteStops, [favoriteStops class]);
NSData *archivedData=[NSKeyedArchiver archivedDataWithRootObject:self.favoriteStops];
return archivedData;
}
When I log the url on which to save, that is:
file:///private/var/mobile/Library/Mobile%20Documents/iCloud~com~information~inArrivo/Stops/favorite.dox
And when I check the error on the debugger it is:
Error Domain=NSCocoaErrorDomain Code=4 "The operation couldn’t be
completed. (Cocoa error 4.)" UserInfo=0x17bd6cb0
{NSFileNewItemLocationKey=file:///private/var/mobile/Applications/445778BF-86AF-4DE3-9E1B-BAC8F79D14D0/tmp/(A%20Document%20Being%20Saved%20By%20In%20Arrivo%20HD)/favorite.dox,
NSFileOriginalItemLocationKey=file:///private/var/mobile/Library/Mobile%20Documents/iCloud~com~information~inArrivo/Stops/favorite.dox,
NSUnderlyingError=0x17bfa860 "The operation couldn’t be completed.
(Cocoa error 4.)",
NSURL=file:///private/var/mobile/Library/Mobile%20Documents/iCloud~com~information~inArrivo/Stops/favorite.dox}
How may I fix it or at least know more?
The TSI Apple team answered me and provided me with a working class. Upon checking their version with mine the change seems to boil down to an update to - (void)queryDidFinishGathering:(NSNotification *)notification; by adding:
if (query.results == 0)
{
[self save]; // no favorites file, so create one
}
to make it as follows:
- (void)queryDidFinishGathering:(NSNotification *)notification {
NSMetadataQuery *query = [notification object];
[query disableUpdates];
[query stopQuery];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSMetadataQueryDidFinishGatheringNotification
object:query];
//•• added
if (query.results == 0)
{
[self save]; // no favorites file, so create one
}
[self loadData:query];
_query = nil;
}

Error when posting on facebook using the share dialog method

I tried putting up a facebook post from my app. Anytime I post an update through the app I get a popup error which says
"there was a problem with posting your status. we've logged the error and will look into it"
I tested it directly on my iphone.
Below is my FBManager singleton code:
FBManager.h:
#import <Foundation/Foundation.h>
#import <FacebookSDK/FacebookSDK.h>
#interface FBManager : NSObject{
}
+(instancetype)sharedFBManager;
- (void)postUpdateWithShareDialog;
#end
FBManager.m
#import "FBManager.h"
#implementation FBManager
+(instancetype)sharedFBManager
{
static FBManager *fbManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
fbManager = [[self alloc]init];
});
return fbManager;
}
-(instancetype)init
{
if (self = [super init]) {
NSLog(#"Facebook manager created");
}
return self;
}
- (void)postUpdateWithShareDialog {
// Check if the Facebook app is installed and we can present the share dialog
FBLinkShareParams *params = [[FBLinkShareParams alloc] init];
params.link = [NSURL URLWithString:#"https://developers.facebook.com/docs/ios/share/"];
// If the Facebook app is installed and we can present the share dialog
if ([FBDialogs canPresentShareDialogWithParams:params]) {
// Present share dialog
[FBDialogs presentShareDialogWithLink:nil
handler:^(FBAppCall *call, NSDictionary *results, NSError *error) {
if(error) {
// An error occurred, we need to handle the error
// See: https://developers.facebook.com/docs/ios/errors
NSLog(#"Error publishing story: %#", error.description);
} else {
// Success
NSLog(#"result %#", results);
}
}];
// If the Facebook app is NOT installed and we can't present the share dialog
} else {
// FALLBACK: publish just a link using the Feed dialog
// Show the feed dialog
[FBWebDialogs presentFeedDialogModallyWithSession:nil
parameters:nil
handler:^(FBWebDialogResult result, NSURL *resultURL, NSError *error) {
if (error) {
// An error occurred, we need to handle the error
// See: https://developers.facebook.com/docs/ios/errors
NSLog(#"Error publishing story: %#", error.description);
} else {
if (result == FBWebDialogResultDialogNotCompleted) {
// User cancelled.
NSLog(#"User cancelled.");
} else {
// Handle the publish feed callback
NSDictionary *urlParams = [self parseURLParams:[resultURL query]];
if (![urlParams valueForKey:#"post_id"]) {
// User cancelled.
NSLog(#"User cancelled.");
} else {
// User clicked the Share button
NSString *result = [NSString stringWithFormat: #"Posted story, id: %#", [urlParams valueForKey:#"post_id"]];
NSLog(#"result %#", result);
}
}
}
}];
}
}
// A function for parsing URL parameters returned by the Feed Dialog.
- (NSDictionary*)parseURLParams:(NSString *)query {
NSArray *pairs = [query componentsSeparatedByString:#"&"];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
for (NSString *pair in pairs) {
NSArray *kv = [pair componentsSeparatedByString:#"="];
NSString *val =
[kv[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
params[kv[0]] = val;
}
return params;
}
#end
I tested it by calling the
- (void)postUpdateWithShareDialog;
method when a certain condition was satisfied. How can I solve this issue?

Resources