Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added initial version of DMLocationManager

  • Loading branch information...
commit 1780ca482e6b03a5d7f5683b61a8409852441e2f 1 parent 40851df
Martin Stolz authored
Showing with 734 additions and 0 deletions.
  1. +250 −0 DMLocationManager.h
  2. +484 −0 DMLocationManager.m
View
250 DMLocationManager.h
@@ -0,0 +1,250 @@
+//
+// Copyright devmob (Martin Stolz) | devmob.de
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreLocation/CoreLocation.h>
+
+/**
+ * The DMLocationManager is a convinience wrapper for the CLLocationManager.
+ * It accepts multiple delegate instances at one time.
+ *
+ * DMLocationManager* locationManager = [DMLocationManager sharedLocationManager];
+ * [locationManager addDelegate:self];
+ * [locationManager removeDelegate:self];
+ *
+ * To avoid bad accuracy of location the property 'queryingInterval' defines how long to search for a location with the desired accuracy.
+ *
+ * DMLocationManager* locationManager = [DMLocationManager sharedLocationManager];
+ * locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
+ * locationManager.queryingInterval = 10.0;
+ *
+ * To avoid getting cached location coordinates deactivate it by setting NO to 'useCache' property.
+ *
+ * DMLocationManager* locationManager = [DMLocationManager sharedLocationManager];
+ * locationManager.useCache = NO;
+ *
+ * The 'cacheAge' defines how old a location is allowed to be.
+ *
+ * DMLocationManager* locationManager = [DMLocationManager sharedLocationManager];
+ * locationManager.cacheAge = 10.0;
+ *
+ * If nessecary the property 'updateLocationOnApplicationDidBecomeActive' can be used to allow the update of the location on application (re-)start.
+ *
+ * DMLocationManager* locationManager = [DMLocationManager sharedLocationManager];
+ * locationManager.updateLocationOnApplicationDidBecomeActive = YES;
+ *
+ * To keep the location permanent up to date the property 'loop' defines whether to repeadeatly determine new location coordinates.
+ * The property 'loopTimeInterval' defines how long to sleep between determining the location successfully and beginning the next determination.
+ *
+ * DMLocationManager* locationManager = [DMLocationManager sharedLocationManager];
+ * locationManager.loop = YES;
+ * locationManager.loopTimeInterval = 10.0;
+ *
+ *
+ *
+ * How it works:
+ *
+ * Use location manager singleton instance:
+ *
+ * DMLocationManager* locationManager = [DMLocationManager sharedLocationManager];
+ *
+ * Listen for changes by setting delegate instance:
+ *
+ * [locationManager addDelegate:self];
+ *
+ * Make listening instance optionally conform to DMLocationManagerDelegate:
+ *
+ * - (void)locationManager:(DMLocationManager*)manager didChangeLocationServiceEnabledState:(BOOL)isLocationServiceEnabled {
+ * }
+ *
+ * - (void)locationManagerWillUpdateLocation:(DMLocationManager*)manager {
+ * }
+ *
+ * - (void)locationManagerDidStopUpdateLocation:(DMLocationManager*)manager {
+ * }
+ *
+ * - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
+ * }
+ *
+ * Start determine location:
+ *
+ * [locationManager startUpdatingLocation];
+ *
+ * Stop determine location on leaving a view e.g.:
+ *
+ * [locationManager stopUpdatingLocation];
+ *
+ * Stop listening for location changes:
+ *
+ * [locationManager removeDelegate:self];
+ *
+ * Activate logging by setting the log level define e.g.:
+ *
+ * #define DM_LOCATION_MANAGER_LOG_LEVEL DM_LOCATION_MANAGER_LOG_LEVEL_INFO
+ */
+
+@protocol DMLocationManagerDelegate;
+
+
+#pragma mark -
+#pragma mark DMLocationManager
+
+#define DM_LOCATION_MANAGER_LOG_LEVEL_NONE 0
+#define DM_LOCATION_MANAGER_LOG_LEVEL_INFO 1
+#define DM_LOCATION_MANAGER_LOG_LEVEL_WARNING 2
+#define DM_LOCATION_MANAGER_LOG_LEVEL_ERROR 3
+#define DM_LOCATION_MANAGER_LOG_LEVEL_DEBUG 4
+
+#define DM_LOCATION_MANAGER_LOG_LEVEL DM_LOCATION_MANAGER_LOG_LEVEL_INFO
+
+@interface DMLocationManager : NSObject <CLLocationManagerDelegate>
+{
+@private
+ NSMutableArray* _delegates; // Delegate instances which must be served
+
+ CLLocationManager* _locationManager; // Shared location manager instance
+ CLLocation* _location;
+ BOOL _useCache;
+ NSTimeInterval _cacheAge;
+ BOOL _isLocationServiceEnabled;
+
+ BOOL _updateLocationOnApplicationDidBecomeActive;
+
+ BOOL _loop;
+ NSTimeInterval _loopTimeInterval;
+ NSTimer* _loopTimer; // Restart searching of new locations after one was found
+
+ NSTimeInterval _queryingInterval;
+ NSTimer* _queryingTimer; // On timeout the updating of location will be stopped
+}
+
+/**
+ * Last determined location by location manager. If nil the location was or could not be updated.
+ */
+@property (nonatomic, retain, readonly) CLLocation* location;
+
+/**
+ * The accuracy which should be aimed. If the accuracy is the desired one, the updating process will be stopped before the query time is reached.
+ * Default is -1.
+ */
+@property (nonatomic, assign) CLLocationAccuracy desiredAccuracy;
+
+/**
+ * Determines how long the location manager must search for new locations with desired accuracy.
+ * Default is 10 seconds.
+ */
+@property (nonatomic, assign) NSTimeInterval queryingInterval;
+
+/**
+ * Returns whether the location manager currently searches for new locations.
+ */
+@property (nonatomic, assign, readonly) BOOL isQuerying;
+
+/**
+ * If YES the last cached location of location manager will be used, which could be some days ago.
+ * Default is NO.
+ */
+@property (nonatomic, assign) BOOL useCache;
+
+/**
+ * The threshold until the location manager uses the cached location of core location manager.
+ * Default is 10 seconds.
+ */
+@property (nonatomic, assign) NSTimeInterval cacheAge;
+
+/**
+ * Returns whether the location service of the device is activated. If the application is not allowed this value will be updated by the first search of new location.
+ */
+@property (nonatomic, assign, readonly) BOOL isLocationServiceEnabled;
+
+/**
+ * If YES the delegates will be informed of location service state changes and a new location will be searched on (re-) activation of the app.
+ * Default is NO.
+ */
+@property (nonatomic, assign) BOOL updateLocationOnApplicationDidBecomeActive;
+
+/**
+ * Repeat searching for new locations after a location was determined.
+ * Default is NO.
+ */
+@property (nonatomic, assign) BOOL loop;
+
+/**
+ * After which amount of time after finding a location the search should be restarted
+ * Default is 10 seconds.
+ */
+@property (nonatomic, assign) NSTimeInterval loopTimeInterval;
+
+/**
+ * Returns the shared instance.
+ */
++ (DMLocationManager*) sharedLocationManager;
+
+/**
+ * Add a delegate of kind DMLocationManagerDelegate which must be served.
+ *
+ * @see DMLocationManagerDelegate
+ */
+- (void)addDelegate:(id<DMLocationManagerDelegate>) delegate;
+
+/**
+ * Remove a delegate of kind DMLocationManagerDelegate which must not be served.
+ *
+ * @see DMLocationManagerDelegate
+ */
+- (void)removeDelegate:(id<DMLocationManagerDelegate>) delegate;
+
+/**
+ * Start updating the location
+ */
+- (void)startUpdatingLocation;
+
+/**
+ * Stop updating the location
+ */
+- (void)stopUpdatingLocation;
+
+@end
+
+
+#pragma mark -
+#pragma mark DMLocationManagerDelegate
+
+@protocol DMLocationManagerDelegate <CLLocationManagerDelegate>
+@optional
+
+/**
+ * Informs about changes of location service state.
+ *
+ * @see DMLocationManager
+ */
+- (void)locationManager:(DMLocationManager*)manager didChangeLocationServiceEnabledState:(BOOL)isLocationServiceEnabled;
+
+/**
+ * Informs when new locations will be started to search for.
+ *
+ * @see DMLocationManager
+ */
+- (void)locationManagerWillUpdateLocation:(DMLocationManager*)manager;
+
+/**
+ * Informs about stop searching new locations.
+ *
+ * @see DMLocationManager
+ */
+- (void)locationManagerDidStopUpdateLocation:(DMLocationManager*)manager;
+
+@end
View
484 DMLocationManager.m
@@ -0,0 +1,484 @@
+//
+// Copyright devmob (Martin Stolz) | devmob.de
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "DMLocationManager.h"
+
+@interface DMLocationManager (private)
+- (void)initLocationManager;
+- (void)initListener;
+- (void)update;
+
+- (void)startQueryingTimer;
+- (void)stopQueryingTimer;
+
+- (void)startLoopTimer;
+- (void)stopLoopTimer;
+
+- (void)willUpdateLocationHandler;
+
+- (void)informDidChangeLocationServiceEnabledState:(BOOL)locationServiceEnabled;
+- (void)informWillUpdateLocation;
+- (void)informDidStopUpdateLocation;
+- (void)informDidUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation;
+- (void)informDidFailWithError:(NSError*)error;
+@end
+
+static DMLocationManager* sharedLocationManager = nil;
+
+@implementation DMLocationManager
+
+@synthesize location = _location;
+@dynamic desiredAccuracy;
+
+@dynamic isQuerying;
+@synthesize queryingInterval = _queryingInterval;
+
+@synthesize useCache = _useCache;
+@synthesize cacheAge = _cacheAge;
+@synthesize isLocationServiceEnabled = _isLocationServiceEnabled;
+
+@synthesize updateLocationOnApplicationDidBecomeActive = _updateLocationOnApplicationDidBecomeActive;
+
+@synthesize loopTimeInterval = _loopTimeInterval;
+@synthesize loop = _loop;
+
++ (DMLocationManager*) sharedLocationManager
+{
+ if (nil == sharedLocationManager)
+ {
+ sharedLocationManager = [[DMLocationManager alloc] init];
+ }
+
+ return sharedLocationManager;
+}
+
+
+#pragma mark -
+#pragma mark Initialization
+
+- (id) init
+{
+ self = [super init];
+ if (self != nil)
+ {
+ [self initLocationManager];
+ [self initListener];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ _locationManager.delegate = nil;
+ [_locationManager release];
+
+ [_delegates release];
+
+ [_queryingTimer release];
+
+ [super dealloc];
+}
+
+- (void)initLocationManager
+{
+ _locationManager = [CLLocationManager new];
+
+ _delegates = [NSMutableArray new];
+
+ _useCache = YES;
+ _cacheAge = 10.0;
+ _queryingInterval = 10.0;
+
+ _isLocationServiceEnabled = [CLLocationManager locationServicesEnabled];
+
+ _updateLocationOnApplicationDidBecomeActive = NO;
+
+ _loop = NO;
+ _loopTimeInterval = 10.0;
+}
+
+- (void)initListener
+{
+ // Listen for 'did become active' of application
+ [[NSNotificationCenter defaultCenter] addObserver: self
+ selector: @selector(update:)
+ name: UIApplicationDidBecomeActiveNotification
+ object: nil];
+
+ // Listen for application enters background
+ [[NSNotificationCenter defaultCenter] addObserver: self
+ selector: @selector(update:)
+ name: UIApplicationDidEnterBackgroundNotification
+ object: nil];
+}
+
+
+#pragma mark -
+#pragma mark Update
+
+- (void)update:(NSNotification*)notification
+{
+ // Update location manager after restart of app
+ if ([UIApplicationDidBecomeActiveNotification isEqualToString: [notification name]])
+ {
+ // Did location service changed enabled state?
+ BOOL locationServicesEnabled = [CLLocationManager locationServicesEnabled];
+ if (_isLocationServiceEnabled != locationServicesEnabled)
+ {
+ _isLocationServiceEnabled = locationServicesEnabled;
+ [self informDidChangeLocationServiceEnabledState: _isLocationServiceEnabled];
+ }
+
+ // Whether to update the location on becoming active again
+ if (YES == _updateLocationOnApplicationDidBecomeActive)
+ {
+ [self startUpdatingLocation];
+ }
+ }
+
+ // Stop all processes on app entering background
+ else if ([UIApplicationDidEnterBackgroundNotification isEqualToString: [notification name]])
+ {
+ [self stopUpdatingLocation];
+ }
+}
+
+
+#pragma mark -
+#pragma mark Delegates
+
+- (void)addDelegate:(id<DMLocationManagerDelegate>) delegate
+{
+ if (nil == delegate)
+ return;
+
+ if ([_delegates containsObject: delegate])
+ return;
+
+ [_delegates addObject: delegate];
+}
+
+- (void)removeDelegate:(id<DMLocationManagerDelegate>) delegate
+{
+ if ([_delegates containsObject: delegate])
+ [_delegates removeObject: delegate];
+}
+
+
+#pragma mark -
+#pragma mark Inform delegates
+
+- (void)informDidChangeLocationServiceEnabledState:(BOOL)locationServiceEnabled
+{
+ for (id<DMLocationManagerDelegate> delegate in _delegates)
+ {
+ if ([delegate respondsToSelector: @selector(locationManager:didChangeLocationServiceEnabledState:)])
+ [delegate locationManager: self didChangeLocationServiceEnabledState: locationServiceEnabled];
+ }
+}
+
+- (void)informDidStopUpdateLocation
+{
+ for (id<DMLocationManagerDelegate> delegate in _delegates)
+ {
+ if ([delegate respondsToSelector: @selector(locationManagerDidStopUpdateLocation:)])
+ [delegate locationManagerDidStopUpdateLocation: self];
+ }
+}
+
+- (void)informWillUpdateLocation
+{
+ for (id<DMLocationManagerDelegate> delegate in _delegates)
+ {
+ if ([delegate respondsToSelector: @selector(locationManagerWillUpdateLocation:)])
+ [delegate locationManagerWillUpdateLocation: self];
+ }
+}
+
+- (void)informDidUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation
+{
+ for (id<DMLocationManagerDelegate> delegate in _delegates)
+ {
+ if ([delegate respondsToSelector: @selector(locationManager:didUpdateToLocation:fromLocation:)])
+ [delegate locationManager: _locationManager didUpdateToLocation: newLocation fromLocation: oldLocation];
+ }
+}
+
+- (void)informDidFailWithError:(NSError*)error
+{
+ for (id<DMLocationManagerDelegate> delegate in _delegates)
+ {
+ if ([delegate respondsToSelector: @selector(locationManager:didFailWithError:)])
+ [delegate locationManager: _locationManager didFailWithError: error];
+ }
+}
+
+
+#pragma mark -
+#pragma mark Location
+
+/**
+ * Start updating the location
+ *
+ */
+- (void)startUpdatingLocation
+{
+ [self willUpdateLocationHandler];
+
+ _locationManager.delegate = self;
+ [_locationManager startUpdatingLocation];
+}
+
+/**
+ * Stop updating the location
+ *
+ */
+- (void)stopUpdatingLocation
+{
+ [_locationManager stopUpdatingLocation];
+ _locationManager.delegate = nil;
+
+ [self stopQueryingTimer];
+ [self stopLoopTimer];
+
+ [self informDidStopUpdateLocation];
+}
+
+
+#pragma mark -
+#pragma mark Querying timer
+
+- (void)startQueryingTimer
+{
+ [self stopQueryingTimer];
+
+ _queryingTimer = [[NSTimer scheduledTimerWithTimeInterval: _queryingInterval target: self selector: @selector(queryingTimerPassed:) userInfo: nil repeats: YES] retain];
+}
+
+- (void)stopQueryingTimer
+{
+ if (_queryingTimer)
+ {
+ if ([_queryingTimer isValid])
+ {
+ [_queryingTimer invalidate];
+ }
+ [_queryingTimer release];
+ _queryingTimer = nil;
+ }
+}
+
+- (void)queryingTimerPassed:(NSTimer*)queryingTimer
+{
+ [self stopUpdatingLocation];
+ [self stopQueryingTimer];
+
+ if (_location)
+ {
+ [self informDidUpdateToLocation: _location fromLocation: nil];
+ }
+ else
+ {
+ [self informDidFailWithError: nil];
+ }
+}
+
+
+#pragma mark -
+#pragma mark Loop timer
+
+- (void)startLoopTimer
+{
+ [self stopLoopTimer];
+
+ _loopTimer = [[NSTimer scheduledTimerWithTimeInterval: _loopTimeInterval target: self selector: @selector(loopTimerPassed:) userInfo: nil repeats: YES] retain];
+}
+
+- (void)stopLoopTimer
+{
+ if (_loopTimer)
+ {
+ if ([_loopTimer isValid])
+ {
+ [_loopTimer invalidate];
+ }
+ [_loopTimer release];
+ _loopTimer = nil;
+ }
+}
+
+- (void)loopTimerPassed:(NSTimer*)loopTimer
+{
+ [self stopLoopTimer];
+ [self startUpdatingLocation];
+}
+
+
+#pragma mark -
+#pragma mark Desired accuracy
+
+- (void)setDesiredAccuracy:(CLLocationAccuracy)accuracy
+{
+ _locationManager.desiredAccuracy = accuracy;
+}
+
+- (CLLocationAccuracy) desiredAccuracy
+{
+ return _locationManager.desiredAccuracy;
+}
+
+
+#pragma mark -
+#pragma mark Public getter
+
+/**
+ * Returns whether the location manager currently searches for new locations.
+ *
+ */
+- (BOOL)isQuerying
+{
+ return [_queryingTimer isValid];
+}
+
+
+#pragma mark -
+#pragma mark Event handling
+
+/**
+ * Handling the start of querying for a new location.
+ *
+ */
+- (void)willUpdateLocationHandler
+{
+ [self startQueryingTimer];
+
+ [self informWillUpdateLocation];
+}
+
+/**
+ * Handling the new location.
+ *
+ */
+- (void)didUpdateLocationHandler
+{
+ [self stopUpdatingLocation];
+
+ [self informDidUpdateToLocation: _location fromLocation: nil];
+
+ // If YES start immedeatly searching new locations after one was found
+ if (YES == _loop)
+ {
+ [self startLoopTimer];
+ }
+}
+
+/**
+ * Handling the some error.
+ *
+ */
+- (void)didFailWithErrorHandler:(NSError*)error
+{
+ [self stopUpdatingLocation];
+
+ [self informDidFailWithError: error];
+}
+
+#pragma mark -
+#pragma mark CLLocationManagerDelegate
+
+/**
+ * Invoked when a new location is available. oldLocation may be nil if there is no previous location
+ *
+ */
+- (void)locationManager:(CLLocationManager*)manager
+ didUpdateToLocation:(CLLocation*)newLocation
+ fromLocation:(CLLocation*)oldLocation
+{
+#if DM_LOCATION_MANAGER_LOG_LEVEL >= DM_LOCATION_MANAGER_LOG_LEVEL_DEBUG
+ NSLog(@"locationManager didUpdateToLocation: %@\nfrom: %@", newLocation, oldLocation);
+#endif
+
+ // If cache is deactivated do only use fresh locations within 'cache time interval'
+ if (NO == _useCache)
+ {
+ NSDate* eventDate = newLocation.timestamp;
+ NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
+
+ if(abs(howRecent) > _cacheAge)
+ {
+#if DM_LOCATION_MANAGER_LOG_LEVEL >= DM_LOCATION_MANAGER_LOG_LEVEL_DEBUG
+ NSLog(@"locationManager didUpdateToLocation with timestamp %@ which is to old to use", newLocation.timestamp);
+#endif
+ return;
+ }
+ }
+
+ // If cache is activated or location is fresh enough determine the accuracy of the location in comparison to old locations
+ // If the desired accuracy is reached stop here with success, else let the location manager query again
+
+ // We have a measurement that meets our requirements, so we can stop updating the location
+ if (newLocation.horizontalAccuracy <= manager.desiredAccuracy)
+ {
+ [_location release];
+ _location = [newLocation retain];
+
+ [self didUpdateLocationHandler];
+ }
+ // New location is better than the old one but does not reach the desired accuracy
+ else if (_location == nil || _location.horizontalAccuracy > newLocation.horizontalAccuracy)
+ {
+ [_location release];
+ _location = [newLocation retain];
+ }
+}
+
+/**
+ * Invoked when an error occurred. Check error domain and code for reason.
+ *
+ */
+- (void)locationManager:(CLLocationManager*)manager
+ didFailWithError:(NSError*)error
+{
+#if DM_LOCATION_MANAGER_LOG_LEVEL >= DM_LOCATION_MANAGER_LOG_LEVEL_ERROR
+ NSLog(@"locationManager didFailWithError: %@", [error domain]);
+#endif
+
+ if ([error domain] == kCLErrorDomain)
+ {
+ switch ([error code])
+ {
+ case kCLErrorDenied:
+ {
+ if (_isLocationServiceEnabled != NO)
+ {
+ _isLocationServiceEnabled = NO;
+ [self informDidChangeLocationServiceEnabledState: _isLocationServiceEnabled];
+ }
+
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ [self didFailWithErrorHandler: error];
+}
+
+@end
Please sign in to comment.
Something went wrong with that request. Please try again.