Sunrise and Sunset in iOS using CoreLocation

Recently I found myself needing to determine the local sunrise and sunset times within an iOS application that I was working on. The app that we were building was designed with different color schemes for daytime and nighttime in order to make the app more pleasant to use in different lighting conditions.

There are several approaches that one could take to determine when it’s dark or light outside. For example, you could tap into the iPhones ambient light sensor to get an instantaneous brightness reading. Another technique could be send a request to a web service if a data connection is available. We decided to go with a different approach.

After doing a bit of searching online, I found a nice library that, when given a timezone, longitude, and latitude, will provide the local sunrise and sunset times. The library is an open source Cocoa-pod called EDSunriseSet and is a port of the original work of Paul Schlyter. (The EDSunriseSet not only provides sunrise and sunset times, but also civil, nautical, and astronomical twilight times.) So the question is, “how do you get the longitude and latitude?”

Fortunately, iOS provides a convenient framework called CoreLocation that you can use to gain access to the user’s location and thus obtain the longitude and latitude necessary for the calculation. CoreLocation is a powerful framework with lots of features that you can optionally use based on your apps needs.

Authorization Request

To use the features of CoreLocation, you must request permission from the user to access their location. There are two different types of requests that you can make: requestWhenInUseAuthorization or requestAlwaysAuthorization. If you use requestWhenInUseAuthorization, the app will only be able to use the location service when in the foreground. However, requestAlwaysAuthorization will give you access any time your application is running, including when it’s in the background.

Apple provides a standard prompt for presenting the request to the user, but it also gives you the ability to add a custom message to explain why you would like to access their location. To specify your custom message you must add the appropriate key to the info.plist, either NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription and set the value to your message text.

Checking for Available Features

As always, when you use a system framework, you need to take precautions to make sure the features you are trying to use are available on the given OS version. The code below is an example of how to fire up the location service. First, check the result of locationServicesEnabled to make sure the user hasn’t globally disabled all location services on the device. Next, if running on iOS8 or later, check the current authorization status. If the status is not yet determined, then make your request to the user. Finally, call `startUpdatingLocation`. It is safe to start the location services even if your app has not yet been granted permission to use them; the service will just not deliver any data until permission is granted.

- (void)startLocationService {
if (![CLLocationManager locationServicesEnabled]) {
NSLog(@"Location Services are DISABLED globally. Cannot start.");
}
else {
NSLog(@"Location Services are ENABLED");
if ([self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
// This means we're running on iOS 8 or later
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if(status == kCLAuthorizationStatusNotDetermined) {
NSLog(@"Requesting authorization to use CoreLocation");
[self.locationManager requestWhenInUseAuthorization];
} else if(status == kCLAuthorizationStatusAuthorizedWhenInUse || status == kCLAuthorizationStatusAuthorizedAlways) {
NSLog(@"App has authorization to use location service");
} else {
NSLog(@"Location Services are not available for this app");
}
}
[self.locationManager startUpdatingLocation];
}
}

Receiving the Data

To receive the location data from the service, you must implement the appropriate locationManager delegate method based on which type of location service you enabled. In the above example the following method would be used:

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;

The locations argument will contain one or more locations that were acquired by the service. The last item in the array will be the most recent. Always check the `timestamp` field of the location and make sure the location isn’t too stale. If the service had been unable to acquire a location for some period of time, the array may contain one or more older cached values.

The CoreLocation framework is feature rich, but the documentation provided by Apple is very good. Before you begin implementing, I would highly recommend that you read through docs and familiarize yourself with all the different levers that are available for you to pull. If you don’t, you may end up with data that is much less accurate then you were hoping for, or your app might be crushing the device’s battery unnecessarily.

Incorporating the Data

Once I was able to acquire a longitude and latitude as described above, it was only a couple more lines of code to get the information I was looking for. Add the EDSunriseSet cocoa-pod to your project and use it like this:

EDSunriseSet *sunInfo = [EDSunriseSet sunrisesetWithTimezone:timeZone
latitude:location.coordinate.latitude
longitude:location.coordinate.longitude];

Finally, call one (or both) of the following methods depending on which data you’re looking for.

[sunInfo calculateTwilight:[NSDate date]];
[sunInfo calculateSunriseSunset:[NSDate date]];

That’s it! Pretty neat, eh? What fun things have you done using CoreLocation?

Conversation
  • Stewart says:

    Hi Jordan,

    How do you then figure out if it’s daytime or nighttime?

  • Comments are closed.