13

I need a long running task to be done in background as well as in foreground. This updates the core data. So to maintain UI responsive I created an another thread where I use different managedObjectContext(MOC). So a timer is set in background as well as in foreground and is inactivated appropriately when state changes. Before the task is starting and after the task is completed when I press home button it calls the two delegate methods properly but during the task is active when I press home button screen changes and UI hangs (becomes blank) but the two delegate methods are not called properly and the app is not terminated. I could not find the reason why this happens so. It would be helpful if someone can help.

I will attach the required code with this :

-(void) startTimerThread
{
    dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        // Add code here to do background processing
        NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
        [context setPersistentStoreCoordinator:[(AppDelegate *)[[UIApplication sharedApplication] delegate] persistentStoreCoordinator]];
        self.managedObjectContext = context;
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(mergeChanges:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:context];
        NSLog(@"managedObjContext : %@\n",self.managedObjectContext);
        [self getDataFromFile];

        dispatch_async( dispatch_get_main_queue(), ^{
            // Add code here to update the UI/send notifications based on the
            // results of the background processing

            [[NSNotificationCenter defaultCenter] postNotificationName:@"ReloadAppDelegateTable" object:nil];
            [context release];
            [[NSNotificationCenter defaultCenter] removeObserver:self 
                                                            name:NSManagedObjectContextDidSaveNotification
                                                          object:context];
        });
    });
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    NSLog(@"Background\n");
    [self.notificationTimer invalidate];
    self.notificationTimer = nil;
    UIApplication  *app = [UIApplication sharedApplication];
    self.bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask]; 
        bgTask = UIBackgroundTaskInvalid;
    }];

    //start location update timer and background timer 
    self.timer = [NSTimer scheduledTimerWithTimeInterval:180 target:self
                                                selector:@selector(startLocationServices) userInfo:nil repeats:YES];
    self.locationManager.delegate = self; 
    [self.locationManager startUpdatingLocation]; 

    self.logDownloader.managedObjectContext = self.managedObjectContext;
    NSLog(@"managedObjContext : %@\n",self.logDownloader.managedObjectContext);
    self.backgroundTimer = [NSTimer scheduledTimerWithTimeInterval:90 target:self.logDownloader selector:@selector(getDataFromFile) userInfo:nil repeats:YES];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    NSLog(@"Foreground\n");
    //invalidate background timer and location update timer
    [self.timer invalidate];
    [self.backgroundTimer invalidate];
    self.timer = nil;
    self.notificationTimer = nil;

    self.logDownloader.managedObjectContext = self.managedObjectContext;
    NSLog(@"managedObjContext : %@\n",self.logDownloader.managedObjectContext);
    [[NSNotificationCenter defaultCenter] postNotificationName:@"ReloadAppDelegateTable" object:nil];

    self.notificationTimer = [NSTimer scheduledTimerWithTimeInterval:180 target:self.logDownloader selector:@selector(startTimerThread) userInfo:nil repeats:YES];
}
aparna
  • 353
  • 2
  • 3
  • 13

4 Answers4

35

On iOS13+, if you implement UIWindowSceneDelegate, it calls func sceneDidEnterBackground(_ scene: UIScene), instead.

Ilias Karim
  • 4,798
  • 3
  • 38
  • 60
  • Yes ( timestamp as of iOS 14.5 SDK, Xcode 12.5.1), there is a pattern shift with the app lifecycle. As we move more towards SwiftUI , bridging SwiftUI and UIKit, and updating our projects with a new build project this SceneDelegate is now part of the app lifecycle. Good call IIias Karim. I had to add the "Application does not run in background this option can be found in your ***-info.plist" as described above. We might want to add to this answer. – Matthew Ferguson Jul 17 '21 at 02:30
21

The reason why applicationDidEnterBackground: and applicationDidEnterForeground: are never called is because these methods are used in joint with Application does not run in background this option can be found in your ***-info.plist. If this option is set to YES than your app will never call these methods, because these when you press the home button with an app that has set the option to YES the instance of the app that is running will get terminated so everytime you press the home button and then select the app icon a new instance is being created so it is using applicationWillTerminate:.

