Send local notification when download completes through NSURLSession / NSURLSessionDownloadTask
Asked Answered
P

3

12

I am using NSURLSessionDownloadTask objects on an NSURLSession to allow users to download documents while the app is in the background / device locked. I also want to inform the user that individual downloads have finished through a local notification.

To that end, I am triggering a local notification in the -URLSession:downloadTask:didFinishDownloadingToURL: download task delegate method, however I am wondering if there might be a better place to add the code triggering a notification, since the way Apple explains it, the download task will be passed to the system, and from that I am deriving that those delegates will not be called anymore on the download task's delegate once (or shortly after) the app is backgrounded.

My question: What is the best place to add the code for triggering the local notifications? Has anybody had any previous experience in adding this sort of a functionality to their application?

Pitchy answered 29/7, 2015 at 20:18 Comment(1)
Some things I am noticing from experimenting with this: When multiple files are queued up to be downloaded the notifications are either coalesced and sent at the end of all downloads or not all of them are triggered. I have also experimented with adding the notification trigger code in -application:handleEventsForBackgroundURLSession:completionHandler: but that seems (from the tests I've done so far) to only trigger the notification once, not for each document that has been downloaded.Pitchy
L
9

Answer on your question can be found in Apple documentation URL Loading System Programming Guide:

In iOS, when a background transfer completes or requires credentials, if your app is no longer running, iOS automatically relaunches your app in the background and calls the application:handleEventsForBackgroundURLSession:completionHandler: method on your app’s UIApplicationDelegate object. This call provides the identifier of the session that caused your app to be launched. Your app should store that completion handler, create a background configuration object with the same identifier, and create a session with that configuration object. The new session is automatically reassociated with ongoing background activity. Later, when the session finishes the last background download task, it sends the session delegate a URLSessionDidFinishEventsForBackgroundURLSession: message. Your session delegate should then call the stored completion handler.

If any task completed while your app was suspended, the delegate’s URLSession:downloadTask:didFinishDownloadingToURL: method is then called with the task and the URL for the newly downloaded file associated with it.

As you see it's much more complicated then just set delegate object. By delegate methods you will be notified only if app in foreground mode. In other cases (app in background mode, app is terminated) you need handle AppDelegate methods that are described in above quote.

Also Apple provides example project, that shows how to work with background download/upload tasks. This example will help you to find place where to put "Local Notification" code.

Logomachy answered 1/8, 2015 at 22:3 Comment(4)
Even though the documentation says that application:handleEventsForBackgroundURLSession:completionHandler is called every time a task completes, I have found that this is not the case, it is actually called once when the whole session finishes. Any ideas regarding this?Pitchy
@Andrei It doesn't say that it called every time when task is completed. It says that this method will be called if app was terminated when task is completed. Probably app was in background when all other tasks are completed. In this case this method will be called only once.Logomachy
thanks for the quick reply. Any idea how I should go about finding out when one task was finished?Pitchy
@Andrei, as I understand you need handle this call application:handleEventsForBackgroundURLSession:completionHandler if task completed when app was terminated. In all other cases you need handle NSURLSession delegate methods. Also all logic described in Apple documentation has to be implemented in your app.Logomachy
O
0

As Visput explained above, this method will be called once the download completes.application:handleEventsForBackgroundURLSession:completionHandler:

This will happen if you use the NSURLSessionConfiguration class with the backgroundSessionConfiguraton. You might be missing that piece.

NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.BGTransfer"];
sessionConfiguration.HTTPMaximumConnectionsPerHost = 5; // To set the max concurrent connections

It is explained in detail here.

Overslaugh answered 6/8, 2015 at 12:19 Comment(0)
A
-1

As Suggested by @Gautam Jain u have to use backgroundSessionConfiguration to achieve ur objective.Below i have attached a example ,hope it helps you

DownloadModel.h

#import "AppDelegate.h"
@interface DownloadModel : NSObject<NSURLSessionDelegate,NSURLSessionTaskDelegate,NSURLSessionDownloadDelegate>{
NSString *resp;
}

+(instancetype)shared;
-(NSURLSessionDownloadTask *) downloadTaskWithURL:(NSURL*)url ;
@end

DownloadModel.m

#import "DownloadModel.h"
@interface DownloadModel ()
@property (strong,nonatomic) NSURLSession *downloadSession;
@end

@implementation DownloadModel
+(instancetype)shared{
static dispatch_once_t onceToken;
static DownloadModel *downloader=nil;

dispatch_once(&onceToken, ^{

    downloader=[DownloadModel new];
});

return downloader;

}

-(id)init{
self=[super init];
if(self){

    NSURLSessionConfiguration *downloadConfig=[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"DownloadDemo"];
    //        downloadConfig.timeoutIntervalForRequest = 30;
    //        downloadConfig.timeoutIntervalForResource = 30;
    //        downloadConfig.HTTPMaximumConnectionsPerHost = 1;
    //        downloadConfig.sessionSendsLaunchEvents=YES;
    downloadConfig.allowsCellularAccess = YES;
    downloadConfig.networkServiceType = NSURLNetworkServiceTypeBackground;
    //        downloadConfig.discretionary = YES;
    self.downloadSession=[NSURLSession sessionWithConfiguration:downloadConfig delegate:self delegateQueue:nil];
    self.downloadSession.sessionDescription=@"Video Downloader";

}

return self;
}

-(NSURLSessionDownloadTask *) downloadTaskWithURL:(NSURL*)url{
return [self.downloadSession downloadTaskWithURL:url];
}

#pragma mark download delegate

use notification OR Local Notification in this method

- (void)URLSession:(NSURLSession *)session downloadTask(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL(NSURL *)location{

[[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadFinish" object:downloadTask userInfo:nil];
}

For Progress of Download

- (void)URLSession:(NSURLSession *)session downloadTask(NSURLSessionDownloadTask *)downloadTask didWriteData(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{

CGFloat progress=(CGFloat)totalBytesWritten/totalBytesExpectedToWrite;

NSDictionary *userInfo=@{@"progress":@(progress)};

[[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadProgress" object:downloadTask userInfo:userInfo];


}



#pragma mark delegate
-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
AppDelegate *appdelegate=[[UIApplication sharedApplication] delegate];

if(appdelegate.backgroundSessionCompletionHandler){
    appdelegate.backgroundSessionCompletionHandler();
    appdelegate.backgroundSessionCompletionHandler=nil;
}
}
@end

AppDelegate.h

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (copy ,nonatomic) void(^backgroundSessionCompletionHandler)();

@end

AppDelegate.m

-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{

self.backgroundSessionCompletionHandler=completionHandler;

[DownloadModel shared];
}

ViewController.m Call this Method -(NSURLSessionDownloadTask *) downloadTaskWithURL:(NSURL*)url

- (void)viewDidLoad { 

//Add Notification observers to track download progress and call the above method

[DownloadModel shared] downloadTaskWithURL:url];

} 

Don't Forget to enable Background Fetchenter image description here

Alleman answered 6/8, 2015 at 14:14 Comment(1)
You don't need to add "Background Fetch" for background downloading.Scrutinize

© 2022 - 2024 — McMap. All rights reserved.