1

Im still trying to get on to the ios development but im hoping you can help me.

Currently I have a WCF that returns some json data in the format of

"Address": "3453453",
"Category": "CONCRETE",
"Closest_Property_Number": 2,
"ID": 42,
"Image1": 324,
"Image2": 0,
"Image3": 0,
"Latitude": 2,
"Longitude": "-6.541902",
"Notes": "GHTFHRG",
"User_ID": 2

I then created a class called Location here is the Location.m

#import "Location.h"

@implementation Location {
    NSString* _address;
    NSString* _category;
    NSString* _closest_Property_Number;
    NSString* _iD;
    NSString* _image1;
    NSString* _latitude;
    NSString* _longitude;
    NSString* _notes;
    NSString* _user_ID;
}

@synthesize address = _address;
@synthesize category = _category;
@synthesize closest_Property_Number = _closest_Property_Number;
@synthesize iD = _iD;
@synthesize image1 = _image1;
@synthesize latitude = _latitude;
@synthesize longitude = _longitude;
@synthesize notes = _notes;
@synthesize user_ID = _user_ID;

@end

I think this is right so far? Here is my class where all the importing happens

#import "Location.h"

    - (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.


 NSString *urlAsString = @"http://crm.fpmccann.co.uk/TemperatureWebService/iphonewebservice.svc/retrievelocations";
 NSURL *url = [NSURL URLWithString:urlAsString];
 NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

 [NSURLConnection
  sendAsynchronousRequest:urlRequest
  queue:[[NSOperationQueue alloc] init]
  completionHandler:^(NSURLResponse *response,
                      NSData *data,
                      NSError *error)

  {

      if ([data length] >0 && error == nil)
      {
          NSMutableArray* tmpLocations = [[NSMutableArray alloc] init];
          for (NSDictionary* loc in locations) {
              Location* location = [[Location alloc] initWithParameters:loc];
              [tmpLocations addObject:location];
          }

          NSMutableArray* tmpAnnotations;
          for (NSDictionary* location in tmpLocations)
          {
              // retrieve latitude and longitude from location
              MKPointAnnotation* annotation = [[MKPointAnnotation alloc] init];
              annotation.title = location.address;
              newAnnotation.coordinate = location;
              [tmpAnnotations addObject:annotation];
          }

          dispatch_async(dispatch_get_main_queue(), ^{
              self.locations = tmpLocations;
              self.annotations = tmpAnnotations;
              [self.mapView reloadInputViews];


          });
      }
      else if ([data length] == 0 && error == nil)
      {
          NSLog(@"Nothing was downloaded.");
      }
      else if (error != nil){
          NSLog(@"Error = %@", error);
      }

  }];


}

Here is where i am having problems, I want to show an annotation on a UImapview using the information from the json data. Please see the errors i am having in this part of the code below, commented on the line that they are happening

if ([data length] >0 && error == nil)
  {
      NSMutableArray* tmpLocations = [[NSMutableArray alloc] init];
      for (NSDictionary* loc in locations) {  //receiving error use of undeclared identifier 'locations', did you mean 'Location'
          Location* location = [[Location alloc] initWithParameters:loc];
          [tmpLocations addObject:location];
      }

      NSMutableArray* tmpAnnotations;
      for (NSDictionary* location in tmpLocations)
      {
          // retrieve latitude and longitude from location
          MKPointAnnotation* annotation = [[MKPointAnnotation alloc] init];
          annotation.title = location.address; // receiving error Property 'address' not found on object of type 'NSDictionary'
          newAnnotation.coordinate = location; // receiving error use of undeclared identifier 'newAnnotation'
          [tmpAnnotations addObject:annotation];
      }

      dispatch_async(dispatch_get_main_queue(), ^{
          self.locations = tmpLocations;  /// receiving error Property 'locations' not found on object of type 'MapViewController'
          self.annotations = tmpAnnotations; /// receiving error Property 'annotations' not found on object of type 'MapViewController'
          [self.mapView reloadInputViews];


      });
  }

And here is my MapViewController.h

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>

@interface MapViewController : UIViewController <MKMapViewDelegate>

@property (nonatomic, retain) IBOutlet MKMapView *mapView;

- (IBAction)refreshTapped:(id)sender;

@end
Karl
  • 45
  • 7

1 Answers1

1

You should make a few improvements to your code:

First, it's crucial to conform to the "naming conventions" in Objective-C. Properties, should start with a lowercase letter. For example:

@property (nonatomic, copy) NSString* address;

Properties of type NSString should have a "copy" attribute (the exception is managed objects).

Almost always the name of a class should be in singular form, that is instead of

@class LocationResults;

I would suggest to name it

@class Location;

The preferred way to declare ivars is in the implementation. So, instead of declaring ivars in the interface

In file Location.h

@interface Location : NSObject{
    NSString* address;
}

declare them as shown below:

@interface Location : NSObject
  ...  // public properties and methods
@end

In file Location.m:

@implementation Location {
    NSString* _address;
}
@synthesize address = _address;

Note:

clang supports "auto-synthesized" properties, which let you omit the ivar declaration and the @synthesize directive.


