Help, before I blow my brains out


#1

I’ve created a custom UITableViewCell class (SyncTableViewCell) and associated xib for it. I have also created a model (DataSyncController) for managing the underlying data for these cells. In my view controller I create and load the cells and link them to their data objects. I am using the NSNotificationCenter in my DataSyncController to post notes when various data changes in the object. I observe these notes in my SyncTableViewCell(s) and update the controls accordingly when the underlying data changes.

This is working quite well for me in most cases, but I have two issues. Here is the code in my model object, DataSyncController:

- (void)syncParts
{
    WebService *service = [[WebService alloc] init];
    SQLiteDatabase *database = [SQLiteDatabase database];
    
    NSDate *startTime = [NSDate date];
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerEnabledActivity" object:self userInfo:nil];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerSyncActive" object:self userInfo:nil];

    if (!lastSyncDate) { [self setLastSyncDate:[[database getLastPartSyncDate]stringByReplacingOccurrencesOfString:@" " withString:@"+"]]; }
    if (!lastSyncDate) { lastSyncDate = @"1753-01-01"; }
    
    currentStep = @"Connecting to Web Service..";
    [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerUpdatedStep" object:self userInfo:nil];
    
    [service getPartsSinceDate:lastSyncDate
           withResponseHandler:^(NSDictionary *requestResponse) {
               MIMEtype = [NSString stringWithFormat:@"%@", [requestResponse valueForKey:@"MIMEType"]];
               textEncoding = [NSString stringWithFormat:@"%@", [requestResponse valueForKey:@"textEncodingName"]];
               fileName = [NSString stringWithFormat:@"%@", [requestResponse valueForKey:@"suggestedFilename"]];
               dataSize = [NSNumber numberWithLongLong:(long long)[requestResponse valueForKey:@"expectedContentLength"]];
               currentStep = [NSString stringWithFormat:@"Fetching %@ bytes of data from Web Service; last sync: %@", dataSize, lastSyncDate];
               [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerUpdatedStep" object:self userInfo:nil];
           }
       withReceivedDataHandler:^(NSNumber *requestDataLength) {
           if (dataSize > 0) {
               progress = [NSNumber numberWithFloat:([requestDataLength floatValue] / [dataSize floatValue])];
               NSLog(@"%f", [requestDataLength floatValue]);
               [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerUpdatedProgress" object:self userInfo:nil];
           }
       }
         withCompletionHandler:^(NSArray *JSON) {
             currentStep = [NSString stringWithFormat:@"Fetched %@ bytes of data in %f seconds; parsing JSON and modeling Parts..", [self progress], [[[NSDate alloc] init] timeIntervalSinceDate:startTime]];
             [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerUpdatedStep" object:self userInfo:nil];
             
             NSDate *startTime = [NSDate date];
             
             NSMutableArray *partsArray = [[NSMutableArray alloc] init];
             
             for (NSDictionary *dic in JSON) {
                 Part *currentPart = [[Part alloc] initWithPartNumber:[dic objectForKey:@"P"]
                                                       andDescription:[dic objectForKey:@"D"]
                                                            andWeight:[dic objectForKey:@"W"]
                                                     andPriceCategory:[dic objectForKey:@"C"]
                                                         andListPrice:[dic objectForKey:@"L"]];
                 
                 [partsArray addObject:currentPart];
             }
             
             currentStep = [NSString stringWithFormat:@"JSON parsing and Parts modeling took %f seconds; performing database inserts..", [[[NSDate alloc] init] timeIntervalSinceDate:startTime]];
             [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerUpdatedStep" object:self userInfo:nil];
             
             startTime = [NSDate date];
             
             NSString *SQLerror = [database insertPartsInBulk:partsArray];
             
             if (SQLerror) {
                 progress = 0;
                 [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerUpdatedProgress" object:self userInfo:nil];
                 currentStep = [NSString stringWithFormat:@"Error: %@", SQLerror];
                 [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerDisabledActivity" object:self userInfo:nil];
                 [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerSyncError" object:self userInfo:nil];
             }
             else {
                 if ([partsArray count] == 0) {
                     progress = 0;
                     [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerUpdatedProgress" object:self userInfo:nil];
                     currentStep = [NSString stringWithString:@"Data up-to-date!"];
                 }
                 else {
                     currentStep = [NSString stringWithFormat:@"Inserted %i Parts into database in %f seconds; sync' complete!", [partsArray count], [[[NSDate alloc] init] timeIntervalSinceDate:startTime]];
                 }
                 [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerDisabledActivity" object:self userInfo:nil];
                 [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerSyncComplete" object:self userInfo:nil];
             }
             [[NSNotificationCenter defaultCenter] postNotificationName:@"DataSyncControllerUpdatedStep" object:self userInfo:nil];
             
         }
     ];
}

The first issue is with UILabels on the cell getting updated. In all cases, when you see a ‘currentStep = @""’ followed by a “DataSyncControllerUpdatedStep” notification being posted, this code fires in my SyncTableViewCell:

- (void)updateCurrentStep:(NSNotification *)note
{
    NSLog(@"Observed Update Current Step: %@", [dataSource currentStep]);
    [[self currentStep] setText:[dataSource currentStep]];
}

Every time the notification is posted, I successfully observe it and see the NSLog output. The issue is, that the label is not being updated every time. (or maybe not being redrawn so I can see it…?)

Looking back at the - (void)syncParts code block, you will notice three block handlers being processed. I set “currentStep” and send the notification once prior to the blocks. This update shows successfully on the label. Again, in “withResponseHandler:”, I set “currentStep” and send the notification; again, the update shows successfully on the label. Finally, my problem arises in “withCompletionHandler:”. In this block, only the last update actually shows on the label. The first two just never actually update the label. Again, the NSLog statements show the correct text and that the update is being fired.

What am I missing?

My second, similar issue is with this block of code:

- (void)setIconToActive:(NSNotification *)note
{
    NSLog(@"Animating");
    iconState = 1;
    //[icon setImage:[UIImage imageNamed:@"sync_active_0"]];
    NSArray *images = [NSArray arrayWithObjects:
                       [UIImage imageNamed:@"sync_active_0.png"],
                       [UIImage imageNamed:@"sync_active_1.png"],
                       [UIImage imageNamed:@"sync_active_2.png"],
                       [UIImage imageNamed:@"sync_active_3.png"],
                       [UIImage imageNamed:@"sync_active_4.png"],
                       [UIImage imageNamed:@"sync_active_5.png"],
                       [UIImage imageNamed:@"sync_active_6.png"],
                       [UIImage imageNamed:@"sync_active_7.png"],
                       [UIImage imageNamed:@"sync_active_8.png"],
                       [UIImage imageNamed:@"sync_active_9.png"], nil];
    [icon setAnimationImages:images];
    [icon setAnimationDuration:1];
    [icon setAnimationRepeatCount:0];
    [icon startAnimating];
    if ([icon isAnimating]) { NSLog(@"It IS Animating"); }
}

I am having the same issue here. The UIImageView (icon) does not change (animate) in the cell. Curiously, if I uncomment “[icon setImage:[UIImage imageNamed:@“sync_active_0”]];”, that does successfully update the UIImageView and it is visible in the cell.

Also, if you see anything glaringly asinine, please let me know. This is my first App.


#2

After updating data from which a cell is drawn, consider calling the tableView’s -reloadRowsAtIndexPaths: method (passing in an array containing the single cell’s indexPath object), and picking one of the available UITableViewRowAnimation constants. You’ll probably get the best visual results from a fade animation or no animation at all.


#3

That was my original approach, but, I didn’t like that it reloaded the entire cells contents to it’s defaults from the xib every time, causing me to have to set every control’s contents every time. Also, doing that was causing my animation on the icon to be “reset” every time, which looked really kludgy.

Is there a way to “redraw”, but not “reload”?


#4

Why are you broadcasting so many notifications? If there’s only a single recipient, you should probably use a delegate, instead. I note in particular that you ignore [note object] in your SyncTableViewCell callback and reach directly for self->dataSource. With a relationship that cozy, notifications are just obfuscating the code.

Don’t guess here: find out. You can set a breakpoint on the relevant drawing methods and see whether it fires. Other good things to check are assumptions like “do I have a valid reference to a label?”, “am I in fact the cell on screen at that location?”, “where am I in the view hierarchy?”

NSLog and your debugger can answer all of these questions and more. In general, when you’re having problems and don’t understand why, stop the world using a breakpoint and get a good look at everything in your app at that point. Check your assumptions. Single-step a bit, print-object message recipients, examine arguments – make sure everything is doing what you think it’s doing.

I question whether you’re talking to the objects you think you’re talking to, because -setText: on a label should mark it as dirty, which will cause it to be given a chance to redraw at the appropriate point in the runloop. You should be seeing changes.

One thing to try if you’re at wits’ end is creating a fresh project, reproducing the skeleton of the problem, and verifying the problem still occurs. If not, you have something else going on, and you now have an example of how to do it right.


#5

You can also conveniently log an entire view hierarchy:

NSLog(@"Hierarchy: \n%@",[myView performSelector:@selector(recursiveDescription)]);

source: http://developer.apple.com/library/ios/#technotes/tn2239/_index.html


#6

I was broadcasting all those notifications, mostly as a learning experience. Yes, I could have set up a single notification and used the [note] and [note userInfo] objects to accomplish the same thing. However, maybe this isn’t an approach I want to attempt at all.

I guess before I go any further, maybe I should ask what the best way to link these things together is…?

  1. I have the custom UITableViewCell class, which is handling the cell interactions.

  2. I have a DataSyncController class, which is a data model with a 1-to-1 relationship with each cell.

  3. I have a view controller with a NSArray property which is a collection of DataSyncController(s), which it uses for its data source.

In my view controllers tableView: cellForRowAtIndexPath: delegate I link the cell’s DataSyncController to a property of the UITableViewCell class, which is how I am referencing the data in that class.

I am quite sure there is probably a better way to do this…? Please enlighten me.