0

So I have a UIViewController which loads data asynchronously using NSOperationQueue. I had the commented out line at first when I was using seed data, but now I have switched to using the actual service. The data gets to this method appropriately, but the UI isn't updating. I then read you need to run UI updates on the main thread, so I changed it to the below code:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.menuView = (MenuView*)self.view;
    [MenuUIModel loadAndOnSuccess:^(id data, id context) {
        [self.menuView performSelectorOnMainThread:@selector(bindToModel:) withObject:data waitUntilDone:YES];
        //[self.menuView bindToModel:(MenuUIModel*)data];
    } onFail:^(NSString *error, id context) {
        NSLog(@"FAIL with error: %@", error);
    }];
}

Here is the MenuView, though slightly trimmed.

@interface MenuView : UITableView

@property (strong, nonatomic) NSArray *menuItemViews;

// Menu Item View Loading Outlet
@property (strong, nonatomic) IBOutlet MenuItemView *lastLoadedMenuItemView;

- (void) bindToModel:(MenuUIModel*)model;

@end

@implementation MenuView

- (void)bindToModel:(MenuUIModel*)model
{
    if (model) {
        NSMutableArray *mutableMenuItemViews = [NSMutableArray array];
        UINib* inMemoryNib = [UINib nibWithNibName:@"MenuItemView" bundle:nil];
        for (MenuItemUIModel *item in model.Items) {
            [inMemoryNib instantiateWithOwner:self options:nil];
            [self.lastLoadedMenuItemView bindToModel:item];
            [mutableMenuItemViews addObject:self.lastLoadedMenuItemView];
            index++;
        }
        self.menuItemViews = [[NSArray alloc] initWithArray:mutableMenuItemViews];
    }
}

@end

I realize this method of loading nibs is a little different, but the purpose is so I only load the nib and its resources into memory once but instantiate it as necessary. It also works wonderfully with the seed code.

So I thought that running this method on the main thread would work, but apparently not. Another note, I added a test UILabel to MenuView and it updated perfectly when I tested it in this method. So when I call bindToModel inside of my MenuView does that no longer run on the main thread or something?

Alec
  • 1,646
  • 3
  • 19
  • 35

1 Answers1

1

Edit: My first response confused the Cocoa desktop bindings mechanism with the methods you are referring to here as bindings. Cocoa bindings are not implemented at all in Cocoa Touch on iOS, so my previous advice was irrelevant. Sorry about that!

To answer your question, from the code you've shown, yes everything in your -bindToModel: method is running on the main thread. There is no obvious problem with your threading model visible here.

I would suggest adding some NSLog() calls inside your -bindToModel: methods to ensure everything is working as you expect. The output to the console log will include a thread id, so you can tell for certain that things are happening on the thread you intended.

Kaelin Colclasure
  • 3,925
  • 1
  • 26
  • 36
  • The only thing I run on another thread is the service call to pull data, after that the binding and UI updates are back on the main thread. So there shouldn't be any other threads making model changes, right? – Alec Jul 26 '12 at 16:52
  • Haha I have made a similar mistake. I read almost 100% of the Apple documentation on Cocoa bindings only to find at the very end that it isn't supported for iOS. I'll run some more tests soon and get back with more information if I can. – Alec Jul 26 '12 at 18:35
  • So I have run it with both seed code and with the asynchronous data call, and the binding all occurs on the main thread both ways. But it still doesn't update the UI with the asynchronous data call. And I have gotten it to successfully update a single outlet in the MenuView, so are the chained bindings someone losing their outlet connections or something? – Alec Jul 26 '12 at 18:46
  • I have tested my binding methods and they are all occurring successfully, the UI is just not updating. – Alec Jul 26 '12 at 19:05
  • 1
    Since your `MenuView` is a subclass of `UITableView` I am guessing you are populating the datasource/rows of a table? When you have finished, are you calling the `-reloadData` method of `UITableView`, or otherwise telling the view to update? – Kaelin Colclasure Jul 26 '12 at 19:08
  • I was not, but I tried it and it refreshed my `UILabel` outlets, but nothing else. And again, it works without the `-reloadData` call when I use seed data. – Alec Jul 26 '12 at 19:18
  • Wait don't worry about it, I might have stumbled upon something. Updates to follow after I check some more stuff. – Alec Jul 26 '12 at 19:29
  • 1
    I suspect your "seed data" is available so quickly that the data source is fully populated the first time the view is drawn, but that when you load data from your service it takes longer, and the table view is initially drawn empty. Any time you update the contents of a table view, you have to explicitly tell it to update the affected cells. If the whole contents have changed, `-reloadData` is the method for doing so. If only some portion has been updated, there are finer-grained methods like `-reloadRowsAtIndexPaths:withRowAnimation:`. – Kaelin Colclasure Jul 26 '12 at 19:33
  • Okay yes I found a bug that was preventing everything except `UILabel` outlets from loading, a stupid mistake on my part. But why do I have to call `reloadData` in order to update the table? Isn't that the whole point of outlets? Or is `UITableView` a strange exception? – Alec Jul 26 '12 at 19:36
  • But you are absolutely correct, I added a `sleep(5)` call after my seed data and it ended up not loading as well. I just never would have guess that behavior. Thanks a bunch! – Alec Jul 26 '12 at 19:41
  • 1
    No, outlets are only for making connections between objects in .nib archives and to objects already in memory (via File's Owner). Once you have loaded the archive, the outlet machinery is done. Cocoa bindings (which, again, are not available in Cocoa Touch on iOS) handle the sort of update logic you are thinking of, but they have limitations of their own (like not mixing with threads). – Kaelin Colclasure Jul 26 '12 at 19:41
  • So for a standard `UIView` would my equivalent call to `reloadData` would be methods such as `setNeedsLayout`? And I will need to do some kind of similar redrawing call for all views that I update in this manner? Just trying to solidify my understanding. – Alec Jul 26 '12 at 19:49
  • 1
    Have a look at the `UIView` method `-setNeedsDisplayInRect:` and its related methods. – Kaelin Colclasure Jul 26 '12 at 19:52
  • Sorry I meant `setNeedsDisplay`, my mistake. Okay that should do it, thanks for your help! – Alec Jul 26 '12 at 19:54