0

I am quite new to Objective-C and this is the first time I have attempted to implement MVC. I have a model class where l have an NSArray which will be populated with data from a JSON object. I want to populate my UITableView (in my view controller class), with objects from this array.

Please review my code:

Droplets.h

@interface Droplets : NSObject {
    NSArray *dropletsArray;

}

// Get droplets data
- (void) getDropletsList;

//Object initilization
- (id) init;

//Public properties
@property (strong, nonatomic) NSArray *dropletsArray; // Used to store the selected JSON data objects

@end

Droplets.m

#define kBgQueue dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define kDigialOceanApiURL [NSURL URLWithString:@"http://inspiredwd.com/api-test.php"] //Droplets API call

#import "Droplets.h"

@interface Droplets ()

//Private Properties
@property (strong, nonatomic) NSMutableData *data; // Used to store all JSON data objects
@end


@implementation Droplets;

@synthesize dropletsArray;
@synthesize data;

- (id)init
{
    self = [super init];
    if (self) {

    }
    return self;
}

- (void) getDropletsList {

    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    NSURL *url = kDigialOceanApiURL; // Predefined Digital Ocean URL API http request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection connectionWithRequest:request delegate:self]; //Should be: [[NSURLConnection alloc]initiWithRequest:request delegate:self]; ...however the instance of NSURLConnection is never used, which results in an "entity unsed" error.
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {

    data = [[NSMutableData alloc]init]; // mutable data dictionary is allocated and initilized
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData {

    [data appendData:theData]; // append 'theData' to the mutable data dictionary
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {

    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

    //JSON foundation object returns JSON data from a foundation object. Assigned returned data to a dictionary 'json'.
    NSDictionary* jsonData = [NSJSONSerialization JSONObjectWithData:data
                                                             options:kNilOptions error:0];

    self.dropletsArray = [jsonData objectForKey:@"droplets"]; //dictionary of arrays
    NSLog(@"Droplets %@", self.dropletsArray);
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    // If the application is unable to connect to The Digital Ocean Server, then display an UIAlertView
    UIAlertView *errorView = [[UIAlertView alloc]initWithTitle:@"Error" message:@"Unable to connect to The Digital Ocean Server, please ensure that you are connected via either WIFI or 3G." delegate:nil cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];

    [errorView show];

    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; // Turn of the network activity indicator
}

@end

DropletsList.h

    @class Droplets;

    @interface DropletsList : UITableViewController 

    - (Droplets *) modelDroplets;

    @end

DropletsList.m

    #define RGB(r, g, b) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1]

    @interface DropletsList ()

    //Private properties
    @property (strong, nonatomic) Droplets *modelDroplets;
    @property (strong, nonatomic) NSArray *tableData;

    @end

@implementation DropletsList

@synthesize tableData;

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
        NSLog(@"get my data from model");
    }
    return self;
}

- (Droplets *) modelDroplets
{
    if (!_modelDroplets) _modelDroplets = [[Droplets alloc]init];
    return _modelDroplets;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    _modelDroplets = [[Droplets alloc]init];
    self.tableData = [_modelDroplets dropletsArray];
    [_modelDroplets getDropletsList];

    [self.tableView reloadData]; // reload the droplets table controller

}


- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source


- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView {

    return 1; // Return the number of sections.

}


- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {

    return [_modelDroplets.dropletsArray count]; // Return the number of rows in the section.
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // The cell identified by "dropletsList", is assiged as the UITableViewCell
    UITableViewCell *cell = [tableView
                             dequeueReusableCellWithIdentifier:@"dropletsList"];

    //NSLog(@"Droplets Name: %@",self.dropletsArray);

    // The UITableView text label is assigned the contents from 'dropletsArray', with the object key "name"- name of the droplet
    cell.textLabel.text=[[tableData objectAtIndex:indexPath.row]objectForKey:@"name"];

    // The UITableView text detail label is assigned the contents from 'dropletsArray', with the object key "status"- status of the droplet
    cell.detailTextLabel.text=[[tableData objectAtIndex:indexPath.row]objectForKey:@"status"];

    //Evalulate the status of each droplet, setting the colour appropriate to the staus
    if ([[[tableData objectAtIndex:indexPath.row] objectForKey:@"status"] isEqualToString:@"active"]) {

        //Set the detail text label colour
        cell.detailTextLabel.textColor = RGB (35,179,0);
    }
    return cell;

}

@end

Basically my table doesn't populate. Please could someone help?

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • Are the methods numberOfSectionsInTableView, numberOfRowsInSection and cellForRowAtIndexPath being called? – The dude May 01 '13 at 09:07
  • Hi The Dude, to answer your question, yes the table view is being called from a UIButton. In fact l had this app originally working, but the NSRUL connection delegate methods where all located within my ViewController 'DropletsList'. I then broke out the model into a separate class. – Greg J Arden May 01 '13 at 11:42
  • I think you should follow Anupdas advice, it seems it has to do with network connection. – The dude May 01 '13 at 12:23

1 Answers1

0
- (void)viewDidLoad
{
    [super viewDidLoad];
    _modelDroplets = [[Droplets alloc]init];
    self.tableData = [_modelDroplets dropletsArray];
    [_modelDroplets getDropletsList];

    [self.tableView reloadData]; // reload the droplets table controller

}

In this method you are fetching droplets from a webservice. It is asynchronous, by the time tableView reloads the data it might not have completed fetching the data. You need to have a callback which will reload the tableView on completion of webservice.

EDIT :

Create a class method in Droplets to fetch all data

//Droplets.h
typedef void (^NSArrayBlock)(NSArray * array);
typedef void (^NSErrorBlock)(NSError * error);

//Droplets.m
+ (void)getDropletsWithCompletion:(NSArrayBlock)arrayBlock onError:(NSErrorBlock)errorBlock
{
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:kDigialOceanApiURL];
    [urlRequest setHTTPMethod:@"GET"];
    [urlRequest setCachePolicy:NSURLCacheStorageNotAllowed];
    [urlRequest setTimeoutInterval:30.0f];
    [urlRequest addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    [NSURLConnection sendAsynchronousRequest:urlRequest
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {

                               if (error) {
                                   errorBlock(error);
                               }else{
                                   NSError *serializationError = nil;
                                   NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData
                                                                                        options:NSJSONReadingAllowFragments
                                                                                          error:&serializationError];

                                   arrayBlock(json[@"droplets"]);
                               }

                           }];

}

