Search code examples
objective-cios10unnotificationattachment

Download larger images in UNNotificationServiceExtension


I'm using the UNNotificationServiceExtension to create rich notifications on iOS 10 with an image attachment. Everything works fine with smaller images except when the image to download is a bit larger (e.g. 7MB).

In this case, the download starts but never finishes and the "best attempt" notification is displayed almost immediately after the download started.

According to Apple's docs, images can be up to 10MB so the size should not matter :)

I did implement the serviceExtensionTimeWillExpire where iOS is supposed to notify me that I have to deliver a content as soon as possible but this method is not called so I wonder what is happening.

Update 1 I also noticed that the same image works fine when it's stored locally. So it looks like the problem is in downloading the image. But, as said previously, the downloading is killed almost immediately.

The implementation is straight forward

- (BOOL)handleNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler
{

    self.bestAttemptContent = (UNMutableNotificationContent *) [request.content mutableCopy];

    UNMutableNotificationContent *content = [self.bestAttemptContent mutableCopy];

    NSString *urlString = [content.userInfo valueForKeyPath:@"attachment-url"];

    NSLog(@"Downloading notification attachment completed with: %@", url.absoluteString);
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {

        NSLog(@"Downloading notification attachment %@", error == nil ? @"success" : [NSString stringWithFormat:@"error: %@", error]);

        NSError *fileError;
        UNNotificationAttachment *attachment = [UNNotificationAttachment d360_createWithFileName:url.lastPathComponent identifier:url.absoluteString data:data options:nil error:&fileError];

        if (!attachment) {
            NSLog(@"Could not create local attachment file: %@", fileError);
            contentHandler(content);
            return;
        }

        NSLog(@"Adding attachment: %@", attachment);

        NSMutableArray *attachments = content.attachments ? [content.attachments mutableCopy] : [NSMutableArray array];

        [attachments addObject:attachment];

        content.attachments = attachments;
        contentHandler(content);


    }];
    [task resume];


    return YES;
}

- (void)serviceExtensionTimeWillExpire
{
    NSLog(@"Service extension expired. Using best attempt content  %@", self.bestAttemptContent);
    self.contentHandler(self.bestAttemptContent);
}

When I debug this in XCode, I see the following logs:

2017-03-30 14:51:43.723669 D360TestAppNotificationExtension[3393:398992] [D360Extension]: Handling notification request content
2017-03-30 14:51:43.724103 D360TestAppNotificationExtension[3393:398992] [D360Extension]: Downloading notification attachment: https://upload.wikimedia.org/wikipedia/commons/b/b2/Bled_Castle_05.jpg
Program ended with exit code: 0

The Program ended with exit code: 0 is suspicious. Any idea what is going on?

thanks Jan


Solution

  • So the solution is to use a NSURLSessionDownloadTask instead of a NSURLSessionDataTask. In this case, the download content is saved to a temporary location and uses less memory. The UNNotificationAttachment can be then created directly from the temporary file.

    Just note that the UNNotificationAttachment needs to read a file with a supported file extension so I simply append the remote file name to the local temporary file URL

    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
    
        NSLog(@"Downloading notification attachment completed with %@", error == nil ? @"success" : [NSString stringWithFormat:@"error: %@", error]);
    
        NSError *fileError;
        // create a local URL with extension
        NSURL *urlWithExtension = [NSURL fileURLWithPath:[location.path stringByAppendingString:url.lastPathComponent]];
    
        if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:urlWithExtension error:&fileError]) {
            NSLog(@"Could not append  local attachment file name: %@", fileError);
            contentHandler(content);
            return;
        }
    
        UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:url.absoluteString
                                                                                              URL:urlWithExtension options:nil
                                                                                            error:&fileError];
    
        if (!attachment) {
            NSLog(@"Could not create local attachment file: %@", fileError);
            contentHandler(content);
            return;
        }
    
        NSLog(@"Adding attachment: %@", attachment);
    
        NSMutableArray *attachments = content.attachments ? [content.attachments mutableCopy] : [NSMutableArray array];
    
        [attachments addObject:attachment];
    
        content.attachments = attachments;
    
        contentHandler(content);
    
    
    }];
    [task resume];