1

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?

Prashant
  • 336
  • 3
  • 18
  • 2
    See this (https://stackoverflow.com/questions/45338412/nsurlsessiondownloadtask-move-temporary-file/45904207#45904207) answer and sample project. It may help you. – beshio Jan 31 '18 at 08:11
  • "I could somehow allow the data to be downloaded when the app goes into background" That's an illogical request. – El Tomato Jan 31 '18 at 08:36
  • Thank you @beshio. – Prashant Jan 31 '18 at 08:53
  • @ElTomato, the logic behind the request is that the "initial" data download is huge and one-time. Thus, the user could set the app to download data and then perform some other task on the device. Hope you got the point. – Prashant Jan 31 '18 at 08:57
  • This link can help you what are the possibilities: https://www.raywenderlich.com/143128/background-modes-tutorial-getting-started – Daljeet Jan 31 '18 at 09:25
  • Thank you @Daljeet. I have referred that link too and know for sure that I need **Background Fetch** . Appreciate your help. – Prashant Jan 31 '18 at 10:56
  • @beshio thank you for this. Compiled and run with appcode , great tutorship here. – YvesLeBorg Jan 31 '18 at 20:08
  • Checkout this link https://www.ralfebert.de/ios-examples/networking/urlsession-background-downloads/ – jignesh Vadadoriya Feb 06 '18 at 05:42
  • @jigneshVadadoriya, thank you. This seems promising. I'll check it out. Appreciate your help! – Prashant Feb 06 '18 at 05:47
  • @jigneshVadadoriya, I was testing the example in the link you provided, but the behavior is similar to what's happening in my app/approach. When I run the app via Xcode in the simulator it shows the progress, but when I put the app in background by pressing the home button, the progress/ download stops..then when I bring the app in foreground, the progress/download starts where it had left off. – Prashant Feb 06 '18 at 06:53
  • The answer is given [here](https://stackoverflow.com/questions/8861390/ios-background-downloads-when-the-app-is-not-active). Tested and working solution. – Prashant Feb 07 '18 at 12:02

2 Answers2

0

iirc you shouldn't rely on downloading things when your app is in the background because you dont know when it will be killed due to memory pressure or something else

here's the Apple documentation on it

https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html#//apple_ref/doc/uid/TP40007072-CH2-SW7

John Doe
  • 185
  • 2
  • 13
  • Thank you @johndoe. I have gone through the documentation earlier, but the client has asked that the app be configured in such a way that he/she would put the app to download data and either leave the device for some time, or perform some other activities on the device. – Prashant Jan 31 '18 at 09:00
0

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!

Prashant
  • 336
  • 3
  • 18