Search code examples
iosobjective-cbackground-processnsurlsession

Downloading data when iOS app goes in background


I have a app which downloads a huge amount of data (mostly images and document files) when it is installed for the first time. Currently, I'm only able to display the progress in a HUD. But I wish I could somehow allow the data to be downloaded when the app goes into background (or device gets locked). As the app is being targeted for devices running iOS 7.0 and above, I'm using NSURLSessionto download the data. I've gone through various threads here on Stackoverflow as well as a tutorial here. Even after making changes to my app as per the tutorial, my app does not continue the download. I tested it on an iPad. When the app is sent to background(or locked), the download is paused and resumes when the app comes to foreground.

I'm unsure if my approach is wrong or my implementation is flawed. Any help/advice is welcome.

The flow of the app is as follows: 'LoginViewController' calls an internal method downloadData which has an object of the SaveProjectData class that performs the task of downloading.

LoginViewController.m

@implementation LoginViewController
- (void)viewDidLoad {
}
- (IBAction)sumitButtonDidClick:(id)sender {

    if ([self checkNetworkConnection] ==NotReachable) {

        UIAlertView * alertView=[[UIAlertView alloc] initWithTitle:@"Network Error" message:@"No internet Connection." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
        [alertView show];

    } else {

       MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
        hud.mode = MBProgressHUDModeDeterminateHorizontalBar;
        hud.label.text = NSLocalizedString(@"Downloading...", @"HUD loading title");

         dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{

        double delayInSeconds = 1.0;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));

            if ([self loginStatus]) {

                NSString * userName = self.user_nameTxtField.text;
                 CallProjectAPI * callProjectAPIinst = [[CallProjectAPI alloc] init];

                NSString * appStatus = [[NSUserDefaults standardUserDefaults] objectForKey:@"appStatus"];
                if ([appStatus isEqualToString:@"N"]) {
                       [callProjectAPIinst dataFromJSONFile];
                      [callProjectAPIinst saveImageName];

                } else {

                     [self doSomeWorkWithProgress];
                    [callProjectAPIinst callAPI];
                     [self doSomeWorkWithProgress];
                    [self downloadData]; 
                }


                hud.label.text = NSLocalizedString(@"Complete!", @"HUD completed title");


                porjectVC = [self.storyboard instantiateViewControllerWithIdentifier:@"ProjectViewController"];
                dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
                [self presentViewController:porjectVC animated:YES completion:nil];
                     [hud hideAnimated:YES];
                  });
                [[NSUserDefaults standardUserDefaults] setObject:userName forKey:@"userName"];

            } else {

                dispatch_async(dispatch_get_main_queue(), ^{
                     [hud hideAnimated:YES];
                    UIAlertView * alert=[[UIAlertView alloc]  initWithTitle:@"Alert" message:@"User Name or Password is wrong." delegate:nil cancelButtonTitle:@"ok" otherButtonTitles:nil];
                    [alert show];

                });

            }


         });

    }
}
-(void)downloadData{


    if ([self checkNetworkConnection] ==NotReachable) {

        UIAlertView * alertView=[[UIAlertView alloc] initWithTitle:@"Network Error" message:@"No internet Connection." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
        [alertView show];

    } else {
        NSString * status =[[NSUserDefaults standardUserDefaults] objectForKey:@"DownloadData"];
        if (status == nil) {




                SaveProjectData * saveProjectDataInst = [[SaveProjectData alloc] init];

                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveAboutImage];
                [self doSomeWorkWithProgress];
                [saveProjectDataInst saveConstructionUpdateImages];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveLogImages];
                [saveProjectDataInst saveSmallImages];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveFloorPlanImages];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveUnitPlanImages];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveMasterPlanImages];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveBrochurs];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveGalleryImages];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveAmenitiesImages];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveVideos];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveMapImage];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveBannerImage];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst savewalkthrough];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveFlatImages];
                 [self doSomeWorkWithProgress];
                [saveProjectDataInst saveEventImages];
                 [self doSomeWorkWithProgress];

                [[NSUserDefaults standardUserDefaults] setObject:@"YES" forKey:@"DownloadData"];
               [saveProjectDataInst getUpdatedListForEachProject];
        }
    }
}
- (void)doSomeWorkWithProgress {

    progress += 0.049f;
    dispatch_async(dispatch_get_main_queue(), ^{
        // Instead we could have also passed a reference to the HUD
        // to the HUD to myProgressTask as a method parameter.
        [MBProgressHUD HUDForView:self.view].progress = progress;
    });


}
@end

Another approach could be to provide a button that the user clicks and data is downloaded while the user can still continue to use the device for other perposes. Any pointers as to how I can implement it?


Solution

  • The answer to this question(a similar one) was given here by @HimanshuMahajan I'm adding to that answer and posting a solution that worked for me, tested it on an iPad successfully.

    1) use following line in header file of ViewController

    @property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
    

    2) in ViewDidLoad assign UIBackgroundTaskIdentifier like:

    self.backgroundTask = UIBackgroundTaskInvalid;
    

    3) Use following line of code, here I am just keeping on getDataFromServer method inside beginBackgroundTaskWithExpirationHandler: block

    self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
    
            [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
            self.backgroundTask = UIBackgroundTaskInvalid;
        }];
    
        /* Here your downloading Code, let say getDataFromServer method */
    
        [self getDataFromServer]; // Its dummy method
    
        /* Your downloading Code End Here */
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
            [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
            self.backgroundTask = UIBackgroundTaskInvalid;
        });
    

    4) If you want to check time remaining to download data in background, include following line in applicationDidEnterBackground:(UIApplication *)application delegate method of AppDelegate:

    NSLog(@"Background time remaining = %.1f seconds", [UIApplication sharedApplication].backgroundTimeRemaining);
    

    Adding to the answer:

    5) add the following code in applicationDidEnterBackground:(UIApplication *)application method to allow the background execution without time limit

    UIApplication *app = [UIApplication sharedApplication];
        UIBackgroundTaskIdentifier bgTask;
        bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
            [app endBackgroundTask:bgTask];
        }];
    

    Hope someone finds the answer useful!