Question

I have tableview where is name and status. Status is changed when come apple push notification (APNS). But I have this problem. What can I do, if notification didn't come? Or if user tap on close button of this message. I try to update table by using ASIHTTPRequest:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
    static NSString *CellIdentifier = @"Cell";  
    HomePageTableCell *cell = (HomePageTableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    // Configure the cell...
    NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
    cell.nameLabel.text = [device valueForKey:@"name"];

    if ([[device valueForKey:@"status"] isEqualToNumber:@1]) 
    {
        cell.status.text = @"Not configured";
        cell.stav.image = [UIImage imageNamed:@"not_configured.png"];
    }
    if ([[device valueForKey:@"status"] isEqualToNumber:@2]) 
    {
        //some other states
    }

    return cell;
}

I try this to change status before cell is loading...

- (void) getStatus:(NSString *)serialNumber
{
    NSURL *url = [NSURL URLWithString:@"link to my server"];

    __block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
    __weak ASIHTTPRequest *request_b = request;
    request.delegate = self;

    [request setPostValue:@"updatedevice" forKey:@"cmd"];
    [request setPostValue:serialNumber forKey:@"serial_number"]; //get status of this serial number

    [request setCompletionBlock:^
     {
         if([self isViewLoaded])
         {
             [MBProgressHUD hideHUDForView:self.view animated:YES];
             if([request_b responseStatusCode] != 200)
             {
                 ShowErrorAlert(@"Comunication error", @"There was an error communicating with the server");                     
             }
             else
             {                                      
                 NSString *responseString = [request_b responseString];
                 SBJsonParser *parser = [[SBJsonParser alloc] init];
                 NSDictionary *result = [parser objectWithString:responseString error:nil];

                 status = [result objectForKey:@"status"];
                 NSInteger statusInt = [status intValue]; //change to int value

                 //here I want to change cell status in SQLite, but don't know how
                 //something with indexPath.row? valueForKey:@"status"???                
             }
         }
     }];
    [request setFailedBlock:^
     {
         if ([self isViewLoaded])
         {
             [MBProgressHUD hideHUDForView:self.view animated:YES];
             ShowErrorAlert(@"Error", [[request_b error] localizedDescription]);

         }
     }];

    [request startAsynchronous];
}

Or it is better way to change status in my table view if apple notification didn't come or user didn't tap on notification message? Thanks

EDIT: I don't know how to store data to NSManagedObject *device. Can you help me with this? I try this, but it didn't works: (on place where you write)

NSInteger statusInt = [status intValue]; //change to int value
NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
[device setValue:statusInt forKey:@"status"];

EDIT2: I get it, but problem is with reload table data

 NSString *responseString = [request_b responseString];
 SBJsonParser *parser = [[SBJsonParser alloc] init];
 NSDictionary *result = [parser objectWithString:responseString error:nil];

 NSString *status = [result objectForKey:@"status"];
 NSInteger statusInt = [status intValue]; //change to int value
 NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
 [device setValue:statusInt forKey:@"status"]; //there is problem in save statusInt

// [device setValue:@5 forKey:@"status"]; //if I do this it is ok status is integer16 type

and second problem is in that reload table data. I put there this

[self.tableView reloadData]

but It reloading again and again in loop, what is wrong? I thing there is infinite loop, if I didn't reload table data changes will be visible in next app load. I think problem is that I call

- (void) getStatus:(NSString *)serialNumber atIndexPath:(NSIndexPath *)indexPath
{}

in

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
}

Better should be in viewDidLoad or viewDidApper, but I don't know how make loop for all devices and call

    [self getStatus:[device valueForKey:@"serialNumber"] atIndexPath:indexPath];

on that place.

EDIT3:

what if I do it like this:

- (void)viewDidLoad
{
    [self updateData];
    [self.tableView reloadData];
}
-(void)updateData 
{
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Device"];
    request.returnsDistinctResults = YES;
    //request.resultType = NSDictionaryResultType;
    request.propertiesToFetch = @[@"serialNumber"];

    NSArray *fetchedObjects = [self.managedObjectContext
                               executeFetchRequest:request error:nil];

    NSArray *result = [fetchedObjects valueForKeyPath:@"serialNumber"]; 

    //there I get all serialNumbers of my devices and than I call method getStatus and get "new" status and than update it in Core Data. 
}

Is that good way to solve this problem? I think better will be if I call getStatus method only one times and get array of statuses.

