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);
}
Related
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).
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";
}
}
Good afternoon,
I have an app that asks the user to log in thanks to twitter's doc found there: Log in with twitter. Then, I can successfully load the user's timeline. This timeline loads in a NavigationController which has the interface TWTRTimelineViewController. Now, I would like that when one user holds his finger on a tweet, or just click on one tweet, rather than opening Safari to display it, it pops up a button where I'll be able to work on the tweet after clicking. I will need to get access to the text and the image of the tweet.
From my understanding, I will need to delegate all the tweets to some kind of TWTRTweetView controller to work on them but I'm not so sure how, since I'm completely new to this. I did try to read the doc but couldn't really get it, and most of the example are written in Swift. I'm also not sure how I'm supposed to access the tweet's properties. I have tried STTwitter where I played with some JSON formatted texts and where I was able to get the text and the image URL but I can't figure out how to do so directly with TwitterKit. Here's my actual code of the controller that display the timeline:
#import "TwitterTimelineViewController.h"
#import <TwitterKit/TwitterKit.h>
#import "AppDelegate.h"
#interface TwitterTimelineViewController ()
#property (strong, nonatomic) IBOutlet UITableView *TwitterTableView;
#end
#implementation TwitterTimelineViewController
TWTRUserTimelineDataSource *userTimelineDataSource;
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *delegate=(AppDelegate *)[[UIApplication sharedApplication] delegate];
[[Twitter sharedInstance] startWithConsumerKey:#"myConsumerKey" consumerSecret:#"myConsumerSecretKey"];
TWTRAPIClient *APIClient = [[TWTRAPIClient alloc] init];
userTimelineDataSource = [[TWTRUserTimelineDataSource alloc] initWithScreenName:delegate.twitterUsername APIClient:APIClient];
self.dataSource = userTimelineDataSource;
}
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options {
return [[Twitter sharedInstance] application:app openURL:url options:options];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Any help would be awesome!
Cheers,
Theo.
Found the answer myself. For anyone struggling with the same issue, here the code I wrote. A bit messy but it works.
-(IBAction)ShowTweets: (id) sender{
UIButton *clicked = (UIButton *) sender;
NSString *tweetToDecryptIndex = [NSString stringWithFormat: #"%ld", (long)clicked.tag];
//gets all tweets from current timeline
NSArray *allTweets = self.snapshotTweets;
//look the tweets, get the URL and removes it to get the text only
NSDataDetector *detect = [[NSDataDetector alloc] initWithTypes:NSTextCheckingTypeLink error:nil];
//gets the single tweet from clicked button
TWTRTweet *tweet = [allTweets objectAtIndex:(long)clicked.tag];
NSString *content = tweet.text; //gets the text
NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
NSArray *matches = [linkDetector matchesInString:content options:0 range:NSMakeRange(0, [content length])]; //find the URL
NSURL *url; //contains the url from the text of the tweet
NSString *ciph; //text from tweet without the url
for (NSTextCheckingResult *match in matches) {
if ([match resultType] == NSTextCheckingTypeLink) {
url = [match URL];
ciph = [content substringToIndex:content.length-url.absoluteString.length];
}
}
//Now, ask a JSON answer from twitter of the specific tweet using its ID
TWTRAPIClient *client = [[TWTRAPIClient alloc] init];
NSString *statusesShowEndpoint = #"https://api.twitter.com/1.1/statuses/show.json";
NSDictionary *params = #{#"id" : tweet.tweetID};
NSError *clientError;
NSURLRequest *request = [client URLRequestWithMethod:#"GET" URL:statusesShowEndpoint parameters:params error:&clientError];
if (request) {
[client sendTwitterRequest:request completion:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (data) {
NSError *jsonError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
//NSLog(#"%#", json);
//looking for the media_url
NSString *media = [json valueForKeyPath:#"entities.media"];
NSArray *urlArray = [media valueForKey:#"media_url_https"];
//finally getting the url as a string
NSMutableString * urlOfImageString = [[NSMutableString alloc] init];
for (NSObject * obj in urlArray)
{
[urlOfImageString appendString:[obj description]];
}
//NSLog(#"%#",urlOfImageString);
//constructing name for image to write in path
NSString *tweetID = tweet.tweetID;
NSString *imgName = [tweetID stringByAppendingString:#".jpg"];
NSString *tmp = [#"/your/path" stringByAppendingString:imgName];
//NSLog(#"%#", tmp);
//Now writting the image
NSURL *urlOfImageUrl = [NSURL URLWithString:urlOfImageString];
NSData *imageData = [NSData dataWithContentsOfURL:urlOfImageUrl];
}
else {
NSLog(#"Error: %#", connectionError);
}
}];
}
else {
NSLog(#"Error: %#", clientError);
}
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);
}
}
}
I am working on an iPhone app. It is a Enterprise calendaring app for my agency. We have a RESTful API backend connected to a web application where the appointments are entered. The appointments for an individual are to show up on that user’s iPhone. Data is sent by XML. There are only 3 or 4 appointments per day and less than 10 fields per record, so not a lot of data is transferred at a time (just the selected day’s information).
I tried to design this with an array on the iPhone for the parsed data, but the security checking on the web server makes the application time out when loading the data, and I didn’t handle the asynchronous processing well.
Now I’m wondering if I’m even approaching the problem correctly. Would it be better to use Core Data to store the appointments and then work to update the Core Data store in the background? I know I need to update the data outside of the loading the table process. I’m just at a loss for the best way to approach this.
I have looked through the site for information as to how to approach this. I have tried looking in books. Any help would be appreciated.
Security.h
typedef void (^touchIDComplete)(BOOL);
typedef void (^fileExists)(BOOL);
typedef void (^sessionVerify)(BOOL);
typedef void (^parsingData)(BOOL);
typedef void (^touchIDSuccess)(BOOL);
typedef void (^sessionRetrieved)(BOOL);
typedef void (^touchIDComplete)(BOOL);
typedef void (^sessionReading)(BOOL);
typedef void (^fillArray)(BOOL);
typedef void (^getTheData)(NSData *myData, NSError *error);
typedef void (^gettingESNBlock)(NSString *myESN, NSString *newSession, BOOL success, NSError *error);
typedef void (^checkingESNBlock)(NSString *myESN, NSString *sessionInfo, BOOL success, NSError *error);
#interface Security : NSObject
#property (strong, nonatomic) NSArray *types;
#property (strong, nonatomic) NSArray *esn;
#property (strong, nonatomic) NSString *idfv;
#property (strong, nonatomic) NSData *parseData;
#property (strong, nonatomic) NSString *sessionDetail;
#property (strong, nonatomic) NSString *loginFinished;
#property (strong, nonatomic) NSMutableURLRequest *request;
#property (atomic) NSString *passESN;
- (void)waitForData:(sessionVerify)compblock;
- (void)waitForFile:(fileExists)compblock;
- (void)waitForESN:(parsingData)compblock;
- (void)findESN:(gettingESNBlock)callback;
- (void)checkThumb:(touchIDSuccess)compblock;
- (void)readIt:(sessionRetrieved)compblock;
- (void)readNewSession:(sessionReading)compblock;
- (void)doTheWork:(NSString *)theESN withSession:(NSString *)newSession withSuccess:(BOOL)success error:(NSError *)error;
- (void)checkESN:(checkingESNBlock)callback;
- (void)checkTheSession:(NSString *)oldESN withSession:(NSString *)oldSession withSuccess:(BOOL)success error:(NSError *)error;
- (void)fillAppointmentData:(fillArray)compblock;
- (void)gettingData:(getTheData)compblock;
#end
Security.m
#implementation Security
void(^getESNForCallback)(NSString *myESN, NSString *newSession, BOOL success, NSError *error);
void(^checkESNWithCallback)(NSString *myESN, NSString *oldSession, BOOL success, NSError *error);
- (void)waitForFile:(fileExists) compblock {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectoryPath = [directoryPaths objectAtIndex:0];
NSString *fullPath = [documentsDirectoryPath stringByAppendingString:#"/session.txt"];
compblock([fileManager fileExistsAtPath:fullPath]);
}
- (void) waitForData:(sessionVerify) compblock {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:self.request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
if (statusCode == 401) {
// Insert process for thumbprint and session cookie pull
NSFileManager *fileManagerThree = [NSFileManager defaultManager];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *sessionPath = [documentsPath stringByAppendingPathComponent:#"session.txt"];
NSError *error;
BOOL success = [fileManagerThree removeItemAtPath:sessionPath error:&error];
if (success) {
} else {
}
} else {
return;
}
}
}
self.parseData = data;
compblock (YES);
}];
[task resume];
}
- (void)waitForESN:(parsingData) compblock {
ParseTypeXML *myParser = [[ParseTypeXML alloc] initWithData:self.parseData];
VariableStore *globals = [VariableStore sharedInstance];
if ([myParser.esn count] == 0) {
globals.user_esn = #"Error";
compblock(YES);
} else {
globals.user_esn = myParser.esn[0];
compblock(YES);
}
}
- (void)findESN:(gettingESNBlock)callback {
getESNForCallback = callback;
VariableStore *globals = [VariableStore sharedInstance];
[self doTheWork:globals.user_esn withSession:globals.sessionInfo withSuccess:YES error:nil];
}
- (void)doTheWork:(NSString *)theESN withSession:(NSString *)newSession withSuccess:(BOOL)success error:(NSError *)error {
[self checkThumb:^(BOOL finished) {
if(finished) {
[self readIt:^(BOOL newFile) {
if (newFile) {
[self readNewSession:^(BOOL seen) {
if (seen) {
VariableStore *globals = [VariableStore sharedInstance];
NSDictionary *cookieProperties = [NSDictionary dictionaryWithObjectsAndKeys:
#"ollie/", NSHTTPCookieDomain,
#"\\", NSHTTPCookiePath,
#"Cookie", NSHTTPCookieName,
globals.sessionInfo, NSHTTPCookieValue,
nil];
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
NSArray *cookieArray = [NSArray arrayWithObject:cookie];
NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieArray];
NSMutableString *url = [[NSMutableString alloc] initWithString:#"https://company.com/file.php"];
NSURL *urlNew = [NSURL URLWithString:url];
self.request = [NSMutableURLRequest requestWithURL:urlNew];
[self.request setHTTPMethod:#"GET"];
[self.request setAllHTTPHeaderFields:headers];
[self waitForData:^(BOOL dataReceived) {
if (dataReceived) {
[self waitForESN:^(BOOL esnFound) {
if (esnFound) {
VariableStore *globals = [VariableStore sharedInstance];
getESNForCallback(globals.user_esn, globals.sessionInfo, success, error);
}
}];
}
}];
}
}];
}
}];
}
}];
}
- (void)checkThumb:(touchIDSuccess)compblock {
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
// Authenticate User
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:#"You need to log in."
reply:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(#"success");
compblock(YES);
} else {
switch (error.code) {
case LAErrorAuthenticationFailed:
break;
case LAErrorUserCancel:
break;
case LAErrorUserFallback:
break;
default:
break;
}
}
}];
}
}
- (void)readIt:(sessionRetrieved)compblock {
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
NSString *url = #"https://company.com/specialstring.php";
NSMutableString *postText = [[NSMutableString alloc] init];
[postText appendString:idfv];
NSString *postBody = [NSString stringWithString:postText];
XMLPostSecurity *postAction = [[XMLPostSecurity alloc] init];
VariableStore *globals = [VariableStore sharedInstance];
globals.sessionInfo = [postAction sendPostRequestToUrl:url withBody:postBody];
FileSaving *saver = [[FileSaving alloc] init];
[saver saveSession:globals.sessionInfo];
compblock(YES);
}
-(void)readNewSession:(sessionReading)compblock {
NSFileManager *fileManagerTwo;
NSData *dataBuffer;
fileManagerTwo = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingString:#"/session.txt"];
dataBuffer = [fileManagerTwo contentsAtPath:filePath];
VariableStore *globals = [VariableStore sharedInstance];
globals.sessionInfo = [[NSString alloc] initWithData:dataBuffer encoding:(NSASCIIStringEncoding)];
compblock(YES);
}
- (void)checkESN:(checkingESNBlock)callback {
checkESNWithCallback = callback;
VariableStore *globals = [VariableStore sharedInstance];
[self checkTheSession:globals.user_esn withSession:globals.sessionInfo withSuccess:YES error:nil];
}
- (void)checkTheSession:(NSString *)theESN withSession:(NSString *)oldSession withSuccess:(BOOL)success error:(NSError *)error {
[self readNewSession:^(BOOL seen) {
if (seen) {
VariableStore *globals = [VariableStore sharedInstance];
NSDictionary *cookieProperties = [NSDictionary dictionaryWithObjectsAndKeys:
#"ollie/", NSHTTPCookieDomain,
#"\\", NSHTTPCookiePath,
#"Cookie", NSHTTPCookieName,
globals.sessionInfo, NSHTTPCookieValue,
nil];
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
NSArray *cookieArray = [NSArray arrayWithObject:cookie];
NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieArray];
NSMutableString *url = [[NSMutableString alloc] initWithString:#"https://company.com/file.php"];
NSURL *urlNew = [NSURL URLWithString:url];
self.request = [NSMutableURLRequest requestWithURL:urlNew];
[self.request setHTTPMethod:#"GET"];
[self.request setAllHTTPHeaderFields:headers];
[self waitForData:^(BOOL dataReceived) {
if (dataReceived) {
[self waitForESN:^(BOOL esnFound) {
if (esnFound) {
VariableStore *globals = [VariableStore sharedInstance];
checkESNWithCallback(globals.user_esn, globals.sessionInfo, success, error);
}
}];
}
}];
}
}];
}
- (void)fillAppointmentData:(fillArray)compblock {
VariableStore *globals = [VariableStore sharedInstance];
NSDictionary *cookieProperties = [NSDictionary dictionaryWithObjectsAndKeys:
#"ollie/", NSHTTPCookieDomain,
#"\\", NSHTTPCookiePath,
#"Cookie", NSHTTPCookieName,
globals.sessionInfo, NSHTTPCookieValue,
nil];
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
NSArray *cookieArray = [NSArray arrayWithObject:cookie];
NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieArray];
NSMutableString *url = [[NSMutableString alloc] initWithString:#"https://company.com/file2.php?adb="];
[url appendString:globals.chosenDate];
[url appendString:#"&esn="];
[url appendString:globals.user_esn];
NSURL *urlNew = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlNew];
[request setHTTPMethod:#"GET"];
[request setAllHTTPHeaderFields:headers];
[self gettingData:^(NSData *myData, NSError *error) {
if (myData != nil) {
ParseXML *myParser = [[ParseXML alloc] initWithData:myData];
[globals.appointmentData removeAllObjects];
[globals.appointmentData addObjectsFromArray:myParser.items];
}
}];
}
- (void) gettingData:(getTheData) compblock {
VariableStore *globals = [VariableStore sharedInstance];
globals.got401 = nil;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:self.request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
if (statusCode == 401) {
// Insert process for thumbprint and session cookie pull
NSFileManager *fileManagerThree = [NSFileManager defaultManager];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *sessionPath = [documentsPath stringByAppendingPathComponent:#"session.txt"];
NSError *error;
BOOL success = [fileManagerThree removeItemAtPath:sessionPath error:&error];
if (success) {
} else {
}
globals.got401 = #"Error";
} else {
return;
}
}
}
self.parseData = data;
}];
[task resume];
}
#end
If you only have 3 or 4 appointments worth of data stored locally then the answer is "whatever is easiest for you." It really doesn't matter. You could convert the data to morse code and save dots and dashes and then read that and it would still be small and fast enough.
You can save the data to a plist, serialize it using NSCoding, save it as a SQLite database, or even write the XML and convert it back to an array on reading it (although the XML option is probably the slowest/least efficient.)
Core Data is very powerful (and very cool) but it also has a very steep learning curve. I would not recommend it until you are comfortable working with iOS.
If your application is timing out then there is probably something else wrong. Edit your question to show the code for the problem area and perhaps we can help.