//DropletsList.h

- (void)viewDidLoad
{
    [super viewDidLoad];

    [Droplets getDropletsWithCompletion:^(NSArray *array) {
          self.modelDroplets = droplets;
          [self.tableView reloadData];
     } onError:^(NSError *error) {

           UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Error" message:error.localizedDescription delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
           [alert show];
    }];

}

Disclaimer : Tested and verified :)

Anupdas
  • 10,211
  • 2
  • 35
  • 60
  • Hi Anupdas, thank you for your comments, could you give me an example of a 'callback', in relation to my classes? – Greg J Arden May 01 '13 at 11:38
  • @user1992953 I have edited my answer. It will get you started. – Anupdas May 01 '13 at 12:50
  • Anupdas, you are a star! So just to clarify, obviously your example used a single class method, therefore if l wanted to conform to the NSURLConnectionDelegate, what would l need to do differently? – Greg J Arden May 01 '13 at 15:57
  • @user1992953 Nice to hear that. If you are using NSURLConnectionDelegate, you will have to make a delegate of Droplet and the controller which needs to be triggered on completion of event should implement the delegate. It is more effort than it's worth. You will make it more complicated in the process. – Anupdas May 01 '13 at 16:18
  • @user1992953 Any follow up on the issue ? – Anupdas May 03 '13 at 05:40
  • I've started to read up about blocks, what l've read thus far makes what l have been trying to do so much easier! Thank you again! – Greg J Arden May 03 '13 at 07:57
  • @user1992953 I have put some effort to give you suggestions, be kind enough to accept or upvote if you find it useful so that others looking for answers seem this answer as helpful. – Anupdas May 03 '13 at 08:01
  • I just tested the app without a connection, and obviously prior to implementing the block l had a delegate method for no response, which displayed a UIAlertView. How would l best implement a this? – Greg J Arden May 03 '13 at 08:28
  • @user1992953 I gave a very simple implementation to you, you can see that in the completionHandler of NSURLConnection you can see that there is an error value there, you can provide that also to your completionBlock. I will update my answer with that. – Anupdas May 03 '13 at 08:44
  • Hi Anupdas, sorry for the delay, l have been out of action for a while. Your explanation was spot on! However, l have started to prepare to write some code to go in the other direction, based on your suggestions- pass a string from UIText to my model and NSURL write...how would you best approach this? Segue to my model class? – Greg J Arden Sep 19 '13 at 14:11
  • @user1992953 Can you explain in detail, didn't get any idea from what you said ! – Anupdas Sep 19 '13 at 14:28
  • Sorry, l need to take input from a UITextField on one view and write the contents into a URL string, as in your previous answer- get DropletsWithCompletion method – Greg J Arden Sep 19 '13 at 17:18
  • @user1992953 The simplest way would be include one more parameter to the method making the webservice call. For e.g. `getDropletsForParams:(NSDictionary *)params withCompletion:completionBlock onError:errorBlock` You can write any logic to create urls from strings in it. – Anupdas Sep 20 '13 at 04:48
  • Thank you for your response, however the way l have structured my MVC, is each API call to the server is managed in a separate model class; Here is the write webservice call: https://api.digitalocean.com/droplets/new?name=[DROPLET_NAME]&size_id=[SIZE_ID]&image_id=[IMAGE_ID]&region_id=[REGION_ID]&client_id=[YOUR_CLIENT_ID]&ssh_key_ids=[SSH_KEY_ID1],[SSH_KEY_ID2]. Each of the elements will come from the UITextField in the UI. So l am assuming that l would write these as a string to a public property on my model, then some how filter into the elements of my API call? – Greg J Arden Sep 20 '13 at 09:36
  • @GregJArden As I mentioned earlier, you can use a dictionary of params you just need to create an encoded string out of it, you can enumerate through the dictionary and create the string in the above mentioned format. – Anupdas Sep 20 '13 at 13:56
  • Thank you. I can visualise what you have suggested, but l have never done this, so l will have to go off and do a little reading. Can you suggest any good sites, with examples of this type coding? – Greg J Arden Sep 20 '13 at 15:24
  • @GregJArden This link will provide you an idea [Creating URL query from NSDictionary](http://stackoverflow.com/q/718429/767730) – Anupdas Sep 21 '13 at 00:02
  • Hello Anupdas, l kind of understand, but l think l need to understand the basics first, so let me deconstruct a little first. – Greg J Arden Nov 14 '13 at 15:56
  • ...Step 1: pass the values entered in the view (assuming that l need to initialise the model object first and call a method), then set the UITextField string entered in the view to an NSDictionary (in the model object) with keys and values- firstName, secondName, etc..., Step 2: Write a class method (in the same model class)to format the keys and values as defined by the 3rd party API, Step 3: Write to the NSURL URL address defined...l think this is the right approach, but l haven't done anything like this before. Should l start a new thread do you think? Many thanks, Greg – Greg J Arden Nov 14 '13 at 16:06