Maybe I can set all serialNubers in one variable ('xxx','yyyy','zzz') and on server do SELECT * FROM Devices WHERE serialNumber in (serialNuber).

Do you think this could work? I don't have experience how to take data from array to string like ('array_part1','array_part2'....)

Was it helpful?

Solution

Where in your code do you call [UITableView reloadData]?

You should call reloadData on your tableview once you have retrieved the new data from the server. As your server call is async the server call will run on a separate thread while the main thread continues, therefore I presume you have the following problem...

- (void) ...
{
    [self getStatus:@"SERIAL_NUMBER"];
    [self reloadData]; // This will be called before the async server call above has finished
}

Therefore you are reloading the original data and therefore the new data, which may have loaded a few seconds after, wont be shown.

To fix this, adjust the [getStatus:] method to call the [UITableView reloadData] method on server response.

[request setCompletionBlock:^
 {
     if([self isViewLoaded])
     {
         [MBProgressHUD hideHUDForView:self.view animated:YES];
         if([request_b responseStatusCode] != 200)
         {
             ShowErrorAlert(@"Comunication error", @"There was an error communicating with the server");

         }
         else
         {                 

             NSString *responseString = [request_b responseString];
             SBJsonParser *parser = [[SBJsonParser alloc] init];
             NSDictionary *result = [parser objectWithString:responseString error:nil];

             status = [result objectForKey:@"status"];
             NSInteger statusInt = [status intValue]; //change to int value

             // Store the server response in NSManagedObject *device,
             // which will be used as the data source in the tableView:cellForRowAtIndexPath: method

             // Once stored, check the tableview isn't NULL and therefore can be accessed
             // As this call is async the tableview may have been removed and therefore
             // a call to it will crash
             if(tableView != NULL)
             {
                    [tableView reloadData];
             }           
         }
     }
 }];

ASIHTTPRequest is also no longer supported by the developers, I suggest you look into AFNetworking.


Update

In response to the problem you are now having with setting the statusInt within the device NSManagedObject

 NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
 [device setValue:statusInt forKey:@"status"]; //there is problem in save statusInt

This is caused as statusInt is an NSInteger which is a primary datatype and not an NSObject as expected by [NSManagedObject setValue:forKey:]. From the documentation for [NSManagedObject setValue:forKey:], the methods expected parameters are as follows.

- (void)setValue:(id)value forKey:(NSString *)key

Therefore you need to pass, in this case, an NSNumber. The problem with NSInteger is that it's simply a dynamic typedef for the largest int datatype based on the current system. From NSInteger's implementation you can see the abstraction.

#if __LP64__
typedef long NSInteger;
#else
typedef int NSInteger;
#endif

If your current system is 64-bit it will use the larger long datatype.

Now, technically the returned status value from the server can be stored as it is without any conversion as an NSString. When you need to retrieve and use the primary datatype of int you can use the [NSString intValue] method you have already used.

Although it's best practice to use a NSNumberFormatter which can be useful for locale based number adjustments and ensuring no invalid characters are present.

NSString *status = [result objectForKey:@"status"];
NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
NSNumber * statusNumber = [f numberFromString:status];
NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
[device setValue:statusNumber forKey:@"status"];

To retrieve the primary datatype when you wish to use the int within your code, simply call the [NSNumber intValue].

NSNumber *statusNumber = [device objectForKey:@"status"];
int statusInt = [statusNumber intValue];

As for the problem you are having with the infinite loop, this is caused by called [... getStatus:atIndexPath:], which contains the method call reloadData, from within [UITableView tableView:cellForRowAtIndexPath:].

This is because reloadData actually calls [UITableView tableView:cellForRowAtIndexPath:]. Therefore your code continuously goes as the following...

Initial UITableView data load -> tableView:cellForRowAtIndexPath: -> getStatus:atIndexPath: -> Server Response -> reloadData -> tableView:cellForRowAtIndexPath: -> getStatus:atIndexPath: -> Server Response -> reloadData -> ...

Unfortunately you cant just force one cell to update, you have to request the UITableView to reload all data using reloadData. Therefore, if possible, you need to adjust your server to return an unique ID for devices so you can adjust only the updated device within your NSManagedObject.

A suggested alteration for the getStatus method could be just to use the serialNumber if this is stored within the NSManagedObject as a key.

- (void) getStatus:(NSString*)serialNumber
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top