This is my method
- (void)populateLocationsToSort {
//1. Get UserLocation based on mapview
self.userLocation = [[CLLocation alloc] initWithLatitude:self._mapView.userLocation.coordinate.latitude longitude:self._mapView.userLocation.coordinate.longitude];
//Set self.annotationsToSort so any new values get written onto a clean array
self.myLocationsToSort = nil;
// Loop thru dictionary-->Create allocations --> But dont plot
for (Holiday * holidayObject in self.farSiman) {
// 3. Unload objects values into locals
NSString * latitude = holidayObject.latitude;
NSString * longitude = holidayObject.longitude;
NSString * storeDescription = holidayObject.name;
NSString * address = holidayObject.address;
// 4. Create MyLocation object based on locals gotten from Custom Object
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude.doubleValue;
coordinate.longitude = longitude.doubleValue;
MyLocation *annotation = [[MyLocation alloc] initWithName:storeDescription address:address coordinate:coordinate distance:0];
// 5. Calculate distance between locations & uL
CLLocation *pinLocation = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];
CLLocationDistance calculatedDistance = [pinLocation distanceFromLocation:self.userLocation];
annotation.distance = calculatedDistance/1000;
//Add annotation to local NSMArray
[self.myLocationsToSort addObject:annotation];
**NSLog(#"self.myLocationsToSort in someEarlyMethod is %#",self.myLocationsToSort);**
}
//2. Set appDelegate userLocation
AppDelegate *myDelegate = [[UIApplication sharedApplication] delegate];
myDelegate.userLocation = self.userLocation;
//3. Set appDelegate mylocations
myDelegate.annotationsToSort = self.myLocationsToSort;
}
In the bold line, self.myLocationsToSort is already null. I thought setting a value to nil was cleaning it out basically, ready to be re-used? I need to do so because this method is called once on launch and a second time after an NSNotification is received when data is gotten from the web. If I call this method again from the NSNotification selector, the new web data gets written on top of the old data and it spits out an inconsistent mess of values :)
Setting it to nil removes the reference to that object. If you are using ARC and it is the last strong reference to that object, the system autoreleases the object and frees its memory. In either case, it does not "clean it out and be ready for re-use", you need to re-allocate and initialize your object. If you would rather just remove all of the objects, and assuming myLocationsToSort is an NSMutableArray you can just call
[self.myLocationsToSort removeAllObjects];
Otherwise you need to do
self.myLocationsToSort = nil;
self.myLocationsToSort = [[NSMutableArray alloc] init];
Related
I have a map app that allows the User to save annotations as a favorite to a mutable array. All favorite annotations can then be displayed when the User chooses to.
Annotations added to the mutable array are of the MKPointAnnotation class. I can correctly add annotations to the mutable array, but I haven't come up with a working solution that correctly removes a specific annotation from the mutable array. How can a specific annotation be removed from the mutable array that contains multiple annotations that were saved as favorites? Several non-working solutions are found in my sample code.
//** Correctly adds a favorite annotation to the mutable array favoriteAnnotationsArray **
-(void)addToFavoriteAnnotationsArray{
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
NSArray *components = [favoritesString componentsSeparatedByString:#","];
annotation.coordinate = CLLocationCoordinate2DMake([components[1] doubleValue], [components[0] doubleValue]);
annotation.title = [components[2] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
[self.favoriteAnnotationsArray addObject:annotation];
}
//** Need to remove a favorite annotation from the mutable array favoriteAnnotationsArray **
-(void)removeObjectFromFavoriteAnnotationsArray{
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
NSArray *components = [favoritesString componentsSeparatedByString:#","];
annotation.coordinate = CLLocationCoordinate2DMake([components[1] doubleValue], [components[0] doubleValue]);
annotation.title = [components[2] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
//** Used in first non-working solution below **
//NSMutableArray *objectToRemoveArray = [[NSMutableArray alloc] init];
//[objectToRemoveArray addObject:annotation];
//** The following three lines didn't remove any annotations from array **
//[self.favoriteAnnotationsArray removeObjectsInArray:objectToRemoveArray];
//[self.favoriteAnnotationsArray removeObject:annotation];
//[self.favoriteAnnotationsArray removeObjectIdenticalTo:annotation];
//** This only removes the last object in array and not necessarily the correct annotation to remove **
[self.favoriteAnnotationsArray removeLastObject];
}
You will need to specify an unique annotation from the favoriteAnnotationsArray in order for it o be removed successfully.
Maybe you can try something as follows:
-(void)removeAnnotationFromFavoriteAnnotationsArrayWithTitle: (NSString *) titleString {
for(int i=0; i<self.favoriteAnnotationsArray.count; i++) {
MKPointAnnotation *annotation = (MKPointAnnotation *)[self.favoriteAnnotationsArray objectAtIndex:i];
NSString * annotationTitle = annotation.title;
if([annotationTitle isEqualToString:titleString]) {
[self.favoriteAnnotationsArray removeObject:annotation];
break;
}
}
}
If title is not unique enough for you to differentiate between annotations, then you might consider subclassing MKAnnotation and add an unique property and pass it to the above function instead of the title.
One approach is to iterate through your annotation array and find the one to remove. For example:
- (void)addToFavoriteAnnotationsArray:(NSString *)string {
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
NSArray *components = [string componentsSeparatedByString:#","];
annotation.coordinate = CLLocationCoordinate2DMake([components[1] doubleValue], [components[0] doubleValue]);
annotation.title = [components[2] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
[self.favoriteAnnotationsArray addObject:annotation];
}
- (void)removeObjectFromFavoriteAnnotationsArray:(NSString *)string {
NSArray *components = [string componentsSeparatedByString:#","];
NSString *titleOfAnnotationToRemove = [components[2] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
CLLocationCoordinate2D coordinateOfAnnotationToRemove = CLLocationCoordinate2DMake([components[1] doubleValue], [components[0] doubleValue]);
for (NSInteger i = 0; i < self.favoriteAnnotationsArray.count; i++) {
id<MKAnnotation>annotation = self.favoriteAnnotationsArray[i];
if ([annotation.title isEqualToString:titleOfAnnotationToRemove] && coordinateOfAnnotationToRemove.latitude == annotation.coordinate.latitude && coordinateOfAnnotationToRemove.longitude == annotation.coordinate.longitude) {
[self.favoriteAnnotationsArray removeObjectAtIndex:i];
break;
}
}
}
Or if you wanted to match on title, alone, then remove the coordinates from the if statement above. Also note that I've added the string as a parameter to these methods. It's always better to pass a parameter to a method rather than relying upon some property.
Frankly, though, when the user is selecting one to remove, you probably don't want them to have to manually enter the name and coordinates again. You probably want them to pick one from the list. So you might show them a table view of the annotations and when they say they want to remove the third one, you'd do just pass the index to a method like this:
- (void)removeObjectFromFavoriteAnnotationsArray:(NSInteger)index {
[self.favoriteAnnotationsArray removeObjectAtIndex:index];
}
By the way, all of the above routines remove the annotation from your array. If you've also added this annotation to the map view, remember to remove it from there, too.
I am trying to set a CLLocation property (named objectLocation) of a custom class, using the below code, called from my main ViewController. Unfortunately, I receive an error telling me on the "commented" line, that the "expression is not assignable." locationsArray is an array of objects of the custom class. I really need to set this value, so any help is appreciated!
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
for (int i = 0; i < [locationsArray count]; i++) {
if ([locationsArray[i] objectLocation] == NULL) {
[locationsArray[i] objectLocation] = [locations lastObject]; //retrieves most recent location data - THIS IS THE LINE THAT RECEIVES THE ERROR
//now set up a region and start monitoring data for that region
[locationsArray[i] region] = [[CLRegion alloc]
initCircularRegionWithCenter:
[[locationsArray[i] objectLocation] coordinate]
radius:2
identifier:[NSString stringWithFormat:#"%#", [locationsArray[i] objectLocationName]]];
}
}
}
If it is a property of CLLocation within your class and given that your locationsArray holds the object and your locations array does hold a CLLocation object, then please try the following:
((<YOUR_CLASS>*)[locationsArray[i]).objectLocation = [locations lastObject];
Hope this helps.
I'm still learning objective C and iOS and I'm running into a problem. I am creating an array from CoreData that contains latitudes and longitudes. I want to take this array and sort it by the closest location.
This is what I have so far:
NSError *error = nil;
NSFetchRequest *getProjects = [[NSFetchRequest alloc] init];
NSEntityDescription *projectsEntity = [NSEntityDescription entityForName:#"TimeProjects" inManagedObjectContext:context];
[getProjects setEntity:projectsEntity];
projectArray = [[context executeFetchRequest:getProjects error:&error] mutableCopy];
for (NSObject *project in projectArray) {
// Get location of house
NSNumber *lat = [project valueForKey:#"houseLat"];
NSNumber *lng = [project valueForKey:#"HouseLng"];
CLLocationCoordinate2D coord;
coord.latitude = (CLLocationDegrees)[lat doubleValue];
coord.longitude = (CLLocationDegrees)[lng doubleValue];
houseLocation = [[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude];
//NSLog(#"House location: %#", houseLocation);
CLLocationDistance meters = [houseLocation distanceFromLocation:currentLocation];
}
I also have this sorting code but I'm not sure how to put the two together.
[projectArray sortUsingComparator:^NSComparisonResult(id o1, id o2) {
CLLocation *l1 = o1, *l2 = o2;
CLLocationDistance d1 = [l1 distanceFromLocation:currentLocation];
CLLocationDistance d2 = [l2 distanceFromLocation:currentLocation];
return d1 < d2 ? NSOrderedAscending : d1 > d2 ? NSOrderedDescending : NSOrderedSame;
}];
Can some one help me out with making these two things work together?
Your sortUsingComparator block expects CLLocation objects, not instances of your
Core Data class. That would be easy to fix, but what I would recommend is:
Add a transient property currentDistance to your entity. (Transient properties are not stored in the persistent store file.) The type should be "Double".
After fetching the objects, compute the currentDistance for all objects in projectArray.
Finally sort the projectArray array, using a sort descriptor on the currentDistance key.
The advantage is that the distance to the current location is calculated only once for each object, and not calculated repeatedly in the comparator method.
The code would look like this (not compiler checked!):
NSMutableArray *projectArray = ... // your mutable copy of the fetched objects
for (TimeProjects *project in projectArray) {
CLLocationDegrees lat = [project.houseLat doubleValue];
CLLocationDegrees lng = [project.houseLng doubleValue];
CLLocation *houseLocation = [[CLLocation alloc] initWithLatitude:lat longitude:lng];
CLLocationDistance meters = [houseLocation distanceFromLocation:currentLocation];
project.currentDistance = #(meters);
}
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"currentDistance" ascending:YES]
[projectArray sortUsingDescriptors:#[sort]];
Alternatively, you can make currentDistance a persistent property of the entity and calculate it when the object is created or modified. The advantage is that you could add
a sort descriptor based on currentDistance to the fetch request instead of fetching
first and sorting afterwards. The disadvantage is of course that you have to re-calculate
all values when the current location changes.
In my app I fetch a coredatabase and put results into an array called self.stores. I convert those store locations to MyLocation objects which have a distance property. I plot the MyLocation objects like so:
- (void)plotStorePositions:(NSString *)responseString {
for (id<MKAnnotation> annotation in _mapView.annotations) {
[_mapView removeAnnotation:annotation];
}
//CYCLE THRU STORE OBJECTS
for (Store * storeObject in self.stores) {
NSString * latitude = storeObject.latitude;
NSString * longitude = storeObject.longitude;
NSString * storeDescription = storeObject.name;
NSString * address = storeObject.address;
//CREATE MYLOCATION OBJECTS
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude.doubleValue;
coordinate.longitude = longitude.doubleValue;
MyLocation *annotation = [[MyLocation alloc] initWithName:storeDescription address:address coordinate:coordinate distance:0];
//CREATE A PIN FOR EACH MYLOCATION ANNOTATION
CLLocation *pinLocation = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];
//SET USERLOCATION (Must move this code out)
self.userLocation = [[CLLocation alloc] initWithLatitude:self._mapView.userLocation.coordinate.latitude longitude:self._mapView.userLocation.coordinate.longitude];
//SET USERLOCATION TO APPDELEGATE (Must move this code out)
AppDelegate *myDelegate = [[UIApplication sharedApplication] delegate];
myDelegate.userLocation = self.userLocation;
//CALCULATE DISTANCE AND SET ITS THAT MYLOCATION's DISTANCE PROPERTY
CLLocationDistance calculatedDistance = [pinLocation distanceFromLocation:userLocation];
annotation.distance = calculatedDistance/1000;
[_mapView addAnnotation:annotation];
}
}
This is in a mapview. I then have a tableview where I fetch the same coredatabase and get self.stores again. Then I cycle through that array of self.stores to create STORE storeObjects to put into my tableview cells. But I want to sort those cells. How do I get MyLocation objects which should be in memory? Do I need to pass them to the appDelegate or to the tableview controller?
As you are using Core Data, your best option is to use a NSFetchedResultsController to populate your table view.
All you need then is the managed object context, either from another controller or the app delegate.
I'm making an app that displays the user on the map along with multiple restaurant listings. When the user taps a pin, it stores the coordinates from the annotation and compares them against the users to ensure they are different. Once it is determined that they are different, it sends the coordinates of the business along with the coordinates of the user to Google to request directions. The code is working fine, but in order for it to do so I had to declare a few variables in ways that cause memory leaks. I'm hoping to clean the code up and learn where I erred and the proper way this should be handled.
So below is my code for getting the coordinates from the annotation that was tapped. If I attempt to initialize selectedAnnotation and allocate it's memory in say viewDidLoad by putting selectedAnnotation = [[MapLocation alloc] init]; Then it still shows up as a memory leak. And for reference, selectedAnnotation is a MapLocation(that conforms to MKAnnotation) variable, and as a property I have it (nonatomic, retain) and #synthesize(d).
I thought as long as I allocated it in memory, so long as I set its value to nil in viewDidUnload and released it in dealloc, that there should be no memory issues. What am I missing? Below is a screen shot of my memory leaks when I allocate the memory for selectedAnnotation in viewDidLoad along with the code provided below. If I have already allocated the memory, and check that the variable exists, why would it allocate the memory for the variable again? This happens for any restaurant pin I click on, but obviously not on the user's pin, because I have the code to release it in that case.
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
//NSLog(#"Selected annotation view");
// if we don't have the place holder already allocated
// lazy load the MapLocation placeholder variable
if(!selectedAnnotation)
{
selectedAnnotation = [[MapLocation alloc] init];
}
// save the annotation clicked
selectedAnnotation = view.annotation;
// if the annotation selected was is the same as the user's location
if((selectedAnnotation.coordinate.latitude == savedUserLocation.coordinate.latitude) && (selectedAnnotation.coordinate.longitude == savedUserLocation.coordinate.longitude))
{
// set it to nil and release it
selectedAnnotation = nil;
[selectedAnnotation release];
}
}
I'm having similar trouble with memory issues with the method below. I'm bringing in the JSON data from Google, to extract the address and coordinates of the user's location to display in the AnnotationView. I created all the necessary Arrays and Dictionaries to access the information, but once I allocate the memory for them and assign their values to savedUserLocation, if I try releasing say the NSDictionary variable userLocation, even as the last line of code in this method, the app crashes due to "[CFDictionary release]: message sent to deallocated instance 0x83ccb60". I'm pretty sure it's because I'm setting the values in savedUserLocation via pointer, and once the memory is released that information no longer exists, so what would be the proper way to allocate/release the memory to where I can access the information, without causing memory leaks? I've also tried using autorelease, but the same issues persist.
Here is the code that places the user pin.
- (void)fetchedData:(NSData *)responseData
{
//parse out the json data
NSError *error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSArray *results = [json objectForKey:#"results"]; //2
NSUInteger counter = [results count];
NSDictionary *userLocation = [[NSDictionary alloc] init];
//NSString *address = [[NSString alloc] init];
for(NSUInteger i=0; i < counter; i++)
{
userLocation = [results objectAtIndex:i];
// 2) Get the funded amount and loan amount
NSString *address = [[NSString alloc] initWithString:[userLocation objectForKey:#"formatted_address"]];
NSArray *types = [userLocation objectForKey:#"types"];
NSDictionary *geometry = [userLocation objectForKey:#"geometry"];
NSDictionary *location = [geometry objectForKey:#"location"];
float lat = [[location objectForKey:#"lat"] floatValue];
float lon = [[location objectForKey:#"lng"] floatValue];
CLLocationCoordinate2D newCoordinates;
newCoordinates.latitude = lat;
newCoordinates.longitude = lon;
// count how many types there are
NSUInteger numberOfTypes = [types count];
NSString *type = [[NSString alloc] init];
for(NSUInteger j=0; j < numberOfTypes; j++)
{
type = [types objectAtIndex:j];
if([type rangeOfString:#"street_address" options:NSCaseInsensitiveSearch].location != NSNotFound)
{
NSLog(#"%#", address);
if(!savedUserLocation)
{
savedUserLocation = [[MapLocation alloc] init];
}
[savedUserLocation setTitle:#"You are here!"];
[savedUserLocation setSubtitle:address];
[savedUserLocation setCoordinate:newCoordinates];
}
}
}
// determine which location is closest to the user by calling this function
MapLocation *closestLocation = [self determineClosestLocationToUser:allLocations locationOfUser:savedUserLocation];
// send in the user location and the closest store to them to determine appropriate zoom level and
// to center the map between the two
[self determineMapCenterAndZoomLevelFromUser:savedUserLocation andClosestLocation:closestLocation];
if(!pinDropped)
{
// add the annotation to the map and then release it
[mapView addAnnotation:savedUserLocation];
pinDropped = true;
}
}
Thanks for any and all help/suggestions/advice. I'd really like to understand the nuts and bolts of what I'm doing wrong, as I thought I had a pretty decent grasp on it.
In didSelectAnnotationView, you have this code:
selectedAnnotation = nil;
[selectedAnnotation release];
This causes a memory leak because you are setting selectedAnnotation to nil and then calling release on it.
The call to release does nothing because selectedAnnotation is nil at that point and a call to nil does nothing. This means the memory that had been allocated is never released but since the pointer variable has been set to nil, when didSelectAnnotationView is called again, your code allocates a new object.
You should switch the order of the two statements (call release first and then set to nil).
However, you don't need to alloc a new object just to keep a reference to the "selected annotation".
Declaring a regular ivar (not a retain property) and just setting it equal to the selected annotation should work.
In addition, the map view already has a property called selectedAnnotations which you should be able to use (so you don't need to maintain your own ivar or property). The map view's property is an NSArray but will always contain either 0 or 1 object(s). Be sure to check its count before accessing the object at index 0.
In fetchedData, you have several memory leaks caused by unnecessary alloc calls.
They are not needed because after calling alloc, you are then directly assigning a new reference to the pointer you just allocated memory for.
For example, userLocation is alloc'd before the for-loop but then inside the loop you directly point that variable to an object in the results array.
This means the memory that had originally been allocated for userLocation becomes abandoned with no reference to it. When you try to call release on userLocation, it is trying to release an object that was not allocated by code in fetchedData.
To fix at least userLocation, just declare the variable and don't alloc/init/release it.
The variables address and type (the NSString) have a similar issue.