Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you store data from NSMutable Array in Core Data?

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];
like image 502
Peeter Vedic Avatar asked Aug 20 '14 23:08

Peeter Vedic


1 Answers

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];
like image 189
mitrenegade Avatar answered Sep 22 '22 02:09

mitrenegade