0

I have a UITableView that loads content from a xml file inside a webserver.

  • The TableView is inside a tab Bar Controller;
  • I Call [table reloadData] inside the TBXMLSuccessBlock;
  • The problem is that the content of the TableView dont update.
  • I am using TBXML library;

After the Button have been clicked, How to says to tableVIew that XML file have been downloaded and show it?

- (IBAction)leftButton:(UIButton *)sender {
    if (a<=2 && a != 0) {
        a = a - 1;

        NSString *leftURL = self.getDate;
        [self loadURL:leftURL];
    }

}

- (void)loadURL:(NSString *)newURL{

    if (newURL == NULL) {
        newURL = self.getDate;
    }

    // Create a success block to be called when the asyn request completes
    TBXMLSuccessBlock successBlock = ^(TBXML *tbxmlDocument) {
        NSLog(@"PROCESSING ASYNC CALLBACK");

        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
        // If TBXML found a root node, process element and iterate all children
        if (tbxmlDocument.rootXMLElement)
        {
            // Obtain root element
            TBXMLElement * root = tbxml.rootXMLElement;
        if (root)
        {
            _storeArray = [NSMutableArray new];
            TBXMLElement * elem_PLANT = [TBXML childElementNamed:@"news" parentElement:root];
            while (elem_PLANT !=nil)
            {
                TBXMLElement * elem_title = [TBXML childElementNamed:@"title" parentElement:elem_PLANT];
                NSString *titleName = [TBXML textForElement:elem_title];

                TBXMLElement * elem_artist = [TBXML childElementNamed:@"text" parentElement:elem_PLANT];
                NSString *artistName = [TBXML textForElement:elem_artist];

                TBXMLElement * elem_thumb = [TBXML childElementNamed:@"thumb_url" parentElement:elem_PLANT];
                NSString *thumbName = [TBXML textForElement:elem_thumb];

                NSDictionary *dictionary = [[NSDictionary alloc]initWithObjects:@[titleName, artistName, thumbName] forKeys:@[@"title", @"text", @"thumb"]];
                elem_PLANT = [TBXML nextSiblingNamed:@"news" searchFromElement:elem_PLANT];
                [_storeArray addObject:dictionary];

            }

        }

          [_tableView reloadData];
        }
    };

    // Create a failure block that gets called if something goes wrong
    TBXMLFailureBlock failureBlock = ^(TBXML *tbxmlDocument, NSError * error) {
        NSLog(@"Error! %@ %@", [error localizedDescription], [error userInfo]);
    };

    // Initialize TBXML with the URL of an XML doc. TBXML asynchronously loads and parses the file.
    tbxml = [[TBXML alloc] initWithURL:[NSURL URLWithString:newURL]
                               success:successBlock
                               failure:failureBlock];
}

EDIT: ReloadData inside buttonClick

- (IBAction)leftButton:(UIButton *)sender {
    if (a<=2 && a != 0) {
        a = a - 1;

        NSString *leftURL = self.getDate;
        [self loadURL:leftURL];
        [_tableView reloadData];
    }

    }

- (IBAction)rightButton:(UIButton *)sender {
    if (a<2) {
        a = a + 1;
        NSString *rightURL = self.getDate;
        [self loadURL:rightURL];
        [_tableView reloadData];
    }
}

When app launch: - URL number 1 is loaded; - URL number 1 is showed; When I click one time right: - URL number 2 is loaded; - Nothing happen; When I click one more time right: - URL number 3 is loaded; - URL number 2 is showed; When I click one time left: - URL 2 is loaded; - URL 3 is showed; When I click one more time left: - URL 1 is loaded; - URL 2 is showed;

Marckaraujo
  • 7,422
  • 11
  • 59
  • 97

2 Answers2

1

You said:

When app launch: - URL number 1 is loaded; - URL number 1 is showed; When I click one time right: - URL number 2 is loaded; - Nothing happen; When I click one more time right: - URL number 3 is loaded; - URL number 2 is showed; When I click one time left: - URL 2 is loaded; - URL 3 is showed; When I click one more time left: - URL 1 is loaded; - URL 2 is showed;

From this, we know:

  1. URL/XML loading works—the data is there to be displayed, but the display isn't happening when it should.
  2. Table reloading works—on a later button press, the earlier data is displayed, but there is different data being displayed.
  3. Table reloading is happening too early—that's why it's showing the previous URL/XML's data.