The methods that Kirti mali has said would also be the incorrect methods to use for want you are after, the reason being is that applicationDidBecomeActive: and applicationWillResignActive: are used when something like when you answer a phone call. The instance running is not terminated neither is it sent to the background. The instance is paused until the user has finished on that call when it will become active again.

So the solution to this would be if you want the app to run in background would be to change the option "Application does not run in background" in the ***-info.plist to be NO just applicationDidBecomeActive: and applicationWillResignActive: is the wrong way for these methods to be used.

Please see the apple documentation on UIApplicationDelegate to get a better understanding of these methods.

nishanthshanmugham
  • 2,967
  • 1
  • 25
  • 29
Popeye
  • 11,839
  • 9
  • 58
  • 91
  • Thanks a lot. I can understand now. It was very helpful. Actually I want to call a timer only when it is in background. And this application is using location service and thus it "Application does not run in background" in info.plist is set to "YES". Is there any way to know if it enters background? – aparna Apr 13 '13 at 17:49
  • Put some `NSLogs` to run on an `NSTimers`, but you will know if it was sent to background because once you come back to the app the app instance that was running will continue. – Popeye Apr 13 '13 at 17:52
  • Just make sure you set "Application does not run in background" to NO and it will run in background. – Popeye Apr 13 '13 at 17:54
  • No actually my application should get location updates every 5 minutes so that it does not goes to suspend state. Basically more like a mail client, it will check the server for few updates and have to notify users accordingly when its in background. Instead of using Push notification I did it using local notification + location service. So I cant make "App does not run in background" to NO. Is there any other option to do so – aparna Apr 15 '13 at 06:03
  • I am going to say no, well not at least anything I know about. – Popeye Apr 15 '13 at 06:33
  • There is no way to get notification unless the app is using any of the background services like Location,VOIP or using Push Notification – aparna May 17 '13 at 08:27
  • @Popeye You've just saved me a lot of problems. I owe you a gift next Xmas. – Jean Le Moignan Mar 04 '16 at 15:53
  • @Popeye I am not able to find "Application does not run in background" in the info.plist. I'm on Xcode 12.1, IOS 14 and Swift 5 – Khushneet Nov 23 '20 at 08:23
  • While this answer is technically correct, as of iOS13+ the main reason for these methods not being called is going to be Window Scene Delegate's methods. – Async- Jul 14 '21 at 18:18
7

Since your App runs on background those methods will never called however you can use willEnterForegroundNotification and didEnterBackgroundNotification to do same thing you want.

You can write following codes inside the didFinishLaunchingWithOptions method of ApplicationDelegate

    NotificationCenter.default.addObserver(forName:UIApplication.willEnterForegroundNotification, object: nil, queue: nil) { (_) in
        // Your Code here
}

NotificationCenter.default.addObserver(forName:UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { (_) in
       // Your Code here        
}
Shafraz Buhary
  • 643
  • 6
  • 14
  • I actually had to use UIApplication.didBecomeActiveNotification to catch when my app was coming to the foreground, but other than that it makes sense. – wildcat12 May 10 '20 at 16:26
  • This is exactly what I needed! Works like a charm! (I don't need my app to run in the background, just want to get notified to update the game state when it comes back). I don't know why it's 0 votes here, unfair. Voting up! – Mike Keskinov Sep 04 '20 at 14:45
-4

This method is called when home button is pressed

- (void)applicationDidBecomeActive:(UIApplication *)application

and this method is called when icon button is pressed

- (void)applicationWillResignActive:(UIApplication *)application
Popeye
  • 11,839
  • 9
  • 58
  • 91
kirti Chavda
  • 3,029
  • 2
  • 17
  • 29
  • I referred few sites which explained tat these methods will be called followed by the -(void)applicationDidBecomeActive:(UIApplication *)application and - (void)applicationWillResignActive:(UIApplication *)application http://www.cocoanetics.com/2010/07/understanding-ios-4-backgrounding-and-delegate-messaging/ and https://s3-ap-northeast-1.amazonaws.com/booksikindle/html/Beginning%20IOS%205%20Development1/Beginning_iO-ng_the_iOS_SDK_split_039.html. – aparna Mar 14 '13 at 10:19
  • This is the most incorrect answer I've ever found on StackOverflow. Purely evident wrong information. – albertodebortoli Dec 27 '20 at 13:04