The app i'm making draws a Polyline
based on the users coordinates from CLLocationManager
(these are all kept in an NSMutableArray
)
When the app closes, the current polyline disappears. How can I store the array of points in CoreData to be retrieved when the app starts up? All of the past 'routes' that the user has taken since initiating the app should be recovered and overlaid on the map (which may be quite a lot, so CoreData seems the best option from what i've gathered).
This is the code I use to create an MKPolyline from the loaded coordinates
-(IBAction)didClickLoadCoordinates:(id)sender {
// get a reference to the appDelegate so you can access the global managedObjectContext
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"Route"];
NSError *error;
id results = [appDelegate.managedObjectContext executeFetchRequest:request error:&error];
if ([results count]) {
polyLine = (Route *)(results[0]);
NSArray *coordinates = polyLine.coordinates;
int ct = 0;
for (CLLocation *loc in coordinates) {
NSLog(@"location %d: %@", ct++, loc);
}
// this copies the array to your mutableArray
_locationsArray = [coordinates mutableCopy];
}
NSInteger numberOfSteps = _locationsArray.count;
//convert to coordinates array to construct the polyline
CLLocationCoordinate2D clCoordinates[numberOfSteps];
for (NSInteger index = 0; index < numberOfSteps; index++) {
CLLocation *location = [_locationsArray objectAtIndex:index];
CLLocationCoordinate2D coordinate2 = location.coordinate;
clCoordinates[index] = coordinate2;
}
MKPolyline *routeLine = [MKPolyline polylineWithCoordinates:clCoordinates count:numberOfSteps];
[_mapView addOverlay:routeLine];
I've created a simple class that saves a polyline with an array when the app closes, and queries core data for all polylines when the app returns. I assume you know how to set up core data and managed object contexts. This pattern allows you to directly set and get NSArray objects into the core data object. The basic principle behind it is here: https://coderwall.com/p/mx_wmq
First, create the model with a transformable attribute (your array) and a data attribute to go with it.
Next, create a category on the polyline object.
You should now have these items in your file browser
Polyline+TransformableAttributes.h
#import "Polyline.h"
@interface Polyline (TransformableAttributes)
#pragma mark transformables
-(NSArray *)coordinates;
-(void)setCoordinates:(id)coordinates;
@end
Polyline+TransformableAttributes.m
@implementation Polyline (TransformableAttributes)
#pragma mark Transformables
-(NSArray *)coordinates {
if (!self.coordinates_data)
return nil;
return [NSKeyedUnarchiver unarchiveObjectWithData:self.coordinates_data];
}
-(void)setCoordinates:(id)coordinates {
NSData *coordinates_data = [NSKeyedArchiver archivedDataWithRootObject:coordinates];
[self setValue:coordinates_data forKey:@"coordinates_data"];
}
@end
Appdelegate.m
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"Polyline"];
NSError *error;
id results = [self.managedObjectContext executeFetchRequest:request error:&error];
Polyline *polyline = (Polyline *)(results[0]);
NSArray *coordinates = polyline.coordinates;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:@"Polyline" inManagedObjectContext:self.managedObjectContext];
Polyline *polyline = (Polyline *)object;
[polyline setCoordinates:@[@"a", @"b", @"c"]];
NSError *error;
if ([self.managedObjectContext save:&error]) {
NSLog(@"Saved");
}
else {
NSLog(@"Error: %@", error);
}
}
Please let me know if it works for you. I'll update my answer if needed, so that it can be useful. I can't remember where I originally found this pattern but it was a really helpful and highly upvoted
Edit 1: Added a gps view
Here is a new controller I added:
GPSViewController.h:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import "Polyline+TransformableAttributes.h"
@interface GPSViewController : UIViewController <CLLocationManagerDelegate>
{
NSMutableArray *_locationsArray;
Polyline *polyLine;
CLLocationManager *locationManager;
}
-(IBAction)didClickStartGPS:(id)sender;
-(IBAction)didClickSaveCoordinates:(id)sender;
-(IBAction)didClickLoadCoordinates:(id)sender;
The code in my GPSViewController.m:
Initialize the array to store my coordinates.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
_locationsArray = [NSMutableArray array];
}
When you click the GPS button, it goes here. locationManager is an instance variable of the class.
-(IBAction)didClickStartGPS:(id)sender {
locationManager = [[CLLocationManager alloc] init];
[locationManager setDelegate:self];
[locationManager startUpdatingLocation];
}
This saves the coordinates into a polyline and persists it. Note: with this code, I don't do any specific search descriptors, so if you click save multiple times, you'll get a bunch of polylines in your core data, and it'll probably only load the first one each time. You can do stuff like search through them for a certain id or date if you add that to the polyline object.
-(IBAction)didClickSaveCoordinates:(id)sender {
/*
NSInteger numberOfSteps = _locationsArray.count;
// you don't need to convert it to a coordinates array.
CLLocationCoordinate2D coordinates[numberOfSteps];
for (NSInteger index = 0; index < numberOfSteps; index++) {
CLLocation *location = [_locationsArray objectAtIndex:index];
CLLocationCoordinate2D coordinate2 = location.coordinate;
coordinates[index] = coordinate2;
}
*/
// get a reference to the appDelegate so you can access the global managedObjectContext
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
// creates a new polyline object when app goes into the background, and stores it into core data.
if (!polyLine) {
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:@"Polyline" inManagedObjectContext:appDelegate.managedObjectContext];
polyLine = (Polyline *)object;
}
[polyLine setCoordinates:_locationsArray];
NSError *error;
if ([appDelegate.managedObjectContext save:&error]) {
NSLog(@"Saved");
}
else {
NSLog(@"Error: %@", error);
}
}
This loads the first polyline object from core data and converts it into your _locationArray of CLLocations. I don't do anything with the CLLocationCoordinate2D you can get from them.
-(IBAction)didClickLoadCoordinates:(id)sender {
// get a reference to the appDelegate so you can access the global managedObjectContext
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"Polyline"];
NSError *error;
id results = [appDelegate.managedObjectContext executeFetchRequest:request error:&error];
if ([results count]) {
polyLine = (Polyline *)(results[0]);
NSArray *coordinates = polyLine.coordinates;
int ct = 0;
for (CLLocation *loc in coordinates) {
NSLog(@"location %d: %@", ct++, loc);
}
// this copies the array to your mutableArray
_locationsArray = [coordinates mutableCopy];
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *currentLocation = [locations lastObject];
CLLocationDegrees latitude = currentLocation.coordinate.latitude;
CLLocationDegrees longitude = currentLocation.coordinate.longitude;
CLLocationCoordinate2D locationCoordinates = CLLocationCoordinate2DMake(latitude, longitude);
//store latest location in stored track array;
[_locationsArray addObject:currentLocation];
}
This code is updated on my github:
github.com/bobbyren/StackOverflowTest.git
Edit: To add a new MKPolyline for each Polyline:
NSArray *polylines = [fetchedResultsController allObjects];
for (Polyline *polyline in polylines) {
MKPolyline *mkPolyline = [MKPolyline polylineWithCoordinates:polyline.coordinates count:ct]; // assuming you have written out how to return polyline.coordinates as a CLLocationCoordinate2D[]
[mapView addOverlay:line];
}
[mapView reloadData];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With