Importing NSMutable Array into Map Annotations

Discussion in 'iOS Programming' started by Futhark, Apr 23, 2013.

  1. Futhark macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #1
    Hi all i have a mysql database that consists of 7 columns and i have a script that reads it and extracts the data as JSON which i have pulled into an NSMutableArray, I want to be able to use this array to setup annotations on my Map but i'm not sure what to do here, as you can see i have defined one annotation here which shows up no problem but i'm honestly not sure how to show the NSMutableArray items? The NSMutable array will have more information than needed for the Annotations, I only need, coordinate, title and subtitle so how can i go about doing this? Here is my code so far:

    hazards.h

    Code:
    #import <Foundation/Foundation.h>
    #import <MapKit/MapKit.h>
    
    @interface Hazards : NSObject <MKAnnotation>
    
    @property (nonatomic, assign) CLLocationCoordinate2D coordinate;
    @property (nonatomic, copy) NSString *title;
    @property (nonatomic, copy) NSString *subtitle;
    
    @property (nonatomic, strong) NSString * ID;
    @property (nonatomic, strong) NSString * ROUTE;
    @property (nonatomic, strong) NSString * ADDRESS;
    @property (nonatomic, strong) NSString * LATITUDE;
    @property (nonatomic, strong) NSString * LONGITUDE;
    @property (nonatomic, strong) NSString * HAZARD;
    @property (nonatomic, strong) NSString * RISK;
    
    // Methods
    - (id) initWithID: (NSString *) hazardsID andROUTE: (NSString *) hazardsROUTE andADDRESS: (NSString *) hazardsADDRESS andLATITUDE: (NSString *) hazardsLATITUDE andLONGITUDE: (NSString *) hazardsLONGITUDE andHAZARD: (NSString *) hazardsHAZARD andRISK: (NSString *) hazardsRISK;
    
    @end
    hazards.m

    Code:
    #import "Hazards.h"
    
    @implementation Hazards
    @synthesize coordinate, title, subtitle, ID, ROUTE, ADDRESS, LATITUDE, LONGITUDE, HAZARD, RISK;
    
    - (id) initWithID: (NSString *) hazardsID andROUTE: (NSString *) hazardsROUTE andADDRESS: (NSString *) hazardsADDRESS andLATITUDE: (NSString *) hazardsLATITUDE andLONGITUDE: (NSString *) hazardsLONGITUDE andHAZARD: (NSString *) hazardsHAZARD andRISK: (NSString *) hazardsRISK {
    
        self = [super init];
        if (self)
        {
            ID = hazardsID;
            ROUTE = hazardsROUTE;
            ADDRESS = hazardsADDRESS;
            LATITUDE = hazardsLATITUDE;
            LONGITUDE = hazardsLONGITUDE;
            HAZARD = hazardsHAZARD;
            RISK = hazardsRISK;
        }
    return self;
    }
    
    @end
    viewController.h

    Code:
    #import <UIKit/UIKit.h>
    #import <MapKit/MapKit.h>
    #import "Hazards.h"
    
    @interface ViewController : UIViewController
    
    @property (nonatomic, strong) NSMutableArray *json;
    @property (nonatomic, strong) NSMutableArray *hazardsArray;
    
    @property (weak, nonatomic) IBOutlet MKMapView *mapView;
    
    #pragma mark - Methods
    -(void) retrieveData;
    
    @end
    viewController.m

    Code:
    #import "ViewController.h"
    #import "Hazards.h"
    
    @interface ViewController ()
    
    @end
    
    // Railway Street Ballymena Coordinates
    #define BALLYMENA_LATITUDE 54.857719;
    #define BALLYMENA_LONGITUDE -6.280654;
    
    // Span
    #define THE_SPAN 0.01f;
    
    #define getDataURL @"localhost:8888/rmb/json.php"
    
    @implementation ViewController
    @synthesize json, hazardsArray, mapView;
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Create the region
        MKCoordinateRegion myRegion;
    
        // Center
        CLLocationCoordinate2D center;
        center.latitude = BALLYMENA_LATITUDE;
        center.longitude = BALLYMENA_LONGITUDE;
    
        //Span
        MKCoordinateSpan span;
        span.latitudeDelta = THE_SPAN;
        span.longitudeDelta = THE_SPAN;
    
        myRegion.center = center;
        myRegion.span = span;
    
        // Set our mapview
        [mapView setRegion:myRegion animated: YES];
    
        // Annotation
        NSMutableArray *locations = [[NSMutableArray alloc] init];
        CLLocationCoordinate2D location;
        Hazards *myAnn;
    
        // Pin to show Royal Mail Ballymena delivery office
        myAnn = [[Hazards alloc] init];
        location.latitude = BALLYMENA_LATITUDE;
        location.longitude = BALLYMENA_LONGITUDE;
        myAnn.coordinate = location;
        myAnn.title = @"Royal Mail Ballymena";
        myAnn.subtitle = @"111, Railway Street";
        [locations addObject:myAnn];
    
        [self.mapView addAnnotations:locations];
    
        }
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    #pragma mark - Methods
    -(void) retrieveData {
        NSURL *url = [NSURL URLWithString:getDataURL];
        NSData *data = [NSData dataWithContentsOfURL:url];
    
        json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
    
        // Setup our hazards array
        hazardsArray = [[NSMutableArray alloc] init];
    
        for (int i =0; i < json.count; i++) {
    
            // Create hazard object
            NSString *hazardsID = [[json objectAtIndex:i] objectForKey:@"ID"];
            NSString *hazardsROUTE = [[json objectAtIndex:i] objectForKey:@"ROUTE"];
            NSString *hazardsADDRESS = [[json objectAtIndex:i] objectForKey:@"ADDRESS"];
            NSString *hazardsLATITUDE = [[json objectAtIndex:i] objectForKey:@"LATITUDE"];
            NSString *hazardsLONGITUDE = [[json objectAtIndex:i] objectForKey:@"LONGITUDE"];
            NSString *hazardsHAZARD = [[json objectAtIndex:i] objectForKey:@"HAZARD"];
            NSString *hazardsRISK = [[json objectAtIndex:i] objectForKey:@"RISK"];
    
            Hazards *myHazards = [[Hazards alloc] initWithID:hazardsID andROUTE:hazardsROUTE andADDRESS:hazardsADDRESS andLATITUDE:hazardsLATITUDE andLONGITUDE:hazardsLONGITUDE andHAZARD:hazardsHAZARD andRISK:hazardsRISK];
    
            // Add our hazards object to our hazards array
            [hazardsArray addObject:myHazards];
        }
        // [self.mapView addAnnotation:hazardsArray];
    }
    
    @end
    Many Thanks in advance
     
  2. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #2
    Thanks for the code. Can you explain what isn't working? Are you getting any warnings / errors / run-time crashes? Any other debugging info you can provide us?
     
  3. waterskier2007 macrumors 68000

    waterskier2007

    Joined:
    Jun 19, 2007
    Location:
    White Lake, MI
    #3
    At first glance it appears that the problem is that your Hazards class does not follow the protocol for MKAnnotation. In the documentation here it states that the class must implement the coordinate property. While yours has this as a property, there must be a method that returns this property so that the annotations can be placed on the map

    so in your hazards.m you might try something like this

    Code:
    -(CLLocationCoordinate2D)coordinate
    {
         return CLLocationCoordinate2DMake(self.LATITUDE, self.LONGITUDE)
    }
    
    You may also want to implement (again in hazards.m) but these are optional
    Code:
    -(NSString *)title
    {
         return self.ADDRESS
    }
    
    -(NSString *)subtitle
    {
         return self.ID
    }
    

    Also, according to the documentation I provided above, the coordinate property should actually be
    Code:
    @property (nonatomic, readonly) CLLocationCoordinate2D coordinate
    
     
  4. Futhark thread starter macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #4
    Thanks for the replies, I'll have a try fixing my code later and if i am still struggling i'll try and explain whats happening.

    Thanks Again
     
  5. Duncan C, Apr 23, 2013
    Last edited by a moderator: Apr 23, 2013

    Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #5
    Another thing I see:

    In your retrieveData method you build an array of hazard objects, but then you call addAnnotation instead of addAnnotations

    addAnnotation is for adding a single object that conforms to the MKAnnotation protocol. You should be using the method addAnnotations, which takes an array of annotation objects.


     
  6. Futhark thread starter macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #6
    I've updated my code with the information provided above but i'm getting an error message:

    Passing 'NSString *' to parameter of incompatible type 'CLLocationDegrees' (aka 'double')

    I'm not actually sure what this means? Going back to when i created my mysql database my latitude & longitude were stored as double then it was exported to JSON and then imported into my hazardsArray, just incase this has anything to do with the error?

    it's the line:

    return CLLocationCoordinate2DMake(self.LATITUDE, self.LONGITUDE); that throws up the error

    Code:
    -(CLLocationCoordinate2D)coordinate
    {
        return CLLocationCoordinate2DMake(self.LATITUDE, self.LONGITUDE);
    }
    
    -(NSString *)title
    {
        return self.HAZARD;
    }
    
    -(NSString *)subtitle
    {
        return self.ADDRESS;
    }
    
    @end
    
     
  7. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #7
    What type have you declared self.LATITUDE as being in your code? What about self.LONGITUDE? Check Apple's documentation for this type and see if you can find a method that will convert it to a double, which is what the arguments of CLLocationCoordinate2DMake() should be.

    (I know the answers and could just give you the code, but I'm trying to teach you how to read the messages Xcode gives you and how to read the relevant documentation to help you solve it.)
     
  8. Futhark thread starter macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #8
    I totally understand what you're saying here and really appreciate every ones help :) I'll have another attempt and see what i can come up with and if need be i'll come back to you for more hints hehehehe
     
  9. Futhark thread starter macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #9
    I think i've converted the LATITUDE & LONGITUDE into double's each called dLAT & dLONG but it say's they are unused variables? I thought it was just a matter of adding:

    return CLLocationCoordinate2DMake(self.dLAT, self.dLONG);

    but that didn't work :( am i close? :confused:

    Code:
    - (id) initWithID: (NSString *) hazardsID andROUTE: (NSString *) hazardsROUTE andADDRESS: (NSString *) hazardsADDRESS andLATITUDE: (NSString *) hazardsLATITUDE andLONGITUDE: (NSString *) hazardsLONGITUDE andHAZARD: (NSString *) hazardsHAZARD andRISK: (NSString *) hazardsRISK {
        
        self = [super init];
        if (self)
        {
            ID = hazardsID;
            ROUTE = hazardsROUTE;
            ADDRESS = hazardsADDRESS;
            LATITUDE = hazardsLATITUDE;
            LONGITUDE = hazardsLONGITUDE;
            HAZARD = hazardsHAZARD;
            RISK = hazardsRISK;
            
            double dLAT = [LATITUDE doubleValue];
            double dLONG = [LONGITUDE doubleValue];
        }
    return self;
    }
    
    
    -(CLLocationCoordinate2D)coordinate
    {
        return CLLocationCoordinate2DMake(self.dLAT, self.dLONG);
    }
    
    -(NSString *)title
    {
        return self.HAZARD;
    }
    
    -(NSString *)subtitle
    {
        return self.ADDRESS;
    }
    
    @end
    
     
  10. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #10
    Your problem is misunderstanding scoping. "self." references properties not local variables, especially not those from other methods. Your double dLAT and double dLONG are declared in your initWithID:. What would you say happens to them after your initWithID: finishes running?
     
  11. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #11
    By the way, as a general rule, using a string to store a double-precision floating point value is a bad idea. Double precision floats have like 18 decimal digits of precision, and string representations of numbers are usually limited to a handful of decimal digits.

    lat/long values need double precision to be placed accurately on the map.

    I would STRONGLY suggest that you change your custom annotation class to save it's lat/long pairs internally as doubles. If you are setting them from JSON data, convert the incoming strings to doubles and set the annotations values that way. Don't degrade your internal data types because your source data uses a less-than-ideal data format.
     
  12. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #12
    While I agree with your general point, strings should be less restrictive than doubles in the number of digits they could store, correct? Why would they be "usually limited"?
     
  13. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #13

    It boils down to how the format strings are defined and handled.

    The standard

    Code:
    NSString *aString = [NSString stringWithFormat: @"%f", doubleValue];
    
    Will only output a few decimal places.

    granted, you can use

    Code:
    NSString *aString = [NSString stringWithFormat: @"%.18f", doubleValue];
    
    Instead.

    Another issue is that native floating point data types like double and float do not correspond directly with their decimal equivalents.

    The value 0.1 (1/10) is a repeating "decimal" in binary, just like 1/3 is a repeating decimal, .3333333... in decimal notation.
     
  14. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #14
    The data is received in JSON, which means its going to be a string at some point no matter what, right? JSON is never anything besides plain text, am I right? Therefore, there's no option there.

    The data needs to be a double in the end for it to work with the maps API. Therefore there's no option there, either. There's no way to improve on this system. The data is received as a string and must be changed to a double before it can be used.
     
  15. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #15

    True. My point is that the native format for Apple's annotations is doubles. Better to convert the JSON data to Apple's native format than to store it in the input format.
     
  16. Futhark, Apr 23, 2013
    Last edited: Apr 24, 2013

    Futhark thread starter macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #16
    So is there a chance that when I change the data I imported from JSON into a NSMutableArray and then into a double could have changed knocking out my coordinates accuracy?

    I'm still unsure how to edit my code to fix this error anyway and some advice would be much appreciated as i was hoping to have the Annotations up and running today
     
  17. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #17
    There's a chance. But if you're using NSString's doubleValue method, you should be okay.

    What error, again? Did you fix your scoping issue yet?
     
  18. Futhark thread starter macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #18
    Dejo I'd be more than happy to zip my project and email it to you if you didn't mind? I don't think there's too much wrong it's just me new to programming and I'm confusing myself :)
     
  19. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #19
    Sorry, but I kind of do mind. I'm here, along with plenty of others, to provide guidance in a community setting. I'm afraid I can't be doing one-on-one development consultation.

    What warnings/errors are you getting?
     
  20. Futhark thread starter macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #20
    No problem I understand :)

    I'm just collecting my daughter from school so I'll post the errors as soon as I can.

    I appreciate all the help thanks
     
  21. Futhark, Apr 24, 2013
    Last edited: Apr 24, 2013

    Futhark thread starter macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #21
    Ok so here's the code that's giving me error messages, i know you've mentioned i need to convert my Latitude & Longitude NSString's to doubles but i'm really not sure how to implement this? Here's the section of code that needs looked at, Sorry for being a pain :eek:

    Code:
    #import "Hazards.h"
    
    @implementation Hazards
    @synthesize coordinate, title, subtitle, ID, ROUTE, ADDRESS, LATITUDE, LONGITUDE, HAZARD, RISK;
    
    
    - (id) initWithID: (NSString *) hazardsID andROUTE: (NSString *) hazardsROUTE andADDRESS: (NSString *) hazardsADDRESS andLATITUDE: (NSString *) hazardsLATITUDE andLONGITUDE: (NSString *) hazardsLONGITUDE andHAZARD: (NSString *) hazardsHAZARD andRISK: (NSString *) hazardsRISK {
        
        self = [super init];
        if (self)
        {
            ID = hazardsID;
            ROUTE = hazardsROUTE;
            ADDRESS = hazardsADDRESS;
            LATITUDE = hazardsLATITUDE;
            LONGITUDE = hazardsLONGITUDE;
            HAZARD = hazardsHAZARD;
            RISK = hazardsRISK;
                    
        }
    return self;
    }
    
      // Not sure how this code needs to be changed to make the
      // NSStrings LATITUDE & LONGITUDE turn into doubles
    
    NSString *aString = [NSString stringWithFormat: @"%f", doubleValue];
    
    -(CLLocationCoordinate2D)coordinate
    {
        return CLLocationCoordinate2DMake(self.LATITUDE, self.LONGITUDE);
    }
    
    -(NSString *)title
    {
        return self.HAZARD;
    }
    
    -(NSString *)subtitle
    {
        return self.ADDRESS;
    }
    
    @end
    Error Messages:

    Code:
    NSString *aString = [NSString stringWithFormat: @"%f", doubleValue];
    Use of undeclared identifier 'doubleValue'

    Code:
    return CLLocationCoordinate2DMake(self.LATITUDE, self.LONGITUDE);
    Passing 'NSString *' to parameter of incompatible type 'CLLocationDegrees' (aka 'double')
     
  22. dejo, Apr 24, 2013
    Last edited: Apr 24, 2013

    dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #22
    Well, putting code (your aString assignment) outside of a method is probably not what you intended.

    But, let's take a step back. Remove that line and comment out your coordinate method. As per Duncan C's suggestion, let's make your latitude and longitude (notice the case follows common Cocoa coding guidelines; appropriate adjustments to all other UPPER-CASED variables should be made as well) properties more appropriate types:
    Code:
    @property (nonatomic, assign) CLLocationDegrees latitude;
    @property (nonatomic, assign) CLLocationDegrees longitude;
    
    You'll then want to convert the lat/long strings that come in as parameters in your initWithID:. Any guesses as to how?
     
  23. Futhark thread starter macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #23

    An ABSOLUTE Guess :eek::eek::eek:

    Code:
    -(id) initWithLatitude: (double *) dLat  andLong: (double *) dLong;
     
  24. dejo, Apr 24, 2013
    Last edited: Apr 24, 2013

    dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #24
    I would leave your definition of initWithID:... alone (other than fixing the camel-case issue), since it's using the values pulled from your JSON. Instead I would convert the strings to double when doing the assignment within the method. And, remember, NSString has a doubleValue method (that you've seen before) to help with this.

    So, willing to propose what those two assignment lines would look like? Can you fill-in the blanks?

    Code:
    latitude = _____; // convert the string hazardsLatitude to a double here
    longitude = _____; // convert the string hazardsLongitude to a double here
     
  25. Futhark, Apr 24, 2013
    Last edited by a moderator: Apr 24, 2013

    Futhark thread starter macrumors 65816

    Futhark

    Joined:
    Jun 12, 2011
    Location:
    Northern Ireland
    #25
    I'm feeling so silly right now :eek:

    if i'm to go by what i've read above it's
    Code:
    NSString *aString = [NSString stringWithFormat: @"%f", doubleValue];
    PLEASE be right :D
     

Share This Page