├── LocateMe ├── en.lproj │ ├── InfoPlist.strings │ └── MainStoryboard.storyboard ├── Default.png ├── Default@2x.png ├── Default-568h@2x.png ├── LocateMe-Prefix.pch ├── main.m ├── LMAnnotation.m ├── LocateMe-Info.plist ├── LMViewController.h ├── LMAppDelegate.h ├── LMAnnotation.h ├── LMAppDelegate.m └── LMViewController.m ├── .gitignore ├── PinTracker ├── LMPinTracker.h └── LMPinTracker.m ├── LocationHandler ├── TTLocationHandler.h └── TTLocationHandler.m ├── README.md └── LocateMe.xcodeproj └── project.pbxproj /LocateMe/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LocateMe/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsdavids/TTLocationHandler/HEAD/LocateMe/Default.png -------------------------------------------------------------------------------- /LocateMe/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsdavids/TTLocationHandler/HEAD/LocateMe/Default@2x.png -------------------------------------------------------------------------------- /LocateMe/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsdavids/TTLocationHandler/HEAD/LocateMe/Default-568h@2x.png -------------------------------------------------------------------------------- /LocateMe/LocateMe-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'LocateMe' target in the 'LocateMe' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | .DS_Store 3 | 4 | # Generated Files 5 | *.pyc 6 | *.[oa] 7 | *.6 8 | *6.out 9 | docs/output/ 10 | docs/output-docset/ 11 | 12 | # Python modules 13 | MANIFEST 14 | dist/ 15 | build/ 16 | 17 | # Xcode 18 | Breakpoints.xcbkptlist 19 | xcuserdata 20 | *.xcworkspace 21 | *.xcuserstate 22 | *.pbxuser 23 | *.mode1v3 24 | *.mode2v3 25 | *.perspectivev3 26 | 27 | # Backup Files 28 | *~.nib -------------------------------------------------------------------------------- /LocateMe/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LocateMe 4 | // 5 | // Created by Dean Davids on 10/12/12. 6 | // Copyright (c) 2012 Dean Davids. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "LMAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([LMAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LocateMe/LMAnnotation.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMAnnotation.m 3 | // LocateMe 4 | // 5 | // Created by Dean Davids on 10/13/12. 6 | // Copyright (c) 2012 Dean S. Davids, Tailgate Technology. All rights reserved. 7 | // www.tailgatetechnology.com 8 | // 9 | // All of the code in this file shares same permissions as follows: 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 11 | // associated documentation files (the "Software"), to deal in the Software without restriction, 12 | // including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all copies or substantial 17 | // portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 20 | // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | #import "LMAnnotation.h" 26 | 27 | @implementation LMAnnotation 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /LocateMe/LocateMe-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIBackgroundModes 6 | 7 | location 8 | 9 | CFBundleDevelopmentRegion 10 | en 11 | CFBundleDisplayName 12 | ${PRODUCT_NAME} 13 | CFBundleExecutable 14 | ${EXECUTABLE_NAME} 15 | CFBundleIdentifier 16 | tailgateTechnologies.${PRODUCT_NAME:rfc1034identifier} 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | ${PRODUCT_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 1.0 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | 1.0 29 | LSRequiresIPhoneOS 30 | 31 | UIMainStoryboardFile 32 | MainStoryboard 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /LocateMe/LMViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMViewController.h 3 | // LocateMe 4 | // 5 | // Created by Dean Davids on 10/12/12. 6 | // Copyright (c) 2012 Dean S. Davids, Tailgate Technology. All rights reserved. 7 | // www.tailgatetechnology.com 8 | // 9 | // All of the code in this file shares same permissions as follows: 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 11 | // associated documentation files (the "Software"), to deal in the Software without restriction, 12 | // including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all copies or substantial 17 | // portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 20 | // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | #import 26 | #import 27 | 28 | @interface LMViewController : UIViewController 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /PinTracker/LMPinTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMPinTracker.h 3 | // LocateMe 4 | // 5 | // Created by Dean Davids on 10/17/12. 6 | // Copyright (c) 2012 Dean Davids. All rights reserved. 7 | // 8 | // All of the code in this file shares same permissions as follows: 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 10 | // associated documentation files (the "Software"), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 13 | // subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all copies or substantial 16 | // portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 19 | // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | #import 25 | 26 | @interface LMPinTracker : NSObject 27 | @property(nonatomic)NSTimeInterval uploadInterval; 28 | @end 29 | 30 | // Notification names 31 | extern NSString *const PinLoggerDidSaveNewLocation; -------------------------------------------------------------------------------- /LocateMe/LMAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMAppDelegate.h 3 | // LocateMe 4 | // 5 | // Created by Dean Davids on 10/12/12. 6 | // Copyright (c) 2012 Dean S. Davids, Tailgate Technology. All rights reserved. 7 | // www.tailgatetechnology.com 8 | // 9 | // All of the code in this file shares same permissions as follows: 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 11 | // associated documentation files (the "Software"), to deal in the Software without restriction, 12 | // including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all copies or substantial 17 | // portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 20 | // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | #import 26 | 27 | @class TTLocationHandler; 28 | 29 | @interface LMAppDelegate : UIResponder 30 | 31 | @property (strong, nonatomic) UIWindow *window; 32 | @property (nonatomic, strong)TTLocationHandler *sharedLocationHandler; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /LocateMe/LMAnnotation.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMAnnotation.h 3 | // LocateMe 4 | // 5 | // Created by Dean Davids on 10/13/12. 6 | // Copyright (c) 2012 Dean S. Davids, Tailgate Technology. All rights reserved. 7 | // www.tailgatetechnology.com 8 | // 9 | // All of the code in this file shares same permissions as follows: 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 11 | // associated documentation files (the "Software"), to deal in the Software without restriction, 12 | // including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all copies or substantial 17 | // portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 20 | // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | #import 26 | #import 27 | 28 | @interface LMAnnotation : NSObject 29 | 30 | // for mapping annotation 31 | @property (nonatomic, assign) CLLocationCoordinate2D coordinate; 32 | @property (nonatomic, copy) NSString *title; 33 | @property (nonatomic, copy) NSString *subTitle; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /LocateMe/LMAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMAppDelegate.m 3 | // LocateMe 4 | // 5 | // Created by Dean Davids on 10/12/12. 6 | // Copyright (c) 2012 Dean S. Davids, Tailgate Technology. All rights reserved. 7 | // www.tailgatetechnology.com 8 | // 9 | // All of the code in this file shares same permissions as follows: 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 11 | // associated documentation files (the "Software"), to deal in the Software without restriction, 12 | // including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all copies or substantial 17 | // portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 20 | // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | #import "LMAppDelegate.h" 26 | #import "TTLocationHandler.h" 27 | #import "LMPinTracker.h" 28 | 29 | @interface LMAppDelegate() 30 | @property(nonatomic, strong) LMPinTracker *pinTracker; 31 | @end 32 | 33 | @implementation LMAppDelegate 34 | 35 | +(void)initialize { 36 | 37 | NSMutableDictionary *defs = [NSMutableDictionary dictionary]; 38 | 39 | [defs setObject:[NSNumber numberWithInt:25] forKey:@"NUMBER_OF_PINS_SAVED"]; 40 | 41 | [[NSUserDefaults standardUserDefaults] registerDefaults:defs]; 42 | 43 | [super initialize]; 44 | } 45 | 46 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 47 | { 48 | /* 49 | // NOTE: for this demo to worki in the simulator, go to the menu "Debug/Location/Freeway Drive" and enable 50 | // to simulate the location events. 51 | */ 52 | 53 | // Set up the PinTracker 54 | self.pinTracker = [[LMPinTracker alloc]init]; 55 | self.pinTracker.uploadInterval = 60.0; 56 | 57 | // Set up the location handler. 58 | self.sharedLocationHandler = [TTLocationHandler sharedLocationHandler]; 59 | 60 | // Set background status. Update continuosly in background only when plugged in or regardless of power state. 61 | self.sharedLocationHandler.updatesInBackgroundWhenCharging = YES; 62 | // UPDATING IN BACKGROUND WHILE ON BATTERY WILL IMPACT THE USER'S BATTERY LIFE CONSIDERABLY 63 | self.sharedLocationHandler.continuesUpdatingOnBattery = YES; 64 | 65 | // Set interval of notices on change of location 66 | self.sharedLocationHandler.recencyThreshold = 10.0; 67 | 68 | return YES; 69 | } 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /LocationHandler/TTLocationHandler.h: -------------------------------------------------------------------------------- 1 | // TTLocationHandler.h 2 | // 3 | // Created by Dean Davids on 3/29/12. 4 | // Copyright (c) 2012 Dean S. Davids, Tailgate Technology. All rights reserved. 5 | // http://www.tailgatetechnology.com 6 | // 7 | // Portions of this software Copyright (c) 2010, Long Weekend LLC 8 | // Long Weekend LLC credit for basis of the origination of this code. 9 | // Original code and tutorial for same can be found at http://longweekendmobile.com/2010/06/30/location-region-data-in-background-on-ios4-iphone/ 10 | // 11 | // All of the code in this file shares same permissions as follows: 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 13 | // associated documentation files (the "Software"), to deal in the Software without restriction, 14 | // including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 15 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 16 | // subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all copies or substantial 19 | // portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 22 | // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | #import 28 | #import 29 | 30 | //! Responsible for all Core Location interaction, implements delegate methods of CLLocationManager 31 | @interface TTLocationHandler : NSObject 32 | 33 | // Singleton class method to retrieve or create 34 | + (id) sharedLocationHandler; 35 | 36 | //! Helper function to determine if this reading is good enough in terms of accuracy 37 | - (BOOL) isLocationWithinRequiredAccuracy:(CLLocation *)location; 38 | 39 | //! Helper function to determine if this reading is good enough in terms of recency 40 | - (BOOL) isReadingRecentForLocation: (CLLocation *)location; 41 | 42 | // Registers a region for region monitoring. 43 | - (BOOL)registerNotificationForLocation:(CLLocation *)myLocation withRadius:(NSNumber *)myRadius assignIdentifier:(NSString *)identifier; 44 | 45 | // Remove all registered regions currently being monitored; 46 | - (void)removeAllMonitoredRegions; 47 | 48 | // Returns a region comprised of the current location and of the requested radius 49 | -(CLRegion *)currentRegionWithRadius:(CLLocationDistance)radius; 50 | 51 | @property (nonatomic, strong) CLLocationManager *locationManager; 52 | @property (nonatomic, copy) CLLocation *lastKnownLocation; 53 | 54 | // Monitoring options 55 | @property (nonatomic) BOOL continuesUpdatingWhileActive; 56 | @property (nonatomic) BOOL continuesUpdatingOnBattery; 57 | @property (nonatomic) BOOL updatesInBackgroundWhenCharging; 58 | @property (nonatomic) BOOL ignorePossibleDuplicates; 59 | @property (nonatomic) NSInteger recencyThreshold; 60 | @property (nonatomic) CLLocationAccuracy requiredAccuracy; 61 | @property (nonatomic) BOOL walkMode; 62 | 63 | @end 64 | 65 | // Notification names 66 | extern NSString *const LocationHandlerDidUpdateLocation; 67 | extern NSString *const LocationHandlerDidCrossBoundary; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #TTLocationHandler 2 | 3 | This is the evolution of my experimentation and testing the best manner of use of location services in iOS. It started with a base derived from the code and all that I learned from a series by Mark at The Long Weekend Website. 4 | 5 | You can find that blog, and the original classes here: 6 | http://longweekendmobile.com/2010/06/30/location-region-data-in-background-on-ios4-iphone/ 7 | 8 | That code was released "via an MIT-style license (=do whatever you want with it, but don't blame me, and include my copyright notice in the code if you change/redistribute/use)." Everything in the TTLocationHandler class files follow suit and full credit is given to Long Weekend, LLC for helping to get my own understanding of location handling off the ground. 9 | 10 | In this repository, you will find one class header and implementation that you want to import if you intend to try it in your own project. The entire class is comprised of the files TTLocationHandler.h and TTLocationHandler.m. The other classes are all just a quick mashup to demonstrate use and check that all works as intended. 11 | 12 | ##Event Handling Notes: 13 | - Handler receives the location events from the locationManager, checks for accuracy and time since last event. 14 | - If the last event was stored a set interval earlier, and is within the required accuracy, the location info is accepted and saved. 15 | - Saving a location from a location event is done in a property of the handler and in the standardUserDefaults. Saving in the defaults allows any class in the project to get the most recent location without having to import, hold reference to, or even to know anything at all about the locationHandler class. 16 | - If the time since previous saved event is outside our parameters and the new location is inaccurate, it is stored in a queue array and considered for acceptance only if another more accurate location isn't received. Ten seconds is allowed to wait for more accurate event, and max tries of 10 before giving up and best available is accepted. 17 | 18 | ##Power usage and Background Operation Notes: 19 | - The default is allow continuous updating of location when the application is in foreground. This can be set on or off. 20 | - The default is to switch to significantUpdates only when in the background. There is a setting for continuous updates in background when the device is plugged into external power. 21 | - You can also set continuesUpdatingOnBattery. Use this responsibly as it will impact batter life considerably. 22 | - The locationManager distanceFilter is increased at highway speeds as events come in scary fast and it seems a waste of activity. 23 | - Make note of the backgroundTaskIdentifier assignment. The operations were very unreliable before I included this. I recommend using the begin and end task lines where ever you address responding to the location notification. I also recommend putting those operations on a global queue. 24 | 25 | ## 26 | All of the paramaters are configurable including accuracy required, distance between location events, max tries for accuracy, wait time for accuracy, continue in background, highway mode. I have tried many variations and the defaults here are what I have had the best results with. 27 | My experience, in testing on several devices, is very little impact on battery life and reliable tracking with no serious issues. I am interested to see what improvement might be made by others. 28 | Please fork and add your input. Any ideas will be considered for push. My aim is to improve what I have and share it for all interested. -------------------------------------------------------------------------------- /PinTracker/LMPinTracker.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMPinTracker.m 3 | // LocateMe 4 | // 5 | // Created by Dean Davids on 10/17/12. 6 | // Copyright (c) 2012 Dean Davids. All rights reserved. 7 | // 8 | // All of the code in this file shares same permissions as follows: 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 10 | // associated documentation files (the "Software"), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 13 | // subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all copies or substantial 16 | // portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 19 | // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | #import "LMPinTracker.h" 25 | #import "TTLocationHandler.h" 26 | 27 | @interface LMPinTracker() 28 | 29 | { 30 | NSDate *_mostRecentUploadDate; 31 | } 32 | 33 | -(void)storeMostRecentLocationInfo; 34 | -(void)handleLocationUpdate; 35 | -(void)uploadCurrentData; 36 | 37 | @end 38 | 39 | @implementation LMPinTracker 40 | 41 | -(id)init 42 | { 43 | self = [super init]; 44 | if (self) { 45 | 46 | NSNotificationCenter *defaultNotificatoinCenter = [NSNotificationCenter defaultCenter]; 47 | [defaultNotificatoinCenter addObserver:self selector:@selector(handleLocationUpdate) name:LocationHandlerDidUpdateLocation object:nil]; 48 | } 49 | 50 | return self; 51 | } 52 | 53 | -(void)storeMostRecentLocationInfo 54 | { 55 | static int locationIndex = 0; 56 | 57 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 58 | NSDictionary *lastKnowLocationInfo = [defaults objectForKey:@"LAST_KNOWN_LOCATION"]; 59 | if (!lastKnowLocationInfo) { 60 | return; 61 | } 62 | 63 | // store the location info 64 | NSString *theKey = [NSString stringWithFormat:@"location%i", locationIndex]; 65 | [defaults setObject:[NSDictionary dictionaryWithDictionary:lastKnowLocationInfo] forKey:theKey]; 66 | 67 | int numberOfPinsSaved = [defaults integerForKey:@"NUMBER_OF_PINS_SAVED"]; 68 | 69 | if (locationIndex == numberOfPinsSaved) { 70 | locationIndex = 0; 71 | return; 72 | } 73 | 74 | locationIndex++; 75 | } 76 | 77 | -(void)handleLocationUpdate 78 | { 79 | UIApplication *app = [UIApplication sharedApplication]; 80 | __block UIBackgroundTaskIdentifier locationUpdateTaskID = [app beginBackgroundTaskWithExpirationHandler:^{ 81 | dispatch_async(dispatch_get_main_queue(), ^{ 82 | if (locationUpdateTaskID != UIBackgroundTaskInvalid) { 83 | // *** CONSIDER MORE APPROPRIATE RESPONSE TO EXPIRATION *** // 84 | [app endBackgroundTask:locationUpdateTaskID]; 85 | locationUpdateTaskID = UIBackgroundTaskInvalid; 86 | } 87 | }); 88 | }]; 89 | 90 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 91 | //Enter Background Operations here 92 | [self storeMostRecentLocationInfo]; 93 | 94 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 95 | NSDictionary *lastKnowLocationInfo = [defaults objectForKey:@"LAST_KNOWN_LOCATION"]; 96 | 97 | // Alternately, lastKnownLocation could be obtained directly like so: 98 | CLLocation *lastKnownLocation = [[TTLocationHandler sharedLocationHandler] lastKnownLocation]; 99 | NSLog(@"Alternate location object directly from handler is \n%@",lastKnownLocation); 100 | 101 | if (!lastKnowLocationInfo) { 102 | return; 103 | } 104 | 105 | // Store the location into your sqlite database here 106 | NSLog(@"Retrieved from defaults location info: \n%@ \n Ready for store to database",lastKnowLocationInfo); 107 | 108 | NSTimeInterval timeSinceLastUpload = [_mostRecentUploadDate timeIntervalSinceNow] * -1; 109 | 110 | if (timeSinceLastUpload == 0 || timeSinceLastUpload >= self.uploadInterval) { 111 | [self uploadCurrentData]; 112 | } 113 | 114 | // Close out task Idenetifier on main queue 115 | dispatch_async(dispatch_get_main_queue(), ^{ 116 | // Send notification for any class that needs to know 117 | NSNotification *aNotification = [NSNotification notificationWithName:PinLoggerDidSaveNewLocation object:[lastKnownLocation copy]]; 118 | [[NSNotificationCenter defaultCenter] postNotification:aNotification]; 119 | 120 | if (locationUpdateTaskID != UIBackgroundTaskInvalid) { 121 | [app endBackgroundTask:locationUpdateTaskID]; 122 | locationUpdateTaskID = UIBackgroundTaskInvalid; 123 | } 124 | }); 125 | }); 126 | } 127 | 128 | -(void)uploadCurrentData 129 | { 130 | // Do your upload to web operations here 131 | 132 | 133 | 134 | NSLog(@"Uploaded location data to the web"); 135 | _mostRecentUploadDate = [NSDate date]; 136 | } 137 | 138 | @end 139 | 140 | // Notification names 141 | NSString* const PinLoggerDidSaveNewLocation = @"PinLoggerDidSaveNewLocation"; -------------------------------------------------------------------------------- /LocateMe/LMViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMViewController.m 3 | // LocateMe 4 | // 5 | // Created by Dean Davids on 10/12/12. 6 | // Copyright (c) 2012 Dean S. Davids, Tailgate Technology. All rights reserved. 7 | // www.tailgatetechnology.com 8 | // 9 | // All of the code in this file shares same permissions as follows: 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 11 | // associated documentation files (the "Software"), to deal in the Software without restriction, 12 | // including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all copies or substantial 17 | // portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 20 | // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | #import "LMViewController.h" 26 | #import "TTLocationHandler.h" 27 | #import "LMAnnotation.h" 28 | #import "LMPinTracker.h" 29 | 30 | @interface LMViewController () 31 | // Outlets 32 | @property (nonatomic, weak) IBOutlet UIButton *resetButton; 33 | @property (nonatomic, weak) IBOutlet UISwitch *backgroundToggleSwitch; 34 | @property (nonatomic, weak) IBOutlet UITextField *refreshIntervalField; 35 | @property (nonatomic, weak) IBOutlet UIStepper *refreshIntervalStepper; 36 | @property (nonatomic, weak) IBOutlet UIButton *walkModeToggleButton; 37 | // Private Properties 38 | @property (nonatomic, strong)NSArray *locationsArray; 39 | @property (nonatomic, weak)IBOutlet MKMapView *mapView; 40 | // Private Methods 41 | -(void)handleLocationUpdate; 42 | -(void)updateLocationsArray; 43 | -(void)refreshMapView; 44 | -(void)clearStoredLocations; 45 | // Actions 46 | -(IBAction)resetButtonTouched:(id)sender; 47 | -(IBAction)backgroundSwitchTouche:(id)sender; 48 | -(IBAction)intervalStepActivated:(id)sender; 49 | -(IBAction)toggleWalkMode:(id)sender; 50 | @end 51 | 52 | 53 | @implementation LMViewController 54 | { 55 | BOOL _currentWalkMode; 56 | } 57 | 58 | - (void)viewDidLoad 59 | { 60 | [super viewDidLoad]; 61 | // Do any additional setup after loading the view, typically from a nib. 62 | 63 | NSNotificationCenter *defaultNotificatoinCenter = [NSNotificationCenter defaultCenter]; 64 | [defaultNotificatoinCenter addObserver:self selector:@selector(handleLocationUpdate) name:PinLoggerDidSaveNewLocation object:nil]; 65 | 66 | TTLocationHandler *sharedHandler = [TTLocationHandler sharedLocationHandler]; 67 | NSInteger interval = sharedHandler.recencyThreshold; 68 | self.refreshIntervalStepper.value = interval; 69 | self.refreshIntervalField.text = [NSString stringWithFormat:@"%i sec",interval]; 70 | 71 | self.backgroundToggleSwitch.on = sharedHandler.continuesUpdatingOnBattery; 72 | _currentWalkMode = sharedHandler.walkMode; 73 | [self updateWalkButtonText]; 74 | 75 | sharedHandler = nil; 76 | 77 | // Set up our view 78 | [self updateLocationsArray]; 79 | [self refreshMapView]; 80 | } 81 | 82 | - (void)viewDidDisappear:(BOOL)animated 83 | { 84 | [super viewDidDisappear:animated]; 85 | 86 | NSNotificationCenter *defaultNotificationCenter = [NSNotificationCenter defaultCenter]; 87 | [defaultNotificationCenter removeObserver:self name:PinLoggerDidSaveNewLocation object:nil]; 88 | } 89 | 90 | - (void)viewWillAppear:(BOOL)animated 91 | { 92 | [super viewWillAppear:animated]; 93 | 94 | NSNotificationCenter *defaultNotificatoinCenter = [NSNotificationCenter defaultCenter]; 95 | [defaultNotificatoinCenter addObserver:self selector:@selector(handleLocationUpdate) name:PinLoggerDidSaveNewLocation object:nil]; 96 | } 97 | 98 | - (void)didReceiveMemoryWarning 99 | { 100 | [super didReceiveMemoryWarning]; 101 | // Dispose of any resources that can be recreated. 102 | } 103 | 104 | #pragma mark - Private Methods 105 | 106 | -(void)refreshMapView 107 | { 108 | NSArray *oldAnnotations = [_mapView.annotations filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"!(self isKindOfClass: %@)", [MKUserLocation class]]]; 109 | [self.mapView removeAnnotations:oldAnnotations]; 110 | MKMapRect zoomRect = MKMapRectNull; 111 | for (id annotation in self.locationsArray) { 112 | CLLocationCoordinate2D thisLocation = annotation.coordinate; 113 | if (CLLocationCoordinate2DIsValid(thisLocation) && ![annotation isKindOfClass:[MKUserLocation class]]) { 114 | [self.mapView addAnnotation:annotation]; 115 | // determine limits of map 116 | MKMapPoint annotationPoint = MKMapPointForCoordinate(thisLocation); 117 | MKMapRect pointRect = MKMapRectMake(annotationPoint.x - 4500.0, annotationPoint.y - 6000.0, 9000.0, 12000.0); 118 | if (MKMapRectIsNull(zoomRect)) { 119 | zoomRect = pointRect; 120 | } else { 121 | zoomRect = MKMapRectUnion(zoomRect, pointRect); 122 | } 123 | } 124 | } 125 | [self.mapView setVisibleMapRect:zoomRect animated:YES]; 126 | } 127 | 128 | -(void)handleLocationUpdate 129 | { 130 | [self updateLocationsArray]; 131 | [self refreshMapView]; 132 | } 133 | 134 | -(void)updateLocationsArray 135 | { 136 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 137 | int numberOfPins = [defaults integerForKey:@"NUMBER_OF_PINS_SAVED"]; 138 | NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:numberOfPins]; 139 | 140 | for (int index = 0; index < numberOfPins; index++) { 141 | NSString *theKey = [NSString stringWithFormat:@"location%i", index]; 142 | NSDictionary *savedDict = [defaults objectForKey:theKey]; 143 | 144 | if (savedDict) { 145 | LMAnnotation *theAnnotation = [[LMAnnotation alloc] init]; 146 | CLLocationDegrees lat = [[savedDict valueForKey:@"LATITUDE"] doubleValue]; 147 | CLLocationDegrees Long = [[savedDict valueForKey:@"LONGITUDE"] doubleValue]; 148 | CLLocationCoordinate2D theCoordinate = CLLocationCoordinate2DMake(lat, Long); 149 | theAnnotation.coordinate = theCoordinate; 150 | theAnnotation.title = [NSString stringWithFormat:@"Location%i", index]; 151 | 152 | [mArray addObject:theAnnotation]; 153 | } 154 | } 155 | 156 | self.locationsArray = [NSArray arrayWithArray:mArray]; 157 | } 158 | 159 | -(void)clearStoredLocations 160 | { 161 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 162 | int numberOfPins = [defaults integerForKey:@"NUMBER_OF_PINS_SAVED"]; 163 | 164 | for (int counter = 0; counter < numberOfPins; counter++) { 165 | NSString *key = [NSString stringWithFormat:@"location%i",counter]; 166 | [defaults removeObjectForKey:key]; 167 | } 168 | } 169 | 170 | -(void)updateWalkButtonText 171 | { 172 | if (_currentWalkMode) { 173 | [self.walkModeToggleButton setTitle:@"Walk" forState:UIControlStateNormal]; 174 | } else { 175 | [self.walkModeToggleButton setTitle:@"Vehicle" forState:UIControlStateNormal]; 176 | } 177 | } 178 | 179 | #pragma mark - Actions 180 | 181 | -(IBAction)resetButtonTouched:(id)sender 182 | { 183 | [self clearStoredLocations]; 184 | NSArray *emptyArray = [NSArray array]; 185 | self.locationsArray = emptyArray; 186 | [self refreshMapView]; 187 | 188 | /* Setting lastKnown to nil and then asking for location is just a hacky 189 | * way of getting it to ignore the distance and recency filter and try for a new 190 | * accurate location right away. 191 | */ 192 | TTLocationHandler *handler = [TTLocationHandler sharedLocationHandler]; 193 | [handler setLastKnownLocation:nil]; 194 | CLLocation *ourLocation = handler.lastKnownLocation; 195 | NSLog(@"Reset all location info\n%@",ourLocation); 196 | /* 197 | */ 198 | } 199 | 200 | -(IBAction)backgroundSwitchTouche:(id)sender 201 | { 202 | UISwitch *theSwitch = (UISwitch *)sender; 203 | TTLocationHandler *sharedHandler = [TTLocationHandler sharedLocationHandler]; 204 | sharedHandler.continuesUpdatingOnBattery = theSwitch.on; 205 | sharedHandler = nil; 206 | } 207 | 208 | -(IBAction)intervalStepActivated:(id)sender 209 | { 210 | UIStepper *stepper = (UIStepper *)sender; 211 | int newInterval = stepper.value; 212 | TTLocationHandler *sharedHandler = [TTLocationHandler sharedLocationHandler]; 213 | sharedHandler.recencyThreshold = newInterval; 214 | sharedHandler = nil; 215 | self.refreshIntervalField.text = [NSString stringWithFormat:@"%i sec",newInterval]; 216 | 217 | if (newInterval >= 60) { 218 | stepper.stepValue = 120; 219 | } else if (newInterval >= 30) { 220 | stepper.stepValue = 15; 221 | } else { 222 | stepper.stepValue = 5; 223 | } 224 | 225 | } 226 | 227 | -(void)toggleWalkMode:(id)sender 228 | { 229 | TTLocationHandler *handler = [TTLocationHandler sharedLocationHandler]; 230 | _currentWalkMode = !_currentWalkMode; 231 | handler.walkMode = _currentWalkMode; 232 | 233 | [self updateWalkButtonText]; 234 | 235 | handler = nil; 236 | } 237 | 238 | 239 | #pragma mark - 240 | #pragma mark MKMapViewDelegate 241 | 242 | - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState { 243 | 244 | if (oldState == MKAnnotationViewDragStateDragging) { 245 | 246 | } 247 | } 248 | 249 | - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation { 250 | 251 | if ([annotation isKindOfClass:[MKUserLocation class]]) { 252 | return nil; 253 | } 254 | 255 | static NSString * const kPinAnnotationIdentifier = @"PinIdentifier"; 256 | MKAnnotationView *pinView = [self.mapView dequeueReusableAnnotationViewWithIdentifier:kPinAnnotationIdentifier]; 257 | 258 | 259 | if (pinView) { 260 | pinView.annotation = annotation; 261 | } else { 262 | pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:kPinAnnotationIdentifier]; 263 | [pinView setDraggable:NO]; 264 | pinView.canShowCallout = YES; 265 | } 266 | 267 | return pinView; 268 | } 269 | 270 | @end 271 | -------------------------------------------------------------------------------- /LocateMe/en.lproj/MainStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 33 | 34 | 35 | 36 | 37 | 38 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 59 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /LocateMe.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1B3B7DE71628FF6800AB3227 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B3B7DE61628FF6800AB3227 /* UIKit.framework */; }; 11 | 1B3B7DE91628FF6800AB3227 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B3B7DE81628FF6800AB3227 /* Foundation.framework */; }; 12 | 1B3B7DEB1628FF6800AB3227 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B3B7DEA1628FF6800AB3227 /* CoreGraphics.framework */; }; 13 | 1B3B7DF11628FF6800AB3227 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1B3B7DEF1628FF6800AB3227 /* InfoPlist.strings */; }; 14 | 1B3B7DF31628FF6800AB3227 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B3B7DF21628FF6800AB3227 /* main.m */; }; 15 | 1B3B7DF71628FF6800AB3227 /* LMAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B3B7DF61628FF6800AB3227 /* LMAppDelegate.m */; }; 16 | 1B3B7DF91628FF6800AB3227 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 1B3B7DF81628FF6800AB3227 /* Default.png */; }; 17 | 1B3B7DFB1628FF6800AB3227 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1B3B7DFA1628FF6800AB3227 /* Default@2x.png */; }; 18 | 1B3B7DFD1628FF6800AB3227 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1B3B7DFC1628FF6800AB3227 /* Default-568h@2x.png */; }; 19 | 1B3B7E001628FF6800AB3227 /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1B3B7DFE1628FF6800AB3227 /* MainStoryboard.storyboard */; }; 20 | 1B3B7E031628FF6800AB3227 /* LMViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B3B7E021628FF6800AB3227 /* LMViewController.m */; }; 21 | 1B3B7E0E162902AD00AB3227 /* TTLocationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B3B7E0D162902AD00AB3227 /* TTLocationHandler.m */; }; 22 | 1B3B7E10162903B500AB3227 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B3B7E0F162903B500AB3227 /* CoreLocation.framework */; }; 23 | 1B3B7E12162903CF00AB3227 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B3B7E11162903CF00AB3227 /* MapKit.framework */; }; 24 | 1B3B7E151629207200AB3227 /* LMAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B3B7E141629207200AB3227 /* LMAnnotation.m */; }; 25 | 1BA75A68162F775900A9E1F1 /* LMPinTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BA75A67162F775900A9E1F1 /* LMPinTracker.m */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 1B3B7DE21628FF6800AB3227 /* LocateMe.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LocateMe.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 1B3B7DE61628FF6800AB3227 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 31 | 1B3B7DE81628FF6800AB3227 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 32 | 1B3B7DEA1628FF6800AB3227 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 33 | 1B3B7DEE1628FF6800AB3227 /* LocateMe-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "LocateMe-Info.plist"; sourceTree = ""; }; 34 | 1B3B7DF01628FF6800AB3227 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 35 | 1B3B7DF21628FF6800AB3227 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 36 | 1B3B7DF41628FF6800AB3227 /* LocateMe-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LocateMe-Prefix.pch"; sourceTree = ""; }; 37 | 1B3B7DF51628FF6800AB3227 /* LMAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMAppDelegate.h; sourceTree = ""; }; 38 | 1B3B7DF61628FF6800AB3227 /* LMAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMAppDelegate.m; sourceTree = ""; }; 39 | 1B3B7DF81628FF6800AB3227 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 40 | 1B3B7DFA1628FF6800AB3227 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 41 | 1B3B7DFC1628FF6800AB3227 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 42 | 1B3B7DFF1628FF6800AB3227 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard.storyboard; sourceTree = ""; }; 43 | 1B3B7E011628FF6800AB3227 /* LMViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMViewController.h; sourceTree = ""; }; 44 | 1B3B7E021628FF6800AB3227 /* LMViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMViewController.m; sourceTree = ""; }; 45 | 1B3B7E0C162902AD00AB3227 /* TTLocationHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TTLocationHandler.h; path = LocationHandler/TTLocationHandler.h; sourceTree = SOURCE_ROOT; }; 46 | 1B3B7E0D162902AD00AB3227 /* TTLocationHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TTLocationHandler.m; path = LocationHandler/TTLocationHandler.m; sourceTree = SOURCE_ROOT; }; 47 | 1B3B7E0F162903B500AB3227 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 48 | 1B3B7E11162903CF00AB3227 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 49 | 1B3B7E131629207200AB3227 /* LMAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMAnnotation.h; sourceTree = ""; }; 50 | 1B3B7E141629207200AB3227 /* LMAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMAnnotation.m; sourceTree = ""; }; 51 | 1B3B7E16162A20B600AB3227 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = ""; }; 52 | 1BA75A66162F775900A9E1F1 /* LMPinTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LMPinTracker.h; path = PinTracker/LMPinTracker.h; sourceTree = SOURCE_ROOT; }; 53 | 1BA75A67162F775900A9E1F1 /* LMPinTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LMPinTracker.m; path = PinTracker/LMPinTracker.m; sourceTree = SOURCE_ROOT; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 1B3B7DDF1628FF6800AB3227 /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | 1B3B7E12162903CF00AB3227 /* MapKit.framework in Frameworks */, 62 | 1B3B7E10162903B500AB3227 /* CoreLocation.framework in Frameworks */, 63 | 1B3B7DE71628FF6800AB3227 /* UIKit.framework in Frameworks */, 64 | 1B3B7DE91628FF6800AB3227 /* Foundation.framework in Frameworks */, 65 | 1B3B7DEB1628FF6800AB3227 /* CoreGraphics.framework in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | 1B3B7DD71628FF6800AB3227 = { 73 | isa = PBXGroup; 74 | children = ( 75 | 1B3B7E16162A20B600AB3227 /* README.md */, 76 | 1B3B7DEC1628FF6800AB3227 /* LocateMe */, 77 | 1B3B7DE51628FF6800AB3227 /* Frameworks */, 78 | 1B3B7DE31628FF6800AB3227 /* Products */, 79 | ); 80 | sourceTree = ""; 81 | }; 82 | 1B3B7DE31628FF6800AB3227 /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 1B3B7DE21628FF6800AB3227 /* LocateMe.app */, 86 | ); 87 | name = Products; 88 | sourceTree = ""; 89 | }; 90 | 1B3B7DE51628FF6800AB3227 /* Frameworks */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 1B3B7E11162903CF00AB3227 /* MapKit.framework */, 94 | 1B3B7E0F162903B500AB3227 /* CoreLocation.framework */, 95 | 1B3B7DE61628FF6800AB3227 /* UIKit.framework */, 96 | 1B3B7DE81628FF6800AB3227 /* Foundation.framework */, 97 | 1B3B7DEA1628FF6800AB3227 /* CoreGraphics.framework */, 98 | ); 99 | name = Frameworks; 100 | sourceTree = ""; 101 | }; 102 | 1B3B7DEC1628FF6800AB3227 /* LocateMe */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 1BA75A64162F76BB00A9E1F1 /* Pin Tracker */, 106 | 1B3B7E091628FFD200AB3227 /* LocationHandler */, 107 | 1B3B7DF51628FF6800AB3227 /* LMAppDelegate.h */, 108 | 1B3B7DF61628FF6800AB3227 /* LMAppDelegate.m */, 109 | 1B3B7DFE1628FF6800AB3227 /* MainStoryboard.storyboard */, 110 | 1B3B7E011628FF6800AB3227 /* LMViewController.h */, 111 | 1B3B7E021628FF6800AB3227 /* LMViewController.m */, 112 | 1B3B7E131629207200AB3227 /* LMAnnotation.h */, 113 | 1B3B7E141629207200AB3227 /* LMAnnotation.m */, 114 | 1B3B7DED1628FF6800AB3227 /* Supporting Files */, 115 | ); 116 | path = LocateMe; 117 | sourceTree = ""; 118 | }; 119 | 1B3B7DED1628FF6800AB3227 /* Supporting Files */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 1B3B7DEE1628FF6800AB3227 /* LocateMe-Info.plist */, 123 | 1B3B7DEF1628FF6800AB3227 /* InfoPlist.strings */, 124 | 1B3B7DF21628FF6800AB3227 /* main.m */, 125 | 1B3B7DF41628FF6800AB3227 /* LocateMe-Prefix.pch */, 126 | 1B3B7DF81628FF6800AB3227 /* Default.png */, 127 | 1B3B7DFA1628FF6800AB3227 /* Default@2x.png */, 128 | 1B3B7DFC1628FF6800AB3227 /* Default-568h@2x.png */, 129 | ); 130 | name = "Supporting Files"; 131 | sourceTree = ""; 132 | }; 133 | 1B3B7E091628FFD200AB3227 /* LocationHandler */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 1B3B7E0C162902AD00AB3227 /* TTLocationHandler.h */, 137 | 1B3B7E0D162902AD00AB3227 /* TTLocationHandler.m */, 138 | ); 139 | name = LocationHandler; 140 | sourceTree = ""; 141 | }; 142 | 1BA75A64162F76BB00A9E1F1 /* Pin Tracker */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 1BA75A66162F775900A9E1F1 /* LMPinTracker.h */, 146 | 1BA75A67162F775900A9E1F1 /* LMPinTracker.m */, 147 | ); 148 | name = "Pin Tracker"; 149 | sourceTree = ""; 150 | }; 151 | /* End PBXGroup section */ 152 | 153 | /* Begin PBXNativeTarget section */ 154 | 1B3B7DE11628FF6800AB3227 /* LocateMe */ = { 155 | isa = PBXNativeTarget; 156 | buildConfigurationList = 1B3B7E061628FF6800AB3227 /* Build configuration list for PBXNativeTarget "LocateMe" */; 157 | buildPhases = ( 158 | 1B3B7DDE1628FF6800AB3227 /* Sources */, 159 | 1B3B7DDF1628FF6800AB3227 /* Frameworks */, 160 | 1B3B7DE01628FF6800AB3227 /* Resources */, 161 | ); 162 | buildRules = ( 163 | ); 164 | dependencies = ( 165 | ); 166 | name = LocateMe; 167 | productName = LocateMe; 168 | productReference = 1B3B7DE21628FF6800AB3227 /* LocateMe.app */; 169 | productType = "com.apple.product-type.application"; 170 | }; 171 | /* End PBXNativeTarget section */ 172 | 173 | /* Begin PBXProject section */ 174 | 1B3B7DD91628FF6800AB3227 /* Project object */ = { 175 | isa = PBXProject; 176 | attributes = { 177 | CLASSPREFIX = LM; 178 | LastUpgradeCheck = 0450; 179 | ORGANIZATIONNAME = "Dean Davids"; 180 | }; 181 | buildConfigurationList = 1B3B7DDC1628FF6800AB3227 /* Build configuration list for PBXProject "LocateMe" */; 182 | compatibilityVersion = "Xcode 3.2"; 183 | developmentRegion = English; 184 | hasScannedForEncodings = 0; 185 | knownRegions = ( 186 | en, 187 | ); 188 | mainGroup = 1B3B7DD71628FF6800AB3227; 189 | productRefGroup = 1B3B7DE31628FF6800AB3227 /* Products */; 190 | projectDirPath = ""; 191 | projectRoot = ""; 192 | targets = ( 193 | 1B3B7DE11628FF6800AB3227 /* LocateMe */, 194 | ); 195 | }; 196 | /* End PBXProject section */ 197 | 198 | /* Begin PBXResourcesBuildPhase section */ 199 | 1B3B7DE01628FF6800AB3227 /* Resources */ = { 200 | isa = PBXResourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | 1B3B7DF11628FF6800AB3227 /* InfoPlist.strings in Resources */, 204 | 1B3B7DF91628FF6800AB3227 /* Default.png in Resources */, 205 | 1B3B7DFB1628FF6800AB3227 /* Default@2x.png in Resources */, 206 | 1B3B7DFD1628FF6800AB3227 /* Default-568h@2x.png in Resources */, 207 | 1B3B7E001628FF6800AB3227 /* MainStoryboard.storyboard in Resources */, 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | /* End PBXResourcesBuildPhase section */ 212 | 213 | /* Begin PBXSourcesBuildPhase section */ 214 | 1B3B7DDE1628FF6800AB3227 /* Sources */ = { 215 | isa = PBXSourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | 1B3B7DF31628FF6800AB3227 /* main.m in Sources */, 219 | 1B3B7DF71628FF6800AB3227 /* LMAppDelegate.m in Sources */, 220 | 1B3B7E031628FF6800AB3227 /* LMViewController.m in Sources */, 221 | 1B3B7E0E162902AD00AB3227 /* TTLocationHandler.m in Sources */, 222 | 1B3B7E151629207200AB3227 /* LMAnnotation.m in Sources */, 223 | 1BA75A68162F775900A9E1F1 /* LMPinTracker.m in Sources */, 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | /* End PBXSourcesBuildPhase section */ 228 | 229 | /* Begin PBXVariantGroup section */ 230 | 1B3B7DEF1628FF6800AB3227 /* InfoPlist.strings */ = { 231 | isa = PBXVariantGroup; 232 | children = ( 233 | 1B3B7DF01628FF6800AB3227 /* en */, 234 | ); 235 | name = InfoPlist.strings; 236 | sourceTree = ""; 237 | }; 238 | 1B3B7DFE1628FF6800AB3227 /* MainStoryboard.storyboard */ = { 239 | isa = PBXVariantGroup; 240 | children = ( 241 | 1B3B7DFF1628FF6800AB3227 /* en */, 242 | ); 243 | name = MainStoryboard.storyboard; 244 | sourceTree = ""; 245 | }; 246 | /* End PBXVariantGroup section */ 247 | 248 | /* Begin XCBuildConfiguration section */ 249 | 1B3B7E041628FF6800AB3227 /* Debug */ = { 250 | isa = XCBuildConfiguration; 251 | buildSettings = { 252 | ALWAYS_SEARCH_USER_PATHS = NO; 253 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 254 | CLANG_CXX_LIBRARY = "libc++"; 255 | CLANG_ENABLE_OBJC_ARC = YES; 256 | CLANG_WARN_EMPTY_BODY = YES; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 259 | COPY_PHASE_STRIP = NO; 260 | GCC_C_LANGUAGE_STANDARD = gnu99; 261 | GCC_DYNAMIC_NO_PIC = NO; 262 | GCC_OPTIMIZATION_LEVEL = 0; 263 | GCC_PREPROCESSOR_DEFINITIONS = ( 264 | "DEBUG=1", 265 | "$(inherited)", 266 | ); 267 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 268 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 269 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 270 | GCC_WARN_UNUSED_VARIABLE = YES; 271 | IPHONEOS_DEPLOYMENT_TARGET = 5.1; 272 | ONLY_ACTIVE_ARCH = YES; 273 | SDKROOT = iphoneos; 274 | }; 275 | name = Debug; 276 | }; 277 | 1B3B7E051628FF6800AB3227 /* Release */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ALWAYS_SEARCH_USER_PATHS = NO; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_OBJC_ARC = YES; 284 | CLANG_WARN_EMPTY_BODY = YES; 285 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 286 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 287 | COPY_PHASE_STRIP = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu99; 289 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 290 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 291 | GCC_WARN_UNUSED_VARIABLE = YES; 292 | IPHONEOS_DEPLOYMENT_TARGET = 5.1; 293 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 294 | SDKROOT = iphoneos; 295 | VALIDATE_PRODUCT = YES; 296 | }; 297 | name = Release; 298 | }; 299 | 1B3B7E071628FF6800AB3227 /* Debug */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 303 | GCC_PREFIX_HEADER = "LocateMe/LocateMe-Prefix.pch"; 304 | INFOPLIST_FILE = "LocateMe/LocateMe-Info.plist"; 305 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 306 | PRODUCT_NAME = "$(TARGET_NAME)"; 307 | WRAPPER_EXTENSION = app; 308 | }; 309 | name = Debug; 310 | }; 311 | 1B3B7E081628FF6800AB3227 /* Release */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 315 | GCC_PREFIX_HEADER = "LocateMe/LocateMe-Prefix.pch"; 316 | INFOPLIST_FILE = "LocateMe/LocateMe-Info.plist"; 317 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 318 | PRODUCT_NAME = "$(TARGET_NAME)"; 319 | WRAPPER_EXTENSION = app; 320 | }; 321 | name = Release; 322 | }; 323 | /* End XCBuildConfiguration section */ 324 | 325 | /* Begin XCConfigurationList section */ 326 | 1B3B7DDC1628FF6800AB3227 /* Build configuration list for PBXProject "LocateMe" */ = { 327 | isa = XCConfigurationList; 328 | buildConfigurations = ( 329 | 1B3B7E041628FF6800AB3227 /* Debug */, 330 | 1B3B7E051628FF6800AB3227 /* Release */, 331 | ); 332 | defaultConfigurationIsVisible = 0; 333 | defaultConfigurationName = Release; 334 | }; 335 | 1B3B7E061628FF6800AB3227 /* Build configuration list for PBXNativeTarget "LocateMe" */ = { 336 | isa = XCConfigurationList; 337 | buildConfigurations = ( 338 | 1B3B7E071628FF6800AB3227 /* Debug */, 339 | 1B3B7E081628FF6800AB3227 /* Release */, 340 | ); 341 | defaultConfigurationIsVisible = 0; 342 | defaultConfigurationName = Release; 343 | }; 344 | /* End XCConfigurationList section */ 345 | }; 346 | rootObject = 1B3B7DD91628FF6800AB3227 /* Project object */; 347 | } 348 | -------------------------------------------------------------------------------- /LocationHandler/TTLocationHandler.m: -------------------------------------------------------------------------------- 1 | // TTLocationHandler.m 2 | // 3 | // Created by Dean Davids on 3/29/12. 4 | // Copyright (c) 2012 Dean S. Davids, Tailgate Technology. All rights reserved. 5 | // http://www.tailgatetechnology.com 6 | // 7 | // Portions of this software Copyright (c) 2010, Long Weekend LLC 8 | // Long Weekend LLC credit for basis of the origination of this code. 9 | // Original code and tutorial for same can be found at http://longweekendmobile.com/2010/06/30/location-region-data-in-background-on-ios4-iphone/ 10 | // 11 | // All of the code in this file shares same permissions as follows: 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 13 | // associated documentation files (the "Software"), to deal in the Software without restriction, 14 | // including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 15 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 16 | // subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all copies or substantial 19 | // portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 22 | // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | #import "TTLocationHandler.h" 28 | 29 | 30 | // Private methods 31 | @interface TTLocationHandler () 32 | 33 | @property(nonatomic) BOOL highwayMode; 34 | 35 | -(void) _stopUpdatingLocation; 36 | -(void) _startUpdatingLocation; 37 | -(void) _saveLocationAndNotifyObservers:(CLLocation *)locationToSave; 38 | -(void) _acceptBestAvailableLocation:(id)sender; 39 | -(void) _syncBackgroundUpdatesFlagWithBatteryState; 40 | -(void) _batteryStateDidChange:(NSNotification *)notification; 41 | -(void) _saveLastKnownLocation:(CLLocation *)inLocation; 42 | -(void) _updateStatusBarStyleActive:(NSNumber *)active; 43 | - (BOOL) isLocation:(CLLocation *)firstLocation confirmedMinimumDistance:(double)minDistance fromPreviousLocation:(CLLocation *)secondLocation; 44 | @end 45 | 46 | /** 47 | * \brief Abstracts CLLocationManager functionality, acts as delegate 48 | * \details CLLocationManager sends delegate messages every time the location 49 | * is updated, whether or not those events are useful (e.g. recent? accurate?) 50 | * This class abstracts those things away, issuing a single LocationHandlerDidUpdateLocation 51 | * notification every time a significant location change occurs. 52 | */ 53 | 54 | @implementation TTLocationHandler 55 | { 56 | NSMutableArray *_pendingLocationsQueue; 57 | NSTimer *_pendingLocationsTimer; 58 | NSTimeInterval _pendingLocationsTimerDuration; 59 | BOOL _updateInBackground; 60 | } 61 | 62 | @synthesize highwayMode = _highwayMode; 63 | @synthesize locationManager = _locationManager; 64 | @synthesize lastKnownLocation = _lastKnownLocation; 65 | @synthesize continuesUpdatingWhileActive = _continuesUpdatingWhileActive; 66 | @synthesize continuesUpdatingOnBattery = _continuesUpdatingOnBattery; 67 | @synthesize updatesInBackgroundWhenCharging = _updatesInBackgroundWhenCharging; 68 | @synthesize ignorePossibleDuplicates = _ignorePossibleDuplicates; 69 | @synthesize requiredAccuracy = _requiredAccuracy; 70 | @synthesize recencyThreshold = _recencyThreshold; 71 | @synthesize walkMode = _walkMode; 72 | 73 | #define OUTPUT_LOGS 1 74 | 75 | static const int MAX_TRIES_FOR_ACCURACY = 10; 76 | static const double DEFAULT_DISTANCE_FILTER = 50.00; 77 | static const double WALK_DISTANCE_FILTER = 10.00; 78 | 79 | + (id)sharedLocationHandler { 80 | static dispatch_once_t pred; 81 | static TTLocationHandler *locationHandlerSingleton = nil; 82 | 83 | dispatch_once(&pred, ^{ 84 | locationHandlerSingleton = [[self alloc] init]; 85 | }); 86 | return locationHandlerSingleton; 87 | } 88 | 89 | //! Custom initializer, creates location manager instance 90 | - (id) init 91 | { 92 | if (self = [super init]) 93 | { 94 | // Makes all location readings expire after 60 seconds by default 95 | _recencyThreshold = 60; 96 | 97 | // Clear current value to be sure we start fresh 98 | _lastKnownLocation = nil; 99 | 100 | // Default behaviour is to continually update position whenever app is in foreground and whenever the device is plugged in. 101 | _continuesUpdatingWhileActive = YES; 102 | _continuesUpdatingOnBattery = NO; 103 | _updatesInBackgroundWhenCharging = YES; 104 | _ignorePossibleDuplicates = YES; 105 | _walkMode = NO; 106 | 107 | // Initial highway mode setting is NO 108 | // By default it will change to yes whenever speed is over 22mps (about 45mph). 109 | _highwayMode = NO; 110 | 111 | // Start up the location manager 112 | self.locationManager = [[CLLocationManager alloc] init]; 113 | self.locationManager.delegate = self; 114 | self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters; 115 | self.locationManager.distanceFilter = DEFAULT_DISTANCE_FILTER; 116 | self.requiredAccuracy = 10.0f; 117 | // New property for iOS6 118 | if ([self.locationManager respondsToSelector:@selector(activityType)]) { 119 | self.locationManager.activityType = CLActivityTypeAutomotiveNavigation; 120 | } 121 | 122 | _pendingLocationsQueue = [[NSMutableArray alloc] init]; 123 | _pendingLocationsTimerDuration = 10; 124 | 125 | // Register an observer for if/when this app goes into background & comes back to foreground 126 | // NOTE: THIS CODE IS iOS4.0+ ONLY. 127 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_stopUpdatingLocation) name:UIApplicationDidEnterBackgroundNotification object:nil]; 128 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_startUpdatingLocation) name:UIApplicationDidFinishLaunchingNotification object:nil]; 129 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_startUpdatingLocation) name:UIApplicationWillEnterForegroundNotification object:nil]; 130 | 131 | /* 132 | ** Register for battery state change monitoring to enable active location monitoring in background if the device is plugged in to power 133 | ** We revert to significant location changes only if app is in background and device is not plugged into external power. 134 | */ 135 | [UIDevice currentDevice].batteryMonitoringEnabled = YES; 136 | _updateInBackground = YES; 137 | 138 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_batteryStateDidChange:) name:UIDeviceBatteryStateDidChangeNotification object:nil]; 139 | } 140 | return self; 141 | } 142 | 143 | #pragma mark - 144 | #pragma mark Accessors 145 | 146 | -(void)setHighwayMode:(BOOL)highwayMode { 147 | /* 148 | ** If we are in the background, plugged into charger and travelling at highway speed, the location manager is pumping 149 | ** out new updates several times a second unnecessarily. We'll cut down the activity by changing the distance filter in 150 | ** highway mode. Highway mode is set yes or no every time we get an update so we want to check if it is being changed 151 | ** to a new value before actually altering the location manager property. 152 | */ 153 | if (highwayMode != _highwayMode) { 154 | CGFloat highwayDistanceFilter = 400.00f; 155 | CGFloat cityDistanceFilter = DEFAULT_DISTANCE_FILTER; 156 | if (self.walkMode) { 157 | cityDistanceFilter = WALK_DISTANCE_FILTER; 158 | } 159 | if (highwayMode && self.recencyThreshold > 15.0) { 160 | if (OUTPUT_LOGS) NSLog(@"Setting Highway Mode"); 161 | self.locationManager.distanceFilter = highwayDistanceFilter; 162 | } else { 163 | if (OUTPUT_LOGS) NSLog(@"Turning off Highway Mode"); 164 | self.locationManager.distanceFilter = cityDistanceFilter; 165 | } 166 | _highwayMode = highwayMode; 167 | } 168 | } 169 | 170 | -(void)setWalkMode:(BOOL)walkMode { 171 | if (walkMode == _walkMode) { 172 | return; 173 | } 174 | 175 | if (walkMode) { 176 | self.locationManager.distanceFilter = WALK_DISTANCE_FILTER; 177 | // New property for iOS6 178 | if ([self.locationManager respondsToSelector:@selector(activityType)]) { 179 | self.locationManager.activityType = CLActivityTypeFitness; 180 | } 181 | } else { 182 | self.locationManager.distanceFilter = DEFAULT_DISTANCE_FILTER; 183 | // New property for iOS6 184 | if ([self.locationManager respondsToSelector:@selector(activityType)]) { 185 | self.locationManager.activityType = CLActivityTypeAutomotiveNavigation; 186 | } 187 | } 188 | 189 | _walkMode = walkMode; 190 | } 191 | 192 | /* 193 | ** Gives current location if it has been set 194 | ** instead of returning nil, it will check user defaults for the last known location. 195 | ** If no location has ever been set, returns an arbitrary default 196 | ** The logic here is questionable but if I let it return nil it causes problems and I didn't want to be checking for nil everywhere that I use the property. 197 | ** In reality, it is very unlikely to ever return the arbitrary default location as the manager starts updating immediately on init and, unless this is the very 198 | ** first startup on the device, we have stored last known location info in user defaults. 199 | */ 200 | -(CLLocation *)lastKnownLocation { 201 | if (_lastKnownLocation) { 202 | return _lastKnownLocation; 203 | } 204 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 205 | NSDictionary *locationInfo = [defaults objectForKey:@"LAST_KNOWN_LOCATION"]; 206 | if (locationInfo) { 207 | // Create a new location from the last known info saved in user defaults 208 | CLLocationDegrees lastKnownLat = [[locationInfo valueForKey:@"LATITUDE"] doubleValue]; 209 | CLLocationDegrees lastKnownLong = [[locationInfo valueForKey:@"LONGITUDE"] doubleValue]; 210 | CLLocationCoordinate2D lastKnownCoordinate = CLLocationCoordinate2DMake(lastKnownLat, lastKnownLong); 211 | NSDate *lastKnownTimestamp = [locationInfo valueForKey:@"TIME_STAMP"]; 212 | 213 | CLLocation *mostRecentLocation = [[CLLocation alloc] initWithCoordinate:lastKnownCoordinate altitude:0 214 | horizontalAccuracy:self.requiredAccuracy * 100 215 | verticalAccuracy:-1 216 | timestamp:[lastKnownTimestamp dateByAddingTimeInterval:-self.recencyThreshold]]; 217 | if (OUTPUT_LOGS) NSLog(@"Last Known Location Retrieved = %@",mostRecentLocation); 218 | 219 | [self _startUpdatingLocation]; 220 | return mostRecentLocation; 221 | } else { 222 | CLLocationDegrees defaultLat = [[defaults objectForKey:@"DEFAULT_LOCATION_LATITUDE"] doubleValue]; 223 | CLLocationDegrees defaultLong = [[defaults objectForKey:@"DEFAULT_LOCATION_LONGITUDE"] doubleValue]; 224 | CLLocationCoordinate2D defaultCoordinate = CLLocationCoordinate2DMake(defaultLat, defaultLong); 225 | CLLocation *defaultLocation = [[CLLocation alloc] initWithCoordinate:defaultCoordinate 226 | altitude:0 227 | horizontalAccuracy:self.requiredAccuracy * 1000 228 | verticalAccuracy:-1 229 | timestamp:[NSDate dateWithTimeIntervalSinceNow:-self.recencyThreshold * 10]]; 230 | 231 | if (OUTPUT_LOGS) NSLog(@"Default Location retrieved = %@",defaultLocation); 232 | 233 | [self _startUpdatingLocation]; 234 | return defaultLocation; 235 | } 236 | 237 | return _lastKnownLocation; 238 | } 239 | 240 | -(void)setLastKnownLocation:(CLLocation *)currentLocation { 241 | NSLog(@"setting location to %@",currentLocation); 242 | 243 | _lastKnownLocation = [currentLocation copy]; 244 | 245 | if (CLLocationCoordinate2DIsValid(_lastKnownLocation.coordinate)&& _lastKnownLocation.coordinate.latitude != 0 && _lastKnownLocation.coordinate.longitude != 0) { 246 | [self _saveLastKnownLocation:_lastKnownLocation]; 247 | } 248 | } 249 | 250 | -(void)setUpdatesInBackgroundWhenCharging:(BOOL)updatesInBackgroundWhenCharging { 251 | // If it already is set the same, do nothing 252 | if (_updatesInBackgroundWhenCharging == updatesInBackgroundWhenCharging) { 253 | return; 254 | } 255 | 256 | // Setting is new, enable/disable battery monitoring and set ivars 257 | UIDevice *currentDevice = [UIDevice currentDevice]; 258 | currentDevice.batteryMonitoringEnabled = updatesInBackgroundWhenCharging; 259 | _updatesInBackgroundWhenCharging = updatesInBackgroundWhenCharging; 260 | 261 | if (updatesInBackgroundWhenCharging) { 262 | dispatch_async(dispatch_get_main_queue(), ^{ 263 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_batteryStateDidChange:) name:UIDeviceBatteryStateDidChangeNotification object:nil]; 264 | }); 265 | } else { 266 | dispatch_async(dispatch_get_main_queue(), ^{ 267 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceBatteryStateDidChangeNotification object:Nil]; 268 | }); 269 | } 270 | 271 | [self _syncBackgroundUpdatesFlagWithBatteryState]; 272 | if (_updateInBackground) { 273 | [self _startUpdatingLocation]; 274 | } 275 | 276 | if (OUTPUT_LOGS) NSLog(@"Updates In Background property set to %@",(_updatesInBackgroundWhenCharging ? @"YES" : @"NO")); 277 | if (OUTPUT_LOGS) NSLog(@"updateInBackground Flag set to %@",(_updateInBackground ? @"YES" : @"NO")); 278 | } 279 | 280 | #pragma mark - 281 | #pragma mark Public Methods 282 | 283 | - (BOOL)registerNotificationForLocation:(CLLocation *)myLocation withRadius:(NSNumber *)myRadius assignIdentifier:(NSString *)identifier { 284 | // Do not create regions if support is unavailable or disabled. 285 | if ( ![CLLocationManager regionMonitoringAvailable]) { 286 | return NO; 287 | } 288 | 289 | // If the radius is too large, registration fails automatically, 290 | // so clamp the radius to the max value. 291 | CLLocationDistance theRadius = [myRadius doubleValue]; 292 | if (theRadius > self.locationManager.maximumRegionMonitoringDistance) { 293 | theRadius = self.locationManager.maximumRegionMonitoringDistance; 294 | } 295 | 296 | CLLocationCoordinate2D theCoordinate = myLocation.coordinate; 297 | 298 | // Create the region and start monitoring it. 299 | CLRegion* theRegion = [[CLRegion alloc] initCircularRegionWithCenter:theCoordinate 300 | radius:theRadius identifier:identifier]; 301 | [self.locationManager startMonitoringForRegion:theRegion]; 302 | if (OUTPUT_LOGS) NSLog(@"Registered Region"); 303 | return YES; 304 | } 305 | 306 | /** 307 | * For clearing all monitored regions prior to refreshing your list monitored regions based on 308 | * a significant change in location. 309 | */ 310 | - (void)removeAllMonitoredRegions { 311 | NSSet *regions = self.locationManager.monitoredRegions; 312 | for (CLRegion *theRegion in regions) { 313 | [self.locationManager stopMonitoringForRegion:theRegion]; 314 | } 315 | if (OUTPUT_LOGS) NSLog(@"Removed all Monitored Regions"); 316 | } 317 | 318 | /** 319 | * Get a region of defined radius from current location only 320 | */ 321 | -(CLRegion *)currentRegionWithRadius:(CLLocationDistance)radius { 322 | CLLocation *workingLocation = [self.lastKnownLocation copy]; 323 | CLRegion *theRegion = [[CLRegion alloc] initCircularRegionWithCenter:workingLocation.coordinate 324 | radius:radius 325 | identifier:@"currentRegion"]; 326 | return theRegion; 327 | } 328 | 329 | #pragma mark - 330 | #pragma mark Private Methods 331 | 332 | 333 | /** 334 | ** Main delegate method for CLLocationManager. 335 | ** Called when location is updated - makes decisions about whether or not to update class instance variable currentLocation 336 | */ 337 | -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations 338 | { 339 | CLLocation *locationToTest = [locations lastObject]; 340 | 341 | UIApplication *app = [UIApplication sharedApplication]; 342 | __block UIBackgroundTaskIdentifier UpdatingLocationTaskID = [app beginBackgroundTaskWithExpirationHandler:^{ 343 | dispatch_async(dispatch_get_main_queue(), ^{ 344 | if (UpdatingLocationTaskID != UIBackgroundTaskInvalid) { 345 | if (OUTPUT_LOGS) NSLog(@"Location Update failed to finish prior to expiration"); 346 | // *** CONSIDER MORE APPROPRIATE RESPONSE TO EXPIRATION *** // 347 | [app endBackgroundTask:UpdatingLocationTaskID]; 348 | UpdatingLocationTaskID = UIBackgroundTaskInvalid; 349 | } 350 | }); 351 | }]; 352 | 353 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 354 | BOOL existingLocationIsAccurate = [self isLocationWithinRequiredAccuracy:self.lastKnownLocation]; 355 | BOOL existingLocationIsRecent = [self isReadingRecentForLocation:self.lastKnownLocation]; 356 | 357 | // set highway mode if we are moving faster than about 45mph (20mps) 358 | self.highwayMode = locationToTest.speed >= 20.00; 359 | 360 | [self _syncBackgroundUpdatesFlagWithBatteryState]; 361 | 362 | // 1st, if we already have an accurate location that is recent, ignore the new location 363 | if (existingLocationIsAccurate && existingLocationIsRecent) 364 | { 365 | if (OUTPUT_LOGS) NSLog(@"We have a good location already saved"); 366 | // Discontinue location if not set to continue 367 | [self _stopUpdatingLocation]; 368 | 369 | } else { 370 | NSNumber *activeYes = [NSNumber numberWithBool:YES]; 371 | [self performSelectorOnMainThread:@selector(_updateStatusBarStyleActive:) withObject:activeYes waitUntilDone:NO]; 372 | 373 | /* The existing location is either old or inaccurate, if our new location is an accurate location, we'll use it 374 | ** otherwise we are going to save it in a pending queue and wait to see if a better one comes in. 375 | ** We are also setting a cap, if we already have the max number of locations pending we will go ahead and take 376 | ** this one regardless of accuracy. 377 | */ 378 | if ([self isLocationWithinRequiredAccuracy:locationToTest] ) { 379 | // New location is good, clear the pending queue and save this one. 380 | [_pendingLocationsTimer invalidate]; 381 | _pendingLocationsTimer = nil; 382 | [_pendingLocationsQueue removeAllObjects]; 383 | [self _saveLocationAndNotifyObservers:[locationToTest copy]]; 384 | 385 | if (OUTPUT_LOGS) NSLog(@"New, accurate location was set"); 386 | 387 | }else if (_pendingLocationsQueue.count > MAX_TRIES_FOR_ACCURACY) { 388 | // This is the last location we are going to wait for. It's still not as accurate as we'd like but we'll take it anyway. 389 | [_pendingLocationsTimer invalidate]; 390 | _pendingLocationsTimer = nil; 391 | [self _saveLocationAndNotifyObservers:[_pendingLocationsQueue lastObject]]; 392 | 393 | if (OUTPUT_LOGS) NSLog(@"Location attempts limit reached, accepted location %i",_pendingLocationsQueue.count); 394 | 395 | [_pendingLocationsQueue removeAllObjects]; 396 | } else { 397 | // It's not within our requested accuracy preference and we haven't reached limit of tries 398 | // save to the queue and see if we get a better one before our set wait time expiration. 399 | [_pendingLocationsQueue addObject:[locationToTest copy]]; 400 | if (OUTPUT_LOGS) NSLog(@"Location %i queued for possible acceptance",_pendingLocationsQueue.count); 401 | 402 | // set up a timer to limit how long we'll wait before taking what we have. 403 | [_pendingLocationsTimer invalidate]; 404 | _pendingLocationsTimer = nil; 405 | _pendingLocationsTimer = [NSTimer timerWithTimeInterval:_pendingLocationsTimerDuration 406 | target:self 407 | selector:@selector(_acceptBestAvailableLocation:) 408 | userInfo:nil repeats:NO]; 409 | 410 | // In background on global thread, runloop may be idle 411 | NSRunLoop *loop = [NSRunLoop currentRunLoop]; 412 | [loop addTimer:_pendingLocationsTimer forMode:NSRunLoopCommonModes]; 413 | NSDate *limitDate = [NSDate dateWithTimeIntervalSinceNow:_pendingLocationsTimerDuration + .1]; 414 | [loop runUntilDate:limitDate]; 415 | } 416 | } 417 | 418 | dispatch_async(dispatch_get_main_queue(), ^{ 419 | if (UpdatingLocationTaskID != UIBackgroundTaskInvalid) { 420 | if (OUTPUT_LOGS) NSLog(@"Ending UpdateLocation task normally"); 421 | [app endBackgroundTask:UpdatingLocationTaskID]; 422 | UpdatingLocationTaskID = UIBackgroundTaskInvalid; 423 | } 424 | }); 425 | }); 426 | } 427 | 428 | // called when timer times out, no better location was received after max wait time. 429 | -(void) _acceptBestAvailableLocation:(id)sender { 430 | UIApplication *app = [UIApplication sharedApplication]; 431 | __block UIBackgroundTaskIdentifier acceptTaskID = [app beginBackgroundTaskWithExpirationHandler:^{ 432 | dispatch_async(dispatch_get_main_queue(), ^{ 433 | if (acceptTaskID != UIBackgroundTaskInvalid) { 434 | // *** CONSIDER MORE APPROPRIATE RESPONSE TO EXPIRATION *** // 435 | [app endBackgroundTask:acceptTaskID]; 436 | acceptTaskID = UIBackgroundTaskInvalid; 437 | } 438 | }); 439 | }]; 440 | 441 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 442 | //Enter Background Operations here 443 | _pendingLocationsTimer = nil; 444 | 445 | [self _saveLocationAndNotifyObservers:[_pendingLocationsQueue lastObject]]; 446 | 447 | if (OUTPUT_LOGS) NSLog(@"Time limit reached, no better location came in. Accepted location %i",_pendingLocationsQueue.count); 448 | 449 | [_pendingLocationsQueue removeAllObjects]; 450 | 451 | // Close out task Idenetifier on main queue 452 | dispatch_async(dispatch_get_main_queue(), ^{ 453 | if (acceptTaskID != UIBackgroundTaskInvalid) { 454 | [app endBackgroundTask:acceptTaskID]; 455 | acceptTaskID = UIBackgroundTaskInvalid; 456 | } 457 | }); 458 | }); 459 | } 460 | 461 | // updates the property currentLocation and sends out notification. Calls stop CL from updating if appropriate 462 | -(void) _saveLocationAndNotifyObservers:(CLLocation *)locationToSave { 463 | 464 | CGFloat currentDistanceFilter = DEFAULT_DISTANCE_FILTER; 465 | if (self.walkMode) { 466 | currentDistanceFilter = WALK_DISTANCE_FILTER; 467 | } 468 | if (self.ignorePossibleDuplicates && _lastKnownLocation && ![self isLocation:locationToSave confirmedMinimumDistance:currentDistanceFilter fromPreviousLocation:_lastKnownLocation]) { 469 | // Set to ignore possible duplicates, we have a last known location saved and the new location is not far enough from last known to confirm 470 | if (OUTPUT_LOGS) NSLog(@"possibly duplicate, not far enough from previous and so this event has been ignored"); 471 | [self _stopUpdatingLocation]; 472 | return; 473 | } 474 | 475 | self.lastKnownLocation = locationToSave; 476 | 477 | NSNumber *activeNO = [NSNumber numberWithBool:NO]; 478 | [self performSelectorOnMainThread:@selector(_updateStatusBarStyleActive:) withObject:activeNO waitUntilDone:NO]; 479 | 480 | // Discontinue updating location if not set to continue 481 | [self _stopUpdatingLocation]; 482 | 483 | // Send notification to everyone that cares 484 | dispatch_async(dispatch_get_main_queue(), ^{ 485 | if (OUTPUT_LOGS) NSLog(@"Sending notification out"); 486 | NSNotification *aNotification = [NSNotification notificationWithName:LocationHandlerDidUpdateLocation object:[locationToSave copy]]; 487 | [[NSNotificationCenter defaultCenter] postNotification:aNotification]; 488 | }); 489 | } 490 | 491 | /** 492 | * Stops updating the location in realtime. 493 | * Starts the significantLocationChange service instead. 494 | * Called when the application is about to be put 495 | * in the background so the user's battery isn't 496 | * killed. 497 | * If set to update in background when plugged into 498 | * external power source, we skip this method completely 499 | * letting it continue updating accurately in all circumstances. 500 | */ 501 | - (void) _stopUpdatingLocation 502 | { 503 | UIApplication *app = [UIApplication sharedApplication]; 504 | BOOL inForegroundAndShouldContinue = app.applicationState == UIApplicationStateActive && self.continuesUpdatingWhileActive; 505 | 506 | if (OUTPUT_LOGS) NSLog(@"inForegroundAndShouldContinue = %@, updateInBackground = %@",inForegroundAndShouldContinue ? @"YES" : @"NO", _updateInBackground ? @"YES" : @"NO"); 507 | if (!_updateInBackground) { 508 | if (!inForegroundAndShouldContinue) { 509 | if (OUTPUT_LOGS) NSLog(@"Stopping location updates"); 510 | [[self locationManager] stopUpdatingLocation]; 511 | 512 | if ([CLLocationManager significantLocationChangeMonitoringAvailable]) 513 | { 514 | [self.locationManager startMonitoringSignificantLocationChanges]; 515 | if (OUTPUT_LOGS) NSLog(@"Started monitoring for significant location changes"); 516 | } 517 | } 518 | } 519 | } 520 | 521 | /** 522 | * Starts updating the location in realtime, 523 | * Stops the background monitoring. 524 | * Called when the application is launched to the foreground 525 | */ 526 | - (void) _startUpdatingLocation 527 | { 528 | // Setting curentLocation to nil ensures we will try to update with accurate location on next cycle 529 | //_lastKnownLocation = nil; 530 | if ([CLLocationManager significantLocationChangeMonitoringAvailable]) 531 | { 532 | if (OUTPUT_LOGS) NSLog(@"Stopped monitoring for significant changes"); 533 | [[self locationManager] stopMonitoringSignificantLocationChanges]; 534 | } 535 | 536 | [[self locationManager] startUpdatingLocation]; 537 | if (OUTPUT_LOGS) NSLog(@"Started updating location"); 538 | } 539 | 540 | /* Saves the location to the user defaults for use if the current location 541 | * would potentially return nil, we'll return the last known location instead 542 | */ 543 | -(void)_saveLastKnownLocation:(CLLocation *)inLocation { 544 | if (CLLocationCoordinate2DIsValid(inLocation.coordinate)) { 545 | // Create dictionary from location 546 | NSMutableDictionary *locationInfo = [NSMutableDictionary dictionary]; 547 | [locationInfo setValue:[NSNumber numberWithDouble:inLocation.coordinate.latitude] forKey:@"LATITUDE"]; 548 | [locationInfo setValue:[NSNumber numberWithDouble:inLocation.coordinate.longitude] forKey:@"LONGITUDE"]; 549 | [locationInfo setValue:inLocation.timestamp forKey:@"TIME_STAMP"]; 550 | // Save dictionary to user defaults 551 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 552 | [defaults setObject:[NSDictionary dictionaryWithDictionary:locationInfo] forKey:@"LAST_KNOWN_LOCATION"]; 553 | } 554 | } 555 | 556 | -(void) _syncBackgroundUpdatesFlagWithBatteryState { 557 | // Enable/disable battery monitoring and notifications as applicable 558 | // We would like to call this method only when the battery state changes, but since 559 | // we do not receive that notification when running in the background, I am calling 560 | // it every time we get a new location update. This way if user has it plugged in 561 | // and active, goes to background and then unplugs it, it will stop updating after 562 | // the next go around. 563 | if (!self.updatesInBackgroundWhenCharging) { 564 | _updateInBackground = NO; 565 | return; 566 | } 567 | 568 | UIDevice *currentDevice = [UIDevice currentDevice]; 569 | UIDeviceBatteryState currentBatteryState = [currentDevice batteryState]; 570 | if (currentBatteryState == UIDeviceBatteryStateCharging || currentBatteryState == UIDeviceBatteryStateFull) { 571 | _updateInBackground = YES; 572 | } else { 573 | _updateInBackground = self.continuesUpdatingOnBattery; 574 | // The flag, having been set to no, will cause locationManager to stop on the next update 575 | } 576 | } 577 | 578 | -(void) _batteryStateDidChange:(NSNotification *)notification { 579 | // Set our update flag as appropriate 580 | // This should be the best point of entry to set update flag however it does not 581 | // necessarily get called when the device is in the background therefore I have 582 | // to break out the sync method and do some redundant checking every time I get 583 | // a new location. 584 | UIApplication *app = [UIApplication sharedApplication]; 585 | __block UIBackgroundTaskIdentifier batteryStateChangeTaskIdentifier = [app beginBackgroundTaskWithExpirationHandler:^{ 586 | dispatch_async(dispatch_get_main_queue(), ^{ 587 | if (batteryStateChangeTaskIdentifier != UIBackgroundTaskInvalid) { 588 | if (OUTPUT_LOGS) NSLog(@"Battery State change reaction failed to finish prior to expiration"); 589 | // *** CONSIDER MORE APPROPRIATE RESPONSE TO EXPIRATION *** // 590 | [app endBackgroundTask:batteryStateChangeTaskIdentifier]; 591 | batteryStateChangeTaskIdentifier = UIBackgroundTaskInvalid; 592 | } 593 | }); 594 | }]; 595 | 596 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 597 | _updateInBackground = NO; 598 | if (self.updatesInBackgroundWhenCharging || self.continuesUpdatingOnBattery) { 599 | UIDeviceBatteryState currentBatteryState = [[UIDevice currentDevice] batteryState]; 600 | if (currentBatteryState == UIDeviceBatteryStateCharging || currentBatteryState == UIDeviceBatteryStateFull) { 601 | _updateInBackground = YES; 602 | [self _startUpdatingLocation]; 603 | } else { 604 | _updateInBackground = self.continuesUpdatingOnBattery; 605 | } 606 | } 607 | 608 | dispatch_async(dispatch_get_main_queue(), ^{ 609 | if (batteryStateChangeTaskIdentifier != UIBackgroundTaskInvalid) { 610 | if (OUTPUT_LOGS) NSLog(@"Ending battery state reaction task normally"); 611 | [app endBackgroundTask:batteryStateChangeTaskIdentifier]; 612 | batteryStateChangeTaskIdentifier = UIBackgroundTaskInvalid; 613 | } 614 | }); 615 | }); 616 | } 617 | 618 | // Changing status bar to indicate status of location updates 619 | -(void) _updateStatusBarStyleActive:(NSNumber *)active { 620 | BOOL existingLocationIsAccurate = [self isLocationWithinRequiredAccuracy:self.lastKnownLocation]; 621 | UIApplication *app = [UIApplication sharedApplication]; 622 | if ([active boolValue]) { 623 | if (existingLocationIsAccurate) { 624 | [app setStatusBarStyle:UIStatusBarStyleBlackTranslucent animated:YES]; 625 | } else { 626 | [app setStatusBarStyle:UIStatusBarStyleBlackOpaque animated:YES]; 627 | } 628 | } else { 629 | if (existingLocationIsAccurate) { 630 | [app setStatusBarStyle:UIStatusBarStyleDefault animated:YES]; 631 | } else { 632 | [app setStatusBarStyle:UIStatusBarStyleBlackOpaque animated:YES]; 633 | } 634 | } 635 | } 636 | 637 | #pragma mark - iPhone 4 Or Higher Only 638 | 639 | //! ONLY IMPLEMENTED on IPHONE 4 640 | - (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region 641 | { 642 | // Send notification to everyone that cares 643 | NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:1], @"eventType", region, @"eventRegion",[NSDate date], @"eventDate", nil]; 644 | NSNotification *aNotification = [NSNotification notificationWithName:LocationHandlerDidCrossBoundary object:self userInfo:dict]; 645 | [[NSNotificationCenter defaultCenter] postNotification:aNotification]; 646 | 647 | // get current location update 648 | self.lastKnownLocation = nil; 649 | [self _startUpdatingLocation]; 650 | } 651 | 652 | //! ONLY IMPLEMENTED on IPHONE 4 653 | - (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region 654 | { 655 | // Send notification to everyone that cares 656 | NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:0], @"eventType", region, @"eventRegion",[NSDate date], @"eventDate", nil]; 657 | NSNotification *aNotification = [NSNotification notificationWithName:LocationHandlerDidCrossBoundary object:self userInfo:dict]; 658 | [[NSNotificationCenter defaultCenter] postNotification:aNotification]; 659 | 660 | // get current location update 661 | self.lastKnownLocation = nil; 662 | [self _startUpdatingLocation]; 663 | } 664 | 665 | //! ONLY IMPLEMENTED on IPHONE 4 666 | - (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error 667 | { 668 | if (OUTPUT_LOGS) NSLog(@"Error monitoring"); 669 | } 670 | 671 | 672 | #pragma mark - Location Accuracy/Recency Checking 673 | 674 | /*! 675 | @method isLocationWithinRequiredAccuracy 676 | @abstract Determines if a location is within the required accuracy 677 | @discussion Compares the horizontal accuracy of the location to the required accuracy 678 | @param location The location to check for accuracy 679 | @return Whether the specified location fell within the accuracy range 680 | */ 681 | - (BOOL) isLocationWithinRequiredAccuracy:(CLLocation *)location 682 | { 683 | if (!location) return NO; 684 | else return location.horizontalAccuracy <= self.requiredAccuracy; 685 | } 686 | 687 | 688 | /*! 689 | @method isReadingRecentForLocation 690 | @abstract Determines if a location is recent relative to the system time 691 | @discussion Compares the location's timestamp to the current time minus the recency threshold (instance property) 692 | @param location A CLLocation object with the location to be checked 693 | @return YES if the timestamp of the location parameter is within the current date minus the threshold, NO if older 694 | */ 695 | - (BOOL) isReadingRecentForLocation:(CLLocation *) location 696 | { 697 | // Do we have a valid location? 698 | if (location) 699 | { 700 | NSDate *thresholdDate = [NSDate dateWithTimeIntervalSinceNow:-self.recencyThreshold]; 701 | NSComparisonResult res = [thresholdDate compare:[location timestamp]]; 702 | return (res == NSOrderedAscending); 703 | } 704 | else 705 | { 706 | return NO; 707 | } 708 | } 709 | 710 | /* Tests the two supplied locations adjusting for the accuracy to confirm for certain they are at least x meters apart 711 | * Returns yes if the locations are confirmed minDistance apart 712 | * Returns no if they are within minDistance or if they are within the margin of error and may possibly be closer than minDistance 713 | */ 714 | - (BOOL) isLocation:(CLLocation *)firstLocation confirmedMinimumDistance:(double)minDistance fromPreviousLocation:(CLLocation *)secondLocation 715 | { 716 | if (!firstLocation || !secondLocation) { 717 | return NO; 718 | } 719 | 720 | CGFloat combinedAdjustment = firstLocation.horizontalAccuracy + secondLocation.horizontalAccuracy; 721 | CGFloat adjustedDistanceFilter = minDistance + combinedAdjustment; 722 | 723 | CLLocationDistance unAdjustedDistance = [firstLocation distanceFromLocation:secondLocation]; 724 | 725 | return unAdjustedDistance >= adjustedDistanceFilter; 726 | 727 | } 728 | 729 | //! Standard dealloc 730 | - (void) dealloc 731 | { 732 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 733 | self.lastKnownLocation = nil; 734 | _pendingLocationsQueue = nil; 735 | [_pendingLocationsTimer invalidate]; 736 | _pendingLocationsTimer = nil; 737 | } 738 | 739 | 740 | @end 741 | 742 | // Notification names 743 | NSString* const LocationHandlerDidUpdateLocation = @"LocationHandlerDidUpdateLocation"; 744 | NSString* const LocationHandlerDidCrossBoundary = @"LocationHandlerDidCrossBoundary"; 745 | --------------------------------------------------------------------------------