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];
Peeter Vedic
  • 101
  • 1
  • 3
  • 8
  • I use transformable attributes: http://www.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. – mitrenegade Aug 20 '14 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 – Peeter Vedic Aug 21 '14 at 00: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. – mitrenegade Sep 21 '14 at 00: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) } – mitrenegade Sep 21 '14 at 00: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 – Peeter Vedic Sep 21 '14 at 01:32
  • i'd say you should store the current app session's coordinates in your location array and always update that. when you open the app, load any past locations, display them each separately as a different overlay, and just get rid of them. unless you need to go back and modify them, you shouldn't need to reference them. so once the app closes, save the current locationArray as a past/historic polyline, that will be loaded and displayed in the future, but you don't need to actively update them anymore. – mitrenegade Sep 21 '14 at 01:46
  • I need to load all polylines from core data (keeping them separate) and then update this. My app requires all polylines to be stored in the same array. So is there a way to update all cllocations and loaded polylines into one array and just separate them based on some kind of logic? Can you just say something like... 'if last point of loaded polyline is older than 10 minutes, do not connect to polyline'. I don't know how to create different arrays for each polyline. And my app does need to remember all the coordinates of everywhere the user has gone (this 'network' can be sent to others) – Peeter Vedic Sep 21 '14 at 23:24
  • So basically I just need to know how to load one polyline containing all past updated points, update this polyline and keep the current cllocation updates from this separate. I did a date predicate to get only the recent polyline and that worked great, but I really only need one polyline object in core data that is updated upon loading on the app start. Is this difficult to do? – Peeter Vedic Sep 21 '14 at 23:30
  • if you keep all the points in the same polyline, there's no way to distinguish the different groups of points in the past. is that what you're looking for? if you put all the points together, they'll all for one big line. you'll be able to separate out the past points with the new/current points this way. – mitrenegade Sep 22 '14 at 03:49
  • I'm not looking for one big polyline - I guess i'm just going to have to figure out how to save a group of coordinates into the polyline object each time so they remain separate. Thanks for your help – Peeter Vedic Sep 22 '14 at 12:40
  • you're welcome - i'm happy to help because i'm doing some polyline stuff myself right now and it's helping me too. although i'm not quite understanding what's making it difficult to create a different polyline object each time, loading a different polyline each time, and drawing a different overlay for each one. i've actually implemented this kind of stuff in one of my own apps. if you'd like, i would love to take a look at your actual code and maybe just implement some of the small steps that's being hard to communicate through SO. if you want to share a github project with me let me know. – mitrenegade Sep 22 '14 at 17:42
  • As long as you're getting something out of it as well, i'm happy! I'm learning a lot of stuff. E.g just spent an hour and a half setting up a git hub. Because i'm using a MBXMapKit it doesn't seem to run for some reason, but all the code is there for you to have a look at https://github.com/peetervedic/MyceliumPolyline. That would be a HUGE help, i'd also love to see your project (just so I can get an idea what a working, well built app looks like instead of mine haha). Thanks – Peeter Vedic Sep 23 '14 at 00:09
  • Hey Bobby, i've tested your code from git and works smooth and polylines aren't connected! One minor interesting bug though is whenever the coordinates are loaded, the first point of the polylines starts somewhere in the Indian Ocean off the West Coast of Africa. I researched this and it seems this particular point has a lat/long of 0,0... I'm still stumped as to why this is happening, but it might be of interest to you. Here's a pic of what happens (and i've tried multiple saves and loads and it does the same with each) http://i.imgur.com/MtwPKif.png – Peeter Vedic Sep 24 '14 at 08:30
  • p.s I appreciate your help immensely, for a beginner like myself it's so much better to see how everything works from the get go and throwing myself in the deep end. Learning so much thanks to you – Peeter Vedic Sep 24 '14 at 09:09
  • I've been looking for solutions and I think it could be because viewDidLoad doesn't have enough time to get the right cllocations, or there's some delay so it just reverts to 0,0 lat/long. http://stackoverflow.com/questions/20350685/polyline-not-drawing-from-user-location-blue-dot this might help. Still unsure how to fix though – Peeter Vedic Sep 24 '14 at 10:08
  • I was also thinking just filtering out cllocations outside say, a 100m range. Would that be easy to do? – Peeter Vedic Sep 24 '14 at 10:16
  • since you don't have any speed/efficiency needs right now i think it would be fine just to calculate a distance each time you do something with a CLLocationCoordinate2D point. so you can either filter out the points before you save them, or filter them out when you load them, or filter them out on the map as the user generates them. – mitrenegade Sep 24 '14 at 16:27
  • hm yeah i'm not sure why it would get a 0,0. I'd think that you wouldn't get a didUpdateLocations callback until the manager's ready. You could also try adding a flag that disables logging the location to _locationsArray until you get a valid map location. MKMapView has a function -(void) mapView:(MKMapView *)_mapView didUpdateUserLocation:(MKUserLocation *)userLocation { that you could probably use to set this flag to enable location mapping. i'll update a branch, but maybe you can try it yourself then compare to mine. – mitrenegade Sep 24 '14 at 16:34
  • I tried your solution and still gives me erratic points when loading coordinates (not just 0,0) which is odd. It doesn't happen when I start updating user coordinates though, just in viewDidLoad. I think the best bet would be to filter the points in viewDidLoad by distance but i'm not sure ... – Peeter Vedic Sep 25 '14 at 02:52
  • are you using the simulator or the phone? – mitrenegade Sep 25 '14 at 06:50
  • I'm using the simulator – Peeter Vedic Sep 25 '14 at 07:00
  • what location are you using? do you happen to be using one of the defaults that changes a lot? i'm just thinking about random possibilities here. – mitrenegade Sep 25 '14 at 12:16
  • I've tried using all location simulations and still the same effect – Peeter Vedic Sep 25 '14 at 23:35
  • I'm going to buy the developers license so I can test it on my iPhone and see if it still does the same thing. If that fails, I'll look into somehow filtering distances using distanceFromLocation to get more accurate cllocations. No idea why this is happening though, rest of the code works perfectly – Peeter Vedic Sep 25 '14 at 23:57
  • Tested and still getting the same results, I tried to add some logic in viewDidLoad but i'm not 100% clear what's happening and how it gets CLLocations - it must be loading before CLLocations are accessed. I'm kind of stuck here... have you encountered this problem before? Any help would be much appreciated! – Peeter Vedic Sep 29 '14 at 00:38
  • have you added breakpoints yet? do you know how to trace the execution of the app step by step? if not, click on the line number in your editor and that will add a break point. then you can step through the process and see what exactly is happening. – mitrenegade Sep 29 '14 at 18:01
  • you shouldn't do any filtering in viewDidLoad. I've updated the code to start the locationManager early, but not save any points until your user clicks start tracking. you should do your filtering in the didUpdateLocations call. – mitrenegade Sep 29 '14 at 18:19
  • Hi Bobby thanks for your help, implemented your code and still producing the same result. I should note that all the tracking and polyline creation is correctly done and the polyline saves just fine. It's just in didClickLoadCoordinates that the first point of the polyline is extended for some reason. Should some filtering go into didClickLoadCoordinates? I think that's where the problem is but i'm not sure :/ – Peeter Vedic Sep 30 '14 at 00:22
  • sorry i've just read my other comments and it may not have been clear, the discrepancy happens in didLoadCoordinates... everything else is fine when startTracking is pressed and when coordinates are saved. – Peeter Vedic Sep 30 '14 at 11:03
  • maybe some old coordinates have been saved with bad coordinates. you should just delete the app and restart it, that way your core data gets cleared out and you remove the old, bad coordinates. – mitrenegade Sep 30 '14 at 13:34
  • also, i'm not sure if you're trying the breakpoint process i suggested, but you should add a breakpoint at the beginning of didClickLoadCoordinates, or NSLog every point you load. I'm sure it's just that one of the data saved from before was 0,0 so you will load that point from core data each time. – mitrenegade Sep 30 '14 at 13:36
  • i've been deleting and reinstalling to test that. From the first initial save after each clearing, it still loads the polyline with the first point connected to some way off point. In the didClickLoad log all the locations are spot on from the past saves, so why would there be one random point? It does the same each time I save again, and click load. So there will two lines (or more than two depending on amount of saves) and each starting point will stretch to another random point. – Peeter Vedic Sep 30 '14 at 14:46
  • i found the issue. it comes from incrementing ct too early here: NSArray *coordinates = route.coordinates; int ct = 0; NSInteger numberOfSteps = coordinates.count; CLLocationCoordinate2D clCoordinates[numberOfSteps]; for (CLLocation *loc in coordinates) { NSLog(@"location %d: %@", ct++, loc); CLLocationCoordinate2D coordinate2 = loc.coordinate; //convert to coordinates array to construct the polyline clCoordinates[ct] = coordinate2; } – mitrenegade Sep 30 '14 at 14:58
  • The ct++ should always go into something actually being used, like clCoordinates[ct++] instead of an NSLog. So the coordinate index is being incremented on the log before you actually added the coordinate to the first object. – mitrenegade Sep 30 '14 at 15:00
  • You should be able to find issues like this if you debug step by step, ie put a break point into the loop where you think things are breaking. Since we knew that a 0,0 coordinate appears in didLoadCoordinates, you should break at the point where it outputs coordinates, and examine the values stored into the array at each step. – mitrenegade Sep 30 '14 at 15:01
  • It's working!!! Thank you so much, I haven't been doing this long enough to notice the finer details but I will certainly use breakpoints in the future. I put the `NSLog(@"location %d: %@", ct++, loc);` under the other lines in that for statement and it's working fine, but is doing the ct++ in a log bad practice? Cheers – Peeter Vedic Sep 30 '14 at 15:32
  • Seriously, you've saved me a lot of time and i'm really really happy to have my app up and running! Can't thank you enough :D :D sorry stack overflow for 40,000 comments don't care though – Peeter Vedic Sep 30 '14 at 15:34
  • no problem =) yes, don't increment the counter inside a log statement. ct++ means it adds 1 to it after it uses the ct value. You only want to add 1 to it when you actually add an object. so to be explicit, any time you use ct, just do someArray[ct]. Then at the end of the loop, you can either just type ct++; or ct = ct + 1; to be explicit about exactly when you increment a counter. – mitrenegade Sep 30 '14 at 15:54
  • Ok perfect! Hopefully I won't have to bother you again for a long while :D Let me know how your app goes, i'd like to have a look at how you implement polylines. Cheers – Peeter Vedic Oct 01 '14 at 01:47
  • Hey Bobby, how's everything going? I'm making progress with my app thanks to you, just working on the UI which is another huge learning curve. Anyway quick question, in the didClickLoad method the cllocations are constructed into a polyline. The polyline is then overlaid on the map as per usual. Because I want the user to be able to switch between maps, the loaded polyline gets hidden beneath the new overlay (MapBox is annoying, once you've initialised a map you need to remove it and add a new overlay). – Peeter Vedic Oct 17 '14 at 03:13
  • So basically what I have now is whenever the user selects a new map, the entire didClickLoad method runs again - this works... but it is a problem because there are a lot of coordinates and it takes ages. Is there a way to just load the coordinates initially and then add the polyline as an overlay as needed without having to load all the coordinates? – Peeter Vedic Oct 17 '14 at 03:14
  • Found a solution, don't worry. I didn't know Overlays had an order so I just had to put the new overlay below the loaded polyline! – Peeter Vedic Oct 18 '14 at 23:36
  • Please refer http://stackoverflow.com/questions/29825604/how-to-save-array-to-coredata/40101654#40101654 – Vinoth Anandan Oct 18 '16 at 07:10
  • Please refer http://stackoverflow.com/questions/29825604/how-to-save-array-to-coredata/40101654#40101654 – Vinoth Anandan Oct 18 '16 at 07:13

1 Answers1

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];
mitrenegade
  • 1,834
  • 18
  • 28
  • 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! – Peeter Vedic Aug 21 '14 at 03:57
  • ok then i think for your case, try the simpler solution here: https://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. – mitrenegade Aug 21 '14 at 03:59
  • I thought arrays couldn't be directly stored in core data? http://stackoverflow.com/questions/4546811/store-nsarray-in-core-data-sample-code – Peeter Vedic Aug 21 '14 at 04:08
  • 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. – mitrenegade Aug 21 '14 at 17:01
  • 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 – Peeter Vedic Aug 22 '14 at 12:55
  • if you're talking about how to actually put the polyline into your view as a line and multiple dots? I'd probably look into MKMapView. Pull out the location points as CLLocationCoordinates and add annotations in the mapview. Maybe something like this: http://stackoverflow.com/questions/1336370/positioning-mkmapview-to-show-multiple-annotations-at-once – mitrenegade Aug 22 '14 at 15:31
  • I've used CLLocationManager to store the lat/long in an array already, which is then overlaid on the map with MKPolylineOverlay. What i'm asking is, do I need to create a custom class like you've done with Polyline.h and Polyline.m? – Peeter Vedic Aug 23 '14 at 01:16
  • If you're using core data, you have to create a core data model, which is the basic Polyline.h and Polyline.m. That's the object which gets stored into core data with an NSData attribute. You don't have to do the category, Polyline+TransformableAttributes.h and Poly+TransformableAttributes.m, which takes Polyline and adds methods to it to get and set an array. You can do this manually each time you save and load instead. Here's a decent read on categories: http://www.binpress.com/tutorial/objectivec-lesson-8-categories/73 – mitrenegade Aug 23 '14 at 14:01
  • i'm having a lot of trouble here. I have my polyline implementation in my normal .h and .m files in the View Controller. I don't have a .h or .m file in my core data. If possible can you please send me your xcode project so I can figure out what i'm doing wrong here? I feel like i'm quite close @mitrenegade – Peeter Vedic Aug 26 '14 at 23:53
  • sure, i'll create a project you can use. core data is kind of tough to learn initially. meanwhile, did you create the Polyline.h and Polyline.m by selecting the Polyline entity in the core data model, and doing Editor->Create NSModel subclass? That's the way to generate a core data class which you don't edit. – mitrenegade Aug 27 '14 at 19:45
  • Go ahead and clone this repository: https://github.com/bobbyren/StackOverflowTest.git The relevant code for you is in the Core Data model, and applicationDidBecomeActive: and applicationWillResignActive: – mitrenegade Aug 27 '14 at 19:57
  • I figured out how to create the NSModel subclass, just not sure what goes in it and how it's different to the normal .h and .m in the ViewController. Thank you so much, i'll check out the code today! – Peeter Vedic Aug 29 '14 at 02:02
  • the difference is that creating a subclass from a core data entity means those files are dynamically generated. if you add a new attribute, say an integer called "type", you can regenerate the subclass and it will overwrite your .h and .m files. this is ideal, but any changes and custom methods you write will be overwritten unless you create a category, namely the Polyline+.h and .m files. those are useful to keep some handy methods around even if you change the polyline model and regenerate the subclasses. – mitrenegade Aug 29 '14 at 18:02
  • The handy methods created in Polyline+transformable category are used to change polyline coordinates from an array of CLLocations to an NSData that can be stored inside the core data object. Here's a nice read on the pattern of categories with core data: http://useyourloaf.com/blog/2010/03/23/using-categories-with-core-data.html – mitrenegade Aug 29 '14 at 18:02
  • Learning a lot about subclasses and CoreData thanks to you! I have a question though - my NSMutableArray (with variable *locations) is created from CLLocationManager updates in the ViewController.h and .m files. Where do I implement the NSMutableArray into the example code that you've provided? Should I keep the NSArray or is that not required? – Peeter Vedic Aug 30 '14 at 01:26
  • the core data object is an immutable array...think of it as you're writing to something permanent when you want to write it to disk. while you're aggregating the locations, just do it in a mutable array owned by the ViewController. There should only be one instance when you save those coordinates into core data. That is probably when you close the app, or you click a button that says "save coordinates" or some single moment. At that moment, you can create a polyline object, copy over the coords from the mutable array into the NSArray, and save to core data. – mitrenegade Aug 30 '14 at 08:09
  • Ok that makes more sense. Right now I have the mutable array of constantly updating locations (declared in ViewController), and this is then used to make a MKPolyline in the ViewController. I'm just confused about where and how I should create the polyline object. – Peeter Vedic Aug 31 '14 at 03:26
  • here's the question you should ask. do you need core data? because you want to persist the coordinates when you close the app, the answer is probably yes. here are reasons you'd use core data. http://stackoverflow.com/questions/1883879/why-should-i-use-core-data-for-my-iphone-app now, for the second to second operation of your app, do you need core data? probably not, since you're fine storing coordinates into an array. only when you need to persist it to disk, will you need the core data interface. you will translate the coords in mkpolyline to a polyline when you exit the app, for one. – mitrenegade Aug 31 '14 at 08:21
  • the way i use core data in some of my objects has to do with web APIs. i talk with a backend that sends me down a bunch of json objects. these are stored into core data, for example, student = [{name: Peeter, city: new york}, {name: bobby, city: san francisco}] gets parsed into two Student objects. I don't need them until later, when I open a tableview and load the students in. at that point, I pull the objects from core data. otherwise, they're just stored in the database. – mitrenegade Aug 31 '14 at 08:24
  • Okay so I get that CoreData can be accessed by declaring the ManagedObjectContext in the AppDelegate. But should I tell it to store a polyline OBJECT with the coordinates from my NSMutableArray in Viewcontroller.m? I'm just not seeing how those two are linked – Peeter Vedic Sep 01 '14 at 01:59
  • @PeeterVedic this whole discussion stems from you needing to store the coordinates in an array and persisting them. you don't have to store the data into an object representation in core data. maybe core data is a little beyond your needs. here's another way to persist an array: use NSUserDefaults: http://stackoverflow.com/questions/7570708/ios-store-an-array-with-nsuserdefault – mitrenegade Sep 01 '14 at 11:29
  • the polyline object is an abstraction that changes your array of cllocation coordinates into a core data object. there's no way to persist an mkpolyline directly because it is not NSCoding compliant. so yes, you have to teoo the managedObjectContext to create a polyline object, populate the polyline data with your array or coordinates, and save it to the database. – mitrenegade Sep 01 '14 at 11:32
  • I think CoreData is going to be initially the toughest to grasp, but for the app i'm developing a ton of location coordinates need to be stored and then retrieved when the app starts. I really appreciate you taking the time to help me through this - i've also been watching tutorials and slowly grasping the abstract idea that is CoreData. My biggest question is still, how do you populate the polyline data with my NSMutableArray that has been created? Can you give me a direct example? – Peeter Vedic Sep 01 '14 at 12:53
  • So to be clear, i've already got CLLocation constantly updating locations that are being stored in NSMutableArray *locationsArray. I'd just like to know how to use this array to populate the data to be saved to the database. – Peeter Vedic Sep 01 '14 at 12:55
  • take a look at the sample code i sent you. that code currently populates a Polyline core data object when the app closes. It basically takes your array and converts it into data that can be persisted. The line is just this: [polyline setCoordinates:@[@"a", @"b", @"c"]]; For a set of coordinates, it would be [polyLine setCoordinates:yourCLLocationArray];. It's actually so simple that it's easy to miss. try it, and let me know if it works. – mitrenegade Sep 02 '14 at 15:14
  • all of the bulk code is in the Polyline+Transformable.m file, if you want to dig into it. It takes your NSMutableArray, encodes it into an NSData object, and stores the NSData into the core data model. When you open the app, it takes whatever NSData is stored, converts it back into an NSArray, and sets it to the Polyline.m attribute. At that point you would want to pull out each coordinate from polyLine and put it into your NSMutableArray for display. – mitrenegade Sep 02 '14 at 15:16
  • This is all probably simple but it's very frustrating for me. So just two basic questions: 1) In the example you do all the CLLocation stuff in the AppDelegate.m file. Should I be using my code in the AppDelegate file too? This is my code in the ViewController.m file to get the CLLocations into my NSMutableArray... – Peeter Vedic Sep 03 '14 at 12:43
  • I've followed exactly however in the AppDelegate.h declaration of Polyline *polyLine it's displaying error saying "unknown type". In your code it's obviously an NSManagedObject, any clues why it's not recognising it? – Peeter Vedic Sep 04 '14 at 00:59
  • did you generate Polyline.m and Polyline.h by selecting on the Model.xcdatamodeld file and going to Editor->Create NSManagedObject Subclass, or did you create Polyline.m and Polyline.h by hand? Does your Polyline.h subclass NSManagedObject this way? #import @interface Polyline : NSManagedObject? – mitrenegade Sep 04 '14 at 01:49
  • Yep did all that. To fix the problem I changed all instances of Polyline to Route - Xcode kept trying to tell me it was an MKPolyline object and after changing it to Route it fixed the error! I now have a working, save load implementation of CoreData thanks to you! Next step now is to form a new polyline overlay with the loaded points, but i'll have a careful run through of what i've currently learnt before doing that. You have taught me so much, thanks! – Peeter Vedic Sep 04 '14 at 03:12
  • So the polyline overlay I had in my previous code is now gone. It should be easy to get back with this `MKPolyline * routeLine = [MKPolyline polylineWithCoordinates:` but it's not letting me give it the locationsArray. What code should I use to construct the polyline? – Peeter Vedic Sep 04 '14 at 11:35
  • Ok I needed to convert the locationsArray to a coordinates array, to get the CLLocationCoordinate 2D object. It's now displaying the polyline. – Peeter Vedic Sep 04 '14 at 11:45
  • One final question, your code works perfectly and as you said, it only loads the polyline from the first time that I pressed saved. How would one go about loading _all_ previous points, from all previous saves? – Peeter Vedic Sep 04 '14 at 11:52
  • peeter it sounds like you're making a lot of progress! i'm glad it all worked out. stackoverflow got mad at me for making this long comment thread but wouldn't let me take it to a chat because you're too low in points haha. to load all polylines, look into nsfetchedrequest. this one: id results = [appDelegate.managedObjectContext executeFetchRequest:request error:&error]; fetches all core data objects based on your request. you can also add nssortdescriptors and nspredicates. i would add an attribute to polyline called "date" and each time you create a polyline, set it to the current date. – mitrenegade Sep 04 '14 at 13:06
  • I can only hope this conversation will be as incredibly helpful to others as it was to me! Haha need 14 more rep - I only started app development 3 weeks ago but it shouldn't take me too much longer - my brain is about to explode with new knowledge... Anyway sorry stack overflow and i'll check out the fetching. Thank you once again – Peeter Vedic Sep 04 '14 at 13:41
  • I looked up fetch request and the code implemented currently seems to be a 'basic fetch', which is all i'm really after. In this case a date attribute would be overkill because it doesn't matter how polylines are sorted, I just need all polylines to be retrieved. I don't really understand why the code you've given doesn't retrieve all (how did you know it would only retrieve the first save?) Spent all day trying to figure this one out :S – Peeter Vedic Sep 06 '14 at 08:05
  • oh, all i meant was that I'm only pulling out the first object from the fetched results: polyLine = (Polyline *)(results[0]); I use that object to display the coordinates. In the code I give you, each time you save a new polyline object that contains your coordinates. so it was a warning that there will be multiple polyline coordinates inside core data, each one contains all of your CLLocationCoordinates. if you pull the first one as I did, it might not be your most recent one. – mitrenegade Sep 07 '14 at 10:19
  • Okay gotcha. But what code should I use to pull all objects? I tried replacing 0 with different numbers in the `results[0]` bit, and it recalled the separate polylines from different saves (as you said). But I want to just pull _all_ previous polylines without any kind of sorting. Basically `results [all]` but that obviously doesn't work because objective c is too much **fun**. Anyway this is probably the simplest thing ever so please forgive my utter ignorance – Peeter Vedic Sep 07 '14 at 13:14
  • results is an NSArray. Basically, id results = [appDelegate.managedObjectContext executeFetchRequest:request error:&error]; gives you the array of all polylines, and you can just do a for loop through them. id is the equivalent of (void *) and is typically used as an unknown type, but if you look at the executeFetchRequest: signature, it returns an NSArray. So if you wish, you can loop through all the polylines and look for the one you last saved, using perhaps an additional date parameter as I suggested before. – mitrenegade Sep 07 '14 at 16:19
  • But I don't want just the last saved, or just the first saved. I want to display all saved polylines. Is there any way to do this? – Peeter Vedic Sep 08 '14 at 11:42
  • peeter, the way polyline.m is implemented, each Polyline object is a container for all of your cllocation points. each time you click the save button in my sample app, it creates one polyline, stores the whole array of CLLocationCoordinates into it, and sets that object into core data. so each time you use it, you're saving 10 coordinates, for example. if you want all of the saved polyline objects, just use the array "result." what i'm warning you about is that if you get a new coordinate and click save, it'll create a new polyline that contains all of the coordinates. – mitrenegade Sep 08 '14 at 18:11
  • then, when you pull out the fetched list of polylines, there will be a polyline with your 10 coordinates, there will be a polyline with 11 coordinates, etc. does that make any sense at all? do you understand the concept of an array? I've clearly shown you that result is an array, result[0] is the first object in the array, and if you want all the polylines, just use all the objects in result: for(int i=0; i – mitrenegade Sep 08 '14 at 18:13
  • and if you actually put some break points into the implementation of polyline.m, you'll see that your coordinates are an array that are being saved into a polyline object, and being converted into an NSData. I don't have the time to implement the whole app for you so I've given you an example of how to dump a Polyline to memory. You don't need every polyline. You only need one polyline, which is used to store all of your CLLocation points. Figure out some way of creating a polyline and storing it into coredata, while making certain that the one you pull out again is the same one. – mitrenegade Sep 08 '14 at 18:15
  • for example, if you add a Date attribute as I suggest, you can create a new polyline each time you want to save your coordinates. then when you retrieve all polylines that are stored in memory, pull out the most recently stored polyline, so that its array contains the most updated array of CLLocation coordinates. – mitrenegade Sep 08 '14 at 18:17
  • alternatively, each time you save a polyline, load all polylines from core data first. If one already exists, just update its coordinates. That way you'll always only have one Polyline object in core data, which will contain 10 points, 11 points, 12 points, etc each time as you update your coordinates. – mitrenegade Sep 08 '14 at 18:17
  • if you actually want multiple polylines, for example if you have three maps that you want to save separately, add something like a "name" attribute. For each map, save its coordinates and its name into a polyline object. You can add a NSPredicate to search for a specific query: NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", mapName]; and it will pull out the core data with name = mapName. – mitrenegade Sep 08 '14 at 18:20
  • Thank you for your patience and thorough explanation, that made everything much clearer. I will try implement a date attribute to the polyline object and pull only the most recently updated polyline (I don't need all polylines, just the most updated). Thanks again and I will not pester you anymore, you've done more than enough. p.s when I finish the app your name is most definitely going in the credits :D – Peeter Vedic Sep 09 '14 at 03:02
  • haha, good luck! and please do ask me if anything gets you really really stuck. – mitrenegade Sep 09 '14 at 04:20
  • Hey, me again. I've run into a strange issue that is probably easily resolvable. I load the coordinates from core data when the app starts, thereby creating a polyline of the past route on the screen (all good) - but when I start updating locations, it connects the two polylines from the last location on the loaded polyline to the first cllocation update. So where the lines should be separate, there's a large straight bit of polyline. Is there a way to filter out this result? I've tried using a NSDate object to look at the timestamp but not working.. – Peeter Vedic Sep 15 '14 at 23:49
  • http://stackoverflow.com/questions/25858229/create-mkpolyline-with-only-recent-cllocations – Peeter Vedic Sep 15 '14 at 23:50
  • i think you'll have to have multiple polylines, and add multiple overlays to the map. – mitrenegade Sep 15 '14 at 23:54
  • to be more clear, so for each polyline object, make a different MKPolyline object from it, and do [mapview addOVerlay:mkPolyline]. it also depends on how you're drawing your polylines. you can add a breakpoint in the mapview rendering you can maybe see what's causing the drawing between the last point and the new point. – mitrenegade Sep 16 '14 at 00:02
  • For my app it would be best to load all polylines, and then update that. There are going to be separate polylines, or in other words, gaps between polylines correlating with the apps closure. How would you recommend creating and saving the separate polylines? Is there a way to specify in the CLLocationManager to say "if points already exist (polylines loaded), then discard their locations and update from current location? To prevent this kind of thing from happening (straight jump to current location from last point) http://i.imgur.com/WM51qAe.png – Peeter Vedic Sep 18 '14 at 07:12
  • look at the polyline object as a way to store coordinates, and maybe group them. otherwise, any logic you need to have should be done somewhere in your controller. my understanding is that you'll track a set of locations, like where a person's gone while the app's open. when you close it, store those coordinates as one polyline, add a timestamp or something else, and save it to core data. next time your app opens, load any polylines that you've stored, and then your app should do the logic of deciding whether to connect one to the other, or draw them separately. – mitrenegade Sep 18 '14 at 13:21
  • each polyline is just an NSArray of coordinates. so if you don't want them to be connected to each other, create a new MKPolyline from each NSArray of coordinates, and add that overlay to the map. Each MKMapView can have multiple MKPolyline overlays, and they won't connect to each other. Are you pulling out all the CLLocationCoordinates and putting them into the same array? If you do that they will be connected. let me know if this is what you're doing. – mitrenegade Sep 18 '14 at 13:23
  • All the CLLocationCoordinates are going into the same array. This array forms the polyline. So when I open the app again, the polyline loads, and I need to draw the newly updated polyline separately... In no way should they be connected because the user will always be in a new location. So should I do multiple overlays? How can I do this? – Peeter Vedic Sep 19 '14 at 04:22
  • yes, you should make multiple arrays, and multiple overlays. it's actually quite simple. for each polyline object, create a different array of CLLocationCoordate2D values, then: MKPolyline *polyline = [MKPolyline polylineWithCoordinates:fieldCoordinates count:fieldCoordinateCount]; [mapView addOverlay:polyline]; – mitrenegade Sep 19 '14 at 04:42
  • and i've seen some comments where you should use a mapview renderer which gives you control over each line, if you're not already doing that. here's code i used: -(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id)overlay { if ([overlay isKindOfClass:[MKPolyline class]]) { MKPolyline *polyline = (MKPolyline *)overlay; MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay]; renderer.lineWidth = 4; renderer.strokeColor = [UIColor grayColor]; renderer.lineCap = kCGLineCapRound; return renderer;}return nil;} – mitrenegade Sep 19 '14 at 04:44
  • how do I create a different array of CLLocationCoordinate2D values for each line? I understand everything else basically, i'm using the MKOverlayRenderer that you suggested :) Thanks! – Peeter Vedic Sep 20 '14 at 01:53
  • I've edited my answer...it seems like it should be really simple, so maybe you can update your post with the code where you load the Polylines from core data, turn them into a single CLLocation2D array, and draw one single MKPolyline from them. That way we can figure out how you can easily separate each. – mitrenegade Sep 20 '14 at 03:45
  • Updated my post - all the loaded points are copied into the locations array and drawn from that. That can't be right... Where does the code you've provided go. In viewDidLoad? – Peeter Vedic Sep 20 '14 at 06:33
  • struggling to translate this to swift - have raised a question here http://stackoverflow.com/questions/36761841/how-to-store-an-mkpolyline-attribute-as-transformable-in-ios-coredata-with-swift – Greg Apr 21 '16 at 06:56
  • oooh...i actually havent translated much of my core data over to swift so i cant answer that question right now. i started using some extensions but nothing with transformables yet. i'll look into it when i get a chance. – mitrenegade Apr 21 '16 at 13:39