Now, regarding your code in viewDidLoad:

You seem to load a resource from a remote server:

NSData *data = [NSData dataWithContentsOfURL:url];

This is not an appropriate way to load resources from a remote server: it's an synchronous method which merely uses the thread to wait for something happen in the future (a response from the underlying network code).

The underlying network code internally dispatches its work onto internal private threads.

The effect is, you are wasting system resources when you just use a thread which gets blocked anyway for doing nothing. And - even more importantly - since you are calling this method in your main thread you are blocking the main thread and thus blocking UIKit display updates and other UIKit tasks.

Furthermore, networks request may fail in countless ways. The method dataWithContentsOfURL: cannot return reasonable error information.

These are just the most obvious caveats - but rest assured, there are more!

So, when accessing remote resources, generally use NSURLConnection or NSURLSession (or a third party library which utilizes these under the hood). In a first viable approach use the asynchronous class method:

+ (void)sendAsynchronousRequest:(NSURLRequest *)request 
                          queue:(NSOperationQueue *)queue 
              completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler;

While this method is better, it sill has a number of caveats: no way to cancel, no way to tailor authentication, no way to customize anything.

There a bunch of questions and answers how to use sendAsynchronousRequest:queue:completionHandler: on SO. Here are a few related questions and answers:

How to return an UIImage type from a GCD

NSData dataWithContentsOfURL: not returning data for URL that shows in browser

ERROR happened while deserializing the JSON data

As a rule of thumb, always check return values and if an error output parameter is given, provide an NSError object.

In case of sendAsynchronousRequest:queue:completionHandler: you should also check for the status code of the HTTP response and the Content-Type and confirm that you actually get what you requested and what you expect.


Having that said, you would populate your array of Locations as follows:

In the completion handler of sendAsynchronousRequest:queue:completionHandler: you would, first check the error and status code if that matches your expectations. IFF this is true, you have obtained a NSData object containing your JSON, then within the completion handler you implement this code:

NSError* error;
NSArray* locations = [NSJSONSerialization JSONObjectWithData:data
                                                     options:0
                                                       error:&error];
if (locations != nil)
{
    NSMutableArray* tmpLocations = [[NSMutableArray alloc] init];
    for (NSDictionary* loc in locations) {
        Location* location = [[Location alloc] initWithParameters:loc];
        [tmpLocations addObject:location];
    }

    NSMutableArray* tmpAnnotations;
    for (NSDictionary* location in tmpLocations)
    {
        // retrieve latitude and longitude from location
        MKPointAnnotation* annotation = [[MKPointAnnotation alloc] init];
        annotation.title = location.address;
        annotation.coordinate = ...
        [tmpAnnotations addObject:annotation];
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        self.locations = tmpLocations;
        self.annotations = tmpAnnotations;
        [self.tableView reloadData];
     });
}
else {
    // handle error
    ....
}

Note: The actual implementation depends on you more specific requirements. This implementation is merely an example how to solve a such a problem.

The method initWithParameters: should be straight forward.

Community
  • 1
  • 1
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • Hmm, first of all thank you for the very detailed response. Could you help me through a few things if you dont mind? Could you open a chat or discussion to help me? – Karl Nov 12 '13 at 12:53
  • Please see the changes to the .m and .h files? Is this what you are asking? – Karl Nov 12 '13 at 13:20
  • Almost :) You should delete the ivars from the `@interface` completely. The `@interface` remains in the interface file, though. I've made this more clear in my edited answer, please check. Also, keep track of the filenames, when you change the class name. – CouchDeveloper Nov 12 '13 at 13:25
  • So thats them wee changes made, Im nearly sure they are correct. Next up is the + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler; Can you help me on this, im very unsure of what to do with it. – Karl Nov 12 '13 at 14:31
  • please take a look at this example: http://stackoverflow.com/questions/18328379/how-to-return-an-uiimage-type-from-a-gcd/18329253#18329253 – CouchDeveloper Nov 12 '13 at 14:32
  • Sorry about all this, I have edited my question, Could you please have a look at the viewDidLoad method again and point me in the direction on where to go from here. Thank you again – Karl Nov 12 '13 at 15:04
  • Updated again, the bottom bit shows where i am having problems these are commented at the lines – Karl Nov 12 '13 at 16:09
  • 1. the annotation's property `coordinate` must be assigned a variable of type `CLLocationCoordinate2D` which is a struct. You need to somehow create this from your `location` object (longitude, latitude). 2. You should check the status code of the response, and whether its "Content-Type" equals "application/json". Otherwise, it seem so far OK for starting a debugging session to see whats going on. 3. `locations` is an array created from the JSON you received. 4. You need to properly implement the init method for class `Location` which gets initialized from the array `locations`. – CouchDeveloper Nov 12 '13 at 16:16
  • Hint: you should have a property in your Controller for your JSON representation, that is the NSArray which you get from parsing the JSON. I've used `self.locations` in may sample. Likewise, you may need a property for the NSArray of MKPointAnnotations. I've used `self.annotations` for this. You may name these differently. And it is also totally up to your needs. – CouchDeveloper Nov 12 '13 at 16:32