6

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];
44
  • I use transformable attributes: samuelwford.com/mutable-transformable-properties-in-core-data. I actually use immutable attributes, since my saving to disk doesn't happen very often. if you know exactly when you save to disk (ie on app close, or logout) you can basically convert your array to an NSData object and store it into your Core Data object. If you would like a bit more on the pattern I use (including a category on the object) i can post it. Commented Aug 20, 2014 at 23:54
  • Ok thanks. I'll be saving the array (points) just before or when the app closes. Can you give me a bit more info on converting the array to an NSData object, and saving it in Core Data? @mitrenegade Commented Aug 21, 2014 at 0:55
  • ok so it looks like you're pulling the first polyline in core data out and displaying that. When the user moves/creates a new coordinate, do you add it to the same array? Do you have one single array that you put your loaded coordinates into, then add new coordinates to? You could save a group of coordinates into a new polyline each time and store it into core data. Then instead of just using polyLine = (Route *)(results[0]), you can store an array of polylines = (NSArray *)results. Commented Sep 21, 2014 at 0:44
  • or, if you don't need to store the old polylines, but just draw them, just put the code i gave you into an array that iterates over results. for ((Route *)route in results) { ... do everything else you've done for results(0) } Commented Sep 21, 2014 at 0:46
  • all the cllocations are stored in the locationsArray (and the loaded coordinates are copied into this array too). From this, I get the CLLocationCoordinates2D. So yes, just one single array that new coordinates are loaded into and new coordinates are added too. I suppose this is why the loaded polyline is getting connected with the newly updating cllocation polyline... I just need these lines to be separate, but all old polylines still are needed. Think of my app as just creating a giant network of polylines wherever the user has been - but when the app is off there are obviously gaps Commented Sep 21, 2014 at 1:32

1 Answer 1

8

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.

model

Next, create a category on the polyline object.

category

You should now have these items in your file browser

files

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:

enter image description here

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];
Sign up to request clarification or add additional context in comments.

63 Comments

I'm a beginner so i'm currently reading up on Core Data and managing objects etc. Marked as answered for images and thorough detail - will read and implement soon. Thanks a lot!
ok then i think for your case, try the simpler solution here: coderwall.com/p/mx_wmq. you basically convert your array into nsdata when you save core data, and unarchive it when you load it and need to access the array.
I thought arrays couldn't be directly stored in core data? stackoverflow.com/questions/4546811/…
in your core data object model, you can have an NSData attribute. That is essentially your array encoded into raw data bytes. Each time you save and load core data from the sql database, encode or decode it and set it to an array. My pattern gives you access to an NSArray attribute that doesn't get saved into the core data object, but is directly accessible as polyline.coordinateArray.
I've implemented the Core Data framework into my project, i'm just wondering if I need to have a custom class that draws the polyline. In my app this is all done through methods in the ViewController... would you recommend creating a custom class? Thanks
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.