Recently I have found the need to make an app for iPhone that it was sending the position of the user all the time to our server, and then, I have realized the little information about this topic that you can find on internet. I will try to explain how I solved it and I hope this could be helpful to someone who has encountered with the same problem.

I have to admit that I ignored that an app for iOS could be “waking up” without the direct action of the user before I start this development, and this was my first problem to solve for make the app useful even when the app is off. I found two ways to do this, and booth are related with the location.

The first way is using startMonitoringSignificantLocationChanges, that “wake up” the app when it detects a telephony antenna change. Using this option, we save the batery of the phone, but the update of the position will take a long time, because it depends of the quantity of telephony antennas in the area. In a city, the update could be fast, but outside the city, the update could take a long time.

The second way is using regions. With this option, we could define circular regions and the app “wake up” when users enters or leaves the region. This way is more accurate, but battery consumption will be considerably higher.

After doing a lot of tests, I finally have used both options. The region’s option gives me an accuracy location and makes a permanent monitoring of the position. I used the antenna’s option in case of any emergency, for instance when the gps signal is poor and I am not abble to create the regions, in this case and as a last resort, I use the antenna changes until the gps signal returns.

But let’s start seeing a few code to understand the way to work. For this instance, we will create a project based in a Single View, because we are not going to use the screen for anything more than showing a message to turn off the app.

Once the project will be created, we will add the CoreLocation framework and then we will start to work with the AppDelegate class, that be the only one we will use. In the .h file, we will import the location framework. We will add too the CoreLocation delegate and the locManager property that we will use to get de current location.


//AppDelegate.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
@interface IGAppDelegate : UIResponder <UIApplicationDelegate, CLLocationManagerDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) CLLocationManager *locManager;
@end

Now we will go to the .m file on the AppDelegate, here is where really will be the main part and I explain you point to point. The first step to do will be inicialitating the locManager property and then will start to manage the location.


-(void)initLoc
{
 locManager = [CLLocationManager new];
 locManager.delegate = self;
 [locManager startUpdatingLocation];
}

Once the method with which we initilize the location will be created, we will do it every time that our app starts.


- (BOOL)application: (UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
   [self initLoc];
   return YES;
}

The next step will be creating a method that add the regions which want control. In this case, we want to control constantly the position of the user, so it will be enough creating a method as the following, where we will create an area that has 100 meters of radius. I don’t know why, but sometimes when you exit from the region, the system don’t detects this action and for this situation, we will create three regions with differents radius to ensure that this action will be detected.


-(void)createRegions
{
   CLCircularRegion *regionCircular100 = [[CLCircularRegion alloc] initWithCenter:locManager.location.coordinate radius:100 identifier:@”TEST100"];
   [locManager startMonitoringForRegion:regionCircular100];
   CLCircularRegion *regionCircular1000 = [[CLCircularRegion alloc] initWithCenter:locManager.location.coordinate radius:1000 identifier:@”TEST1000"];
 [locManager startMonitoringForRegion:regionCircular1000];
   CLCircularRegion *regionCircular10000 = [[CLCircularRegion alloc] initWithCenter:locManager.location.coordinate radius:10000 identifier:@”TEST10000"];
 [locManager startMonitoringForRegion:regionCircular10000];
}

With this code, we ensure that every time that the user goes out of the radius, the app starts during a few seconds to do the fast action that we need.
The next will be creating a region calling the method createRegions.
For ensure that any problem has occurred, we check that the region was created and we make to repeat four times if it will be necessary.


-(void)startsRegion
{
   int errorI = 0;
   while ([[locManager monitoredRegions] count] < 3)
   {
      ++errorI;
      if (errorI == 4)
      {
         break;
      }
      [self createRegions];
   }
   sleep(2);
}

The next is control what will do when the device exits from one of the regions. The first to will do is remove the regions that we had created. After that, we will initialize the CLLocationManager with the method initLoc and we will create the new regions with the method startsRegion. At last we will check that after the four attempts, any region was created, if it was created, we need exit from the app, else we need start the detection by antennas changes with startMonitoringSignificantLocationChanges. When you will write this, you will see that Xcode show you a warning, that is because you are initializing a CLLocationManager like a CLLocation, this is one of the first extrange situations that I have founded. Mayby anyone understand this and is able to explain me, but the situation is that, if you initialize like a CLLocationManager, the app never will be called when the system detect an antenna changes, the only way to work for me was this. Another extrange thing is that the instruction [clLocAux startMonitoringSignificantLocationChanges] need be the last instruction called for the app, else doesn’t work.


-(void)locationManager: (CLLocationManager *)manager didExitRegion: (CLRegion *)region
{
   for (CLRegion *monitored in [manager monitoredRegions])
   {
      [manager stopMonitoringForRegion:monitored];
   }
   [self initLoc];
   [self startsRegion];
   if (([[locManager monitoredRegions] count] == 0))
   {
      CLLocationManager *clLocAux = [[CLLocation alloc] init];
      [clLocAux startMonitoringSignificantLocationChanges];
   }
   else
   {
      exit(0);
   }
}

Now, we only need to create the regions in the appropriate moments. We will start creating the method when the app is killed doing switch to up from the app administrator. This make that when the app is terminated, the regions will be activated.


- (void)applicationWillTerminate: (UIApplication *)application
{
  [self startsRegion];
}

Afterwards we only need to know what to do when the app opens after the device exits from a region, and for that we come back to the didFinishLaunchingWithOptions:launchOptions and add this condition after initializing the location: if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]).
With this, we will know that the app enters by a system calls, and not because a user opens the app. This is other extrange situation. Sometimes, I don’t know why, after exit from a region, the system “wake up” the app but never calls the didExitRegion, so I will be forced to create a region and do a call to didExitRegion using this region after the app starts to ensure that the didExitRegion is called. You can test this part for be sure that you need it or not, but in my case, I had to include if I want to be sure that the app works fine.


- (BOOL)application: (UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
  [self initLoc];
  if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey])
  {
     CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:locManager.location.coordinate radius:100 identifier:@”FORCE”];
     [self locationManager:self.locManager didExitRegion:region];
  }
  return YES;
}

That’s all to do for controlling the user position, now we will need to know if the app is working correctly, but as the app is working in background and we can’t see anything, we need to send a local notification when we will need update the server, to check the process. For that we will create a method called sendNoti, sending the message to display. This is only necessary to test the process.


-(void)sendNoti: (NSString *)mensaje
{
   UILocalNotification *reminder = [[UILocalNotification alloc] init];
   [reminder setFireDate:[NSDate date]];
   [reminder setTimeZone:[NSTimeZone localTimeZone]];
   [reminder setHasAction:YES];
   [reminder setAlertAction:@”Show”];
   [reminder setSoundName:@”bell.mp3"];
   [reminder setAlertBody:mensaje];
   [[UIApplication sharedApplication] scheduleLocalNotification:reminder];
}

Finally we will send the notification when entry in didExitRegion.


-(void)locationManager: (CLLocationManager *)manager didExitRegion: (CLRegion *)region
{
   [self sendNoti:@”EXIT REGION”];
}

That’s all. I apreciate that if you find any mistakes or you will improve the code, you will tell me to update this example and the readers will be helped for develop this kind of functionality. I hope you find this post helpful and above this lines you can find the link to that example in Github.

Github example