Search code examples
iosobjective-cparse-platformnsarraymkannotation

Display an array of Parse PFGeoPoints as Map Annotations Error


I am attempting to display objects stored in a NSArray on a map. The objects are being pulled from a Parse.com class called Affiliates after a method to fetch locations within 2 miles of a user has been executed. I have used NSLog to confirm that the query is executing correctly, so I know that the fetch is also being executed correctly.

When I try and run the app, I an error is thrown at the for(NSDictionary) section and the app freezes. The error states "Thread 1: EXC_BAD_ACCESS (access code 1, address 0x80120)" After consulting with Google I see that it is some type of memory allocation issue, but I have no idea why it is occurring or how to fix it.

#import "ViewController.h"
#import "MapAnnotation.h"

@import CoreLocation;

@interface ViewController () <CLLocationManagerDelegate>

@property (nonatomic,strong) NSArray *affiliates;

@end

@implementation ViewController

- (void)viewDidLoad {


    [super viewDidLoad];

    // Ask for authorization to collect location from user
    CLLocationManager * locationManager = [[CLLocationManager alloc] init];
    // Check for iOS 8. Without this guard the code will crash with "unknown selector" on iOS 7.
    if ([locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {

        locationManager.delegate = self;
        locationManager.distanceFilter = kCLDistanceFilterNone;
        locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        [locationManager startUpdatingLocation];
        [locationManager requestWhenInUseAuthorization];
    }

    //Set map options
    self.mapView.showsUserLocation = YES;
    self.mapView.delegate = self;
    self.mapView.scrollEnabled = YES;
    self.mapView.zoomEnabled = YES;
    self.mapView.userTrackingMode = YES;

    // Store the user's location as a Parse PFGeoPoint and call the fetchAffiliatesNearPoint method
    [PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError *error) {
        if (!error) {
            [self fetchAffiliatesNearPoint:geoPoint];
            NSLog(@"Got User Location! %@", geoPoint);
        }
    }];

    /* This is where I am having issues

    for(NSDictionary *affiliates in affiliates) {
        CLLocationCoordinate2D annotationCoordinate = CLLocationCoordinate2DMake([affiliates[@"latitude"] doubleValue], [affiliates[@"longitude"] doubleValue]);

        MapAnnotation *annotation = [[MapAnnotation alloc] init];
        annotation.coordinate = annotationCoordinate;
        annotation.title = affiliates[@"name"];
        annotation.subtitle = affiliates[@"url"];
        [self.mapView addAnnotation:annotation];
    }

*/

}


//Fetch an array of affiliates that are within two miles of the user's current location.
- (void)fetchAffiliatesNearPoint:(PFGeoPoint *)geoPoint
{

    PFQuery *query = [PFQuery queryWithClassName:@"Affiliates"];
    [query whereKey:@"geometry" nearGeoPoint:geoPoint withinMiles:2.0];
    [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
        if (!error)
        {
            self.affiliates = objects;
            NSLog(@"Nearby Locations %@", _affiliates);
        }
    }];
}


-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{


    //Zoom map to users current location
    if (!self.initialLocation) {
        self.initialLocation = userLocation.location;
        MKCoordinateRegion mapRegion;
        mapRegion.center = mapView.userLocation.coordinate;
        mapRegion.span.latitudeDelta = .025;
        mapRegion.span.longitudeDelta = .025;

        [mapView setRegion:mapRegion animated: YES];
        }

}
@end

Solution

  • I haven't worked with Parse either but there are a couple of obvious issues that it may be helpful to point out:

    1. This line doesn't make sense:

      for(NSDictionary *affiliates in affiliates)
      

      Here, the affiliates after in refers to the locally declared dictionary variable and not to the property variable of the same name. Since the local variable has no initialized reference and is pointing to some random memory, you get the EXC_BAD_ACCESS. Use self.affiliates to refer explicitly to the property.

      Additionally, it's extremely confusing to name the looping variable the same as the array it is looping through. You are looping through an array of "affiliates" (plural). The array contains dictionaries and each dictionary is an "affiliate" (singular). It would be less confusing to name the dictionary variable as affiliate (singular). Example:

      for (NSDictionary *affiliate in self.affiliates)
      

      Also change the remaining references inside the loop from affiliates to affiliate.

    2. Currently, the loop on self.affiliates is done immediately after geoPointForCurrentLocationInBackground is started. The geoPointForCurrentLocationInBackground appears to be asynchronous which means the loop on self.affiliates will execute before geoPointForCurrentLocationInBackground has actually set that property.

      Instead of looping through self.affiliates in viewDidLoad right after starting geoPointForCurrentLocationInBackground, move the loop to inside the completion block of findObjectsInBackgroundWithBlock (after self.affiliates is set). Example:

      [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
          if (!error)
          {
              self.affiliates = objects;
              NSLog(@"Nearby Locations %@", _affiliates);
      
              for(NSDictionary *affiliate in self.affiliates) {
                  ...
              }
          }
      }];