Your button actions are (indirectly) making the URL-loading call using a library that takes success/failure block(s), so here's what's happening, roughly:

  1. The IBAction calls loadUrl:, which runs TBXML's initWithURL:success:failure:
  2. initWithURL:success:failure: stores the success and failure blocks and starts the actual network action on a separate thread ("in the background"), and returns, almost certainly before the network action has finished.
  3. The IBAction calls reloadData.
  4. Later, in the background, the network action finishes and the success or failure block is run.

So, moving the reloadData call into the success block makes it so that the table is reloaded after the XML data is processed.

But initially this crashed with an error that included:

This may be a result of calling to UIKit from a secondary thread.

UIKit calls—roughly, any method call that affects the user interface—have to be made on the main thread. For some asynchronous libraries that use block callbacks (notably AFNetworking), the block callbacks are guaranteed to be run on the main thread for just this reason. With TBXML, apparently, the block callbacks are run in the background, probably on the same thread that did the actual network loading action. So, we need some way to make the reloadData call to the UITableView happen on the main thread.

There are several ways to do this. My first preference is usually to use the main operation queue (which we get using the mainQueue class method on NSOperationQueue), which is a queue of operations to perform on the main thread:

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    [_tableView reloadData];
}];

The main operation queue then automatically handles executing the block as soon as it can on the main thread. Alternately, but still at the Objective-C/Cocoa/CocoaTouch level, you can use performSelectorOnMainThread:withObject:waitUntilDone: on your table view:

[_tableView performSelectorOnMainThread:@selector(reloadData)
                             withObject:nil
                          waitUntilDone:NO];

(or YES for wait until done—probably doesn't matter in this case), which has exactly the same effect. A third option, though generally not preferred in this case, is to use Grand Central Dispatch directly:

dispatch_async(dispatch_get_main_queue(), ^{
    [_tableView reloadData];
}];

(but when there are higher-level options like the NSOperationQueue route or the performSelectorOnMainThread:withObject:waitUntilDone: route, they should be used instead of this kind of lower-level C Grand Central Dispatch routine; also, changing dispatch_async to dispatch_sync has the same effect as changing waitUntilDone:NO to waitUntilDone:YES in the previous example).

Isaac
  • 10,668
  • 5
  • 59
  • 68
  • I am sure that _tableView points to my table view in xib, also dataSource and delegate points to File's Owner and I do implement inside the UIViewController class. reloadData works when I can inside viewDidAppear, but it just load one time, it don't update when I click inside the button. The NSURL also is valid. – Marckaraujo Mar 23 '13 at 00:32
  • Have you tried calling `reloadData` when the button is clicked, after you have loaded the new data into your data source? – Isaac Mar 23 '13 at 00:33
  • See my edit, as you can see the reloadData "works", but it update the previous data. – Marckaraujo Mar 23 '13 at 02:35
  • Try removing the `reloadData` call inside the button `IBAction` methods—when you call `loadURL:`, it starts the loading process, but returns and calls `reloadData` probably before any data has been loaded. The `reloadData` call inside the success block in the data-loading part of `loadURL:` should take care of refreshing the table. (I don't think removing the extra calls to `reloadData` in the button actions will fix it, but it can't hurt to try.) – Isaac Mar 23 '13 at 03:53
  • If I put inside success block, the app crash: 2013-03-23 12:31:15.696 Semhora[3826:450f] bool _WebTryThreadLock(bool), 0x1e5fb590: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now... 1 0x37e5e3b7 WebThreadLock – Marckaraujo Mar 23 '13 at 15:32
  • maybe I need to implement a delegate or another class that call reloadData after data is complete? – Marckaraujo Mar 23 '13 at 15:33
  • Try it inside the success block, but make it run on the main thread—put `[[NSOperationQueue mainQueue] addOperationWithBlock:^{ [_tableView reloadData]; }];` inside the success block. – Isaac Mar 23 '13 at 15:45
  • Done, it works now, I just dont understand why or when I need to use this NSOperationQueue, could you update your answer with a quick explanation? thanks – Marckaraujo Mar 23 '13 at 16:11
  • I've edited my answer to reflect what was happening, based on the comments above, and to include an explanation of why I suggested the changes you made. Feel free to let me know if you have questions about any of it. – Isaac Mar 23 '13 at 16:53
-1

as you r saying,you have call method reloadData then it should refresh the table view..i think then problem is not there,have u tried someone without using tab bar controller..might be sometimes bcoz of tab bar controller problem may arises...so just try it once..& let us know.. or we help u in some another way

Kalyani
  • 392
  • 2
  • 17