I sometimes get an NSRangeException when I am populating my search array data from the web service. In my example, I reload the search display controller table every time I key in data from the search box. The search entry is then sent to foursquare search api, and once the data is retrieved, I update the my search array data model.
This is how it works:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self searchVenuesFromFoursquare:searchString];
return YES;
}
In the above method, the delegate is called everytime the user enters a value in the search box. It then reloads the search table. In the meantime, my web service call to Foursquare is trying to retrieve the venues matching the search term.
- (void)searchVenuesFromFoursquare:(NSString *)searchTerm
{
void(^completionBlock)(NSArray *obj, NSError *err) =
^(NSArray *obj, NSError *err)
{
dispatch_async(dispatch_get_main_queue(), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.searchVenues = obj;
[self.searchDisplayController.searchResultsTableView reloadData];
});
};
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
if (self.deviceLocation) {
NSString *latLon = [NSString stringWithFormat:#"%g,%g", self.deviceLocation.coordinate.latitude, self.deviceLocation.coordinate.longitude];
[[MakanKakiStore sharedStore]searchVenues:completionBlock withSearchTerm:searchTerm andDeviceLatLng:latLon];
}
else{
[[MakanKakiStore sharedStore]searchVenues:completionBlock withSearchTerm:searchTerm andDeviceLatLng:nil];
}
}
The problems seems to occur in this portion.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.searchDisplayController.searchResultsTableView) {
VenueSearchTableViewCell *searchVenueCell = [tableView dequeueReusableCellWithIdentifier:#"VenueSearchTableViewCell" forIndexPath:indexPath];
[searchVenueCell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
NSDictionary *venueDict = [self.searchVenues objectAtIndex:indexPath.row];
searchVenueCell.venueName.text = venueDict[#"name"];
return searchVenueCell;
}
}
When the cellForRow is trying to retrieve the venueDict from the searchVenues array, I think the Range Exception can occur when the model suddenly changes due to the web service call. This problem is very hard to duplicate as it depends on the web service response speed and the user input.
My temporary fix for now is to add this check
if (indexPath.row + 1 <= self.searchVenues.count) {
NSDictionary *venueDict = [self.searchVenues objectAtIndex:indexPath.row];
}
This seems to be the best solution for now and prevents the range exception problem. However, I was wondering if there is a best practice towards tackling this problem? Most tutorials on UISearchDisplayController seems to be tackling data that is initialize locally rather than from a remote web server.
Hence, I would really appreciate if someone could advice me if there is a best practices approach when populating data for UISearchDisplayController.
Thank you so much.
Have a look at this answer and it will most likely solve all your problems. You will basically throttle your requests to the Foursquare web service. Code:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
static NSTimer *timer;
[timer invalidate];
timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(searchVenuesFromFoursquare:) userInfo:#{#"searchString": searchString} repeats:NO];
return NO;
}
- (void)searchVenuesFromFoursquare:(NSTimer *)timer
{
NSString *searchString = timer.userInfo[#"searchString"];
...
}
Requests to a web service or data store should take place as infrequently as possible while achieving the desired goal.
Related
I am new in iOS.
I am trying to implement upload data in uitableview one by one row.
for that i am using background task.
using following code.
-(void)MethodUploadBgTaskAssign
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *responseString = [self MethodlblUploadClicked];
dispatch_async(dispatch_get_main_queue(), ^(void)
{
if([responseString isEqualToString:#"UploadSuccess"])
{
[self ReloadTblData];
[self ContinueUploadData];
}
});
});
}
-(void) ReloadTblData
{
if([dataArr count]>0)
[dataArr removeObjectAtIndex:0];
[uitblData reloadData];
}
-(void) ContinueUploadData
{
if((dataArr count]>0)
[self MethodUploadBgTaskAssign];
}
My problem is uploading data in table after some time table reload with empty data
because all data uploaded at that time.
I want show updated ui after uploading each cell in table.
What will be necessary changes in code?
appreciate for help.
It looks to me as if you are updating the table - but in the wrong thread (hence the table never actually appears to update). You need to use performSelectorOnMainThread to update the UI.
[self performSelectorOnMainThread:#selector(ReloadTblData:) // Update the table on the main thread
withObject:nil
waitUntilDone:NO];
I think this should work - give it a go!
I'm using OCMockito and I want to test a method in my ViewController that uses a NetworkFetcher object and a block:
- (void)reloadTableViewContents
{
[self.networkFetcher fetchInfo:^(NSArray *result, BOOL success) {
if (success) {
self.model = result;
[self.tableView reloadData];
}
}];
}
In particular, I'd want to mock fetchInfo: so that it returns a dummy result array without hitting the network, and verify that the reloadData method was invoked on the UITableView and the model is what it should be.
As this code is asynchronous, I assume that I should somehow capture the block and invoke it manually from my tests.
How can I accomplish this?
This is quite easy:
- (void) testDataWasReloadAfterInfoFetched
{
NetworkFetcher mockedFetcher = mock([NetowrkFetcher class]);
sut.networkFetcher = mockedFetcher;
UITableView mockedTable = mock([UITableView class]);
sut.tableView = mockedTable;
[sut reloadTableViewContents];
MKTArgumentCaptor captor = [MKTArgumentCaptor new];
[verify(mockedFetcher) fetchInfo:[captor capture]];
void (^callback)(NSArray*, BOOL success) = [captor value];
NSArray* result = [NSArray new];
callback(result, YES);
assertThat(sut.model, equalTo(result));
[verify(mockedTable) reloadData];
}
I put everything in one test method but moving creation of mockedFetcher and mockedTable to setUp will save you lines of similar code in other tests.
(Edit: See Eugen's answer, and my comment. His use of OCMockito's MKTArgumentCaptor not only eliminates the need for the FakeNetworkFetcher, but results in a better test flow that reflects the actual flow. See my Edit note at the end.)
Your real code is asynchronous only because of the real networkFetcher. Replace it with a fake. In this case, I'd use a hand-rolled fake instead of OCMockito:
#interface FakeNetworkFetcher : NSObject
#property (nonatomic, strong) NSArray *fakeResult;
#property (nonatomic) BOOL fakeSuccess;
#end
#implementation FakeNetworkFetcher
- (void)fetchInfo:(void (^)(NSArray *result, BOOL success))block {
if (block)
block(self.fakeResult, self.fakeSuccess);
}
#end
With this, you can create helper functions for your tests. I'm assuming your system under test is in the test fixture as an ivar named sut:
- (void)setUpFakeNetworkFetcherToSucceedWithResult:(NSArray *)fakeResult {
sut.networkFetcher = [[FakeNetworkFetcher alloc] init];
sut.networkFetcher.fakeSuccess = YES;
sut.networkFetcher.fakeResult = fakeResult;
}
- (void)setUpFakeNetworkFetcherToFail
sut.networkFetcher = [[FakeNetworkFetcher alloc] init];
sut.networkFetcher.fakeSuccess = NO;
}
Now your success path test needs to ensure that your table view is reloaded with the updated model. Here's a first, naive attempt:
- (void)testReloadTableViewContents_withSuccess_ShouldReloadTableWithResult {
// given
[self setUpFakeNetworkFetcherToSucceedWithResult:#[#"RESULT"]];
sut.tableView = mock([UITablewView class]);
// when
[sut reloadTableViewContents];
// then
assertThat(sut.model, is(#[#"RESULT"]));
[verify(sut.tableView) reloadData];
}
Unfortunately, this doesn't guarantee that the model is updated before the reloadData message. But you'll want a different test anyway to ensure that the fetched result is represented in the table cells. This can be done by keeping the real UITableView and allowing the run loop to advance with this helper method:
- (void)runForShortTime {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
}
Finally, here's a test that's starting to look good to me:
- (void)testReloadTableViewContents_withSuccess_ShouldShowResultInCell {
// given
[self setUpFakeNetworkFetcherToSucceedWithResult:#[#"RESULT"]];
// when
[sut reloadTableViewContents];
// then
[self runForShortTime];
NSIndexPath *firstRow = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *firstCell = [sut.tableView cellForRowAtIndexPath:firstRow];
assertThat(firstCell.textLabel.text, is(#"RESULT"));
}
But your real test will depend on how your cells actually represent the fetched results. And that shows that this test is fragile: if you decide to change the representation, then you have to go fix up a bunch of tests. So let's extract a helper assertion method:
- (void)assertThatCellForRow:(NSInteger)row showsText:(NSString *)text {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
UITableViewCell *cell = [sut.tableView cellForRowAtIndexPath:indexPath];
assertThat(cell.textLabel.text, is(equalTo(text)));
}
With that, here's a test that uses our various helper methods to be expressive and pretty robust:
- (void)testReloadTableViewContents_withSuccess_ShouldShowResultsInCells {
[self setUpFakeNetworkFetcherToSucceedWithResult:#[#"FOO", #"BAR"]];
[sut reloadTableViewContents];
[self runForShortTime];
[self assertThatCellForRow:0 showsText:#"FOO"];
[self assertThatCellForRow:1 showsText:#"BAR"];
}
Note that I didn't have this end in my head when I started. I even made some false steps along the way which I haven't shown. But this shows how I try to iterate my way to test designs.
Edit: I see now that with my FakeNetworkFetcher, the block get executed in the middle of reloadTableViewContents — which doesn't reflect what will really happen when it's asynchronous. By shifting to capturing the block then invoking it according to Eugen's answer, the block will be executed after reloadTableViewContents completes. This is far better.
- (void)testReloadTableViewContents_withSuccess_ShouldShowResultsInCells {
[sut reloadTableViewContents];
[self simulateNetworkFetcherSucceedingWithResult:#[#"FOO", #"BAR"]];
[self runForShortTime];
[self assertThatCellForRow:0 showsText:#"FOO"];
[self assertThatCellForRow:1 showsText:#"BAR"];
}
Can anybody explain to me how MVC works when it comes to UITableView especially when getting data from the internet.
I would exactly like to know what is the model, view and controller when it comes to a UItableview
I have written the following ViewController code which sources data from the internet and displays it on a table using AFNetworking framework.
Could you please tell me how to change this and separate it into model, view and controller.
I have also written a refresh class, which i am guessing is a part of the model. Could you tell me how exactly do i make changes and make it a part of the model.
EDIT : The below answers help me understand the concept theoritically, Could someone please help me in changing the code accordingly( By writing a new class on how to call the array to this class and populate the table because i am using a json parser). I would like to implent it. And not just understand it theoritically.
#import "ViewController.h"
#import "AFNetworking.h"
#implementation ViewController
#synthesize tableView = _tableView, activityIndicatorView = _activityIndicatorView, movies = _movies;
- (void)viewDidLoad {
[super viewDidLoad];
// Setting Up Table View
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.bounds.size.width, self.view.bounds.size.height) style:UITableViewStylePlain];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.tableView.hidden = YES;
[self.view addSubview:self.tableView];
// Setting Up Activity Indicator View
self.activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.activityIndicatorView.hidesWhenStopped = YES;
self.activityIndicatorView.center = self.view.center;
[self.view addSubview:self.activityIndicatorView];
[self.activityIndicatorView startAnimating];
// Initializing Data Source
self.movies = [[NSArray alloc] init];
NSURL *url = [[NSURL alloc] initWithString:#"http://itunes.apple.com/search?term=rocky&country=us&entity=movie"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:refreshControl];
[refreshControl endRefreshing];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.movies = [JSON objectForKey:#"results"];
[self.activityIndicatorView stopAnimating];
[self.tableView setHidden:NO];
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
- (void)refresh:(UIRefreshControl *)sender
{
NSURL *url = [[NSURL alloc] initWithString:#"http://itunes.apple.com/search?term=rambo&country=us&entity=movie"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.movies = [JSON objectForKey:#"results"];
[self.activityIndicatorView stopAnimating];
[self.tableView setHidden:NO];
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
[sender endRefreshing];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
// Table View Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.movies && self.movies.count) {
return self.movies.count;
} else {
return 0;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = #"Cell Identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID];
}
NSDictionary *movie = [self.movies objectAtIndex:indexPath.row];
cell.textLabel.text = [movie objectForKey:#"trackName"];
cell.detailTextLabel.text = [movie objectForKey:#"artistName"];
NSURL *url = [[NSURL alloc] initWithString:[movie objectForKey:#"artworkUrl100"]];
[cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:#"placeholder"]];
return cell;
}
#end
It's a pretty big question you are asking. But let me answer by making it as simple as possible.
Model - your data source; ultimately it's your web service data
Controller should be the thing that owns the table view and mediates setting properties on your view and reacting to events in the view and making changes , as needed, to the model
View(s) -- a combination of your table view and table view cells
There are a lot of approaches to coordinating between your web data and your table view but one I might suggest would be to refactor your web service calls into a separate store class - say iTunesStore - have that class be responsible for making the calls to the service and setting an internal array with the results, it should also be able to return a row count as well as a specific item for a given row index.
You then have this class respond to calls for the required table view delegate methods. Other things to consider, make this other class a singleton, have it conform to UITableviewDatasource protocol itself and assign it as the table views' data source.
Like I said, a big question with a lot of options for you, but I've given you some things to consider in terms of where to go next.
UPDATE
I'm adding some code examples to help clarify. At the outset, I want to make clear that I am not going to provide the total solution because doing so would require me to assume too much in terms of the necessary actual solution -- and because there are a few different ways to work with AFNetworking, web services, etc....and I don't want to get side tracked going down that rabbit hole. (Such as caching data on the client, background tasks & GCD, etc...) Just showing you how to wire up the basics -- but you will definitely want to learn how to use AFNetworking on a background task, look into Core Data or NSCoding for caching, and a few other topics to do this sort of thing correctly.
Suffice it to say that in a proper solution:
- You don't want to be calling your web service synchronously
- You also don't want to be re-requesting the same data every time - ie don't re-download the same record from the service unless its changed
- I am not showing how to do those things here because its way beyond the scope; look a the book recommendation below as well as this link to get an idea about these topics Ray Wenderlich - sync Core Data with a web service
For your data services code, I would create a 'store' class. (do yourself a favor and get the Big Nerd Ranch iOS book if you don't already have it.
iOS Programming 4th Edition
Take the following the code with a grain of salt - for reasons I can't go into I am not able to do this from my Mac (on a Win machine) and I also am not able to copy or even email myself the code ... so I am doing all in the StackOverflow editor...
My iTunesStore contract (header file) would look something like:
// iTunesStore.h
#import <Foundation/Foundation.h>
#interface iTunesStore : NSObject
- (NSUInteger)recordCount;
- (NSDictionary*)recordAtIndex:(NSUInteger)index; // could be a more specialized record class
+ (instancetype)sharedStore; // singleton
#end
...and the implementation would look something like:
// iTunesStore.m
#import "iTunesStore.h"
// class extension
#interface iTunesStore()
#property (nonatomic, strong) NSArray* records;
#end
#implementation iTunesStore
-(id)init
{
self = [super init];
if(self) {
// DO NOT DO IT THIS WAY IN PRODUCTION
// ONLY FOR DIDACTIC PURPOSES - Read my other comments above
[self loadRecords];
}
return self;
}
- (NSUInteger)recordCount
{
return [self.records count];
}
- (NSDictionary*)recordAtIndex:(NSUInteger)index
{
NSDictionary* record = self.records[index];
}
-(void)loadRecords
{
// simulate loading records from service synchronously (ouch!)
// in production this should use GCD or NSOperationQue to
// load records asynchrononusly
NSInteger recordCount = 10;
NSMutableArray* tempRecords = [NSMutableArray arrayWithCapacity:recordCount];
// load some dummy records
for(NSInteger index = 0; index < recordCount; index++) {
NSDictionary* record = #{#"id": #(index), #"title":[NSString stringWithFormat:#"record %d", index]};
[tempRecords addObject:record];
}
self.records = [tempRecords copy];
}
// create singleton instance
+ (instancetype)sharedStore
{
static dispatch_once_t onceToken;
static id _instance;
dispatch_once(&onceToken, ^{
_instance = [[[self class] alloc] init];
});
return _instance;
}
#end
I now have a 'store' object singleton I can use to get records, return a given record and also tell me a record count. Now I can move a lot of the logic doing this from the viewcontroller.
Now I don't need to do this in your VC viewDidLoad method. Ideally, you would have an async method in your store object to get records and a block to call you back once records are loaded. Inside the block you reload records. The signature for something like that 'might' look like:
[[iTunesStore sharedStore] loadRecordsWithCompletion:^(NSError* error){
... if no error assume load records succeeded
... ensure we are on the correct thread
[self.tableView reloadData]; // will cause table to reload cells
}];
Your view controller data source methods now look like:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection(NSInteger)section {
[[iTunesStore sharedStore] recordCount];
}
Inside cellForRowAtIndexPath - I also call my store object to get the correct record
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// ... get cell
// ... get record
NSDictionary* record = [[iTunesStore sharedStore] recordAtIndex:indexPath.row];
// ... configure cell]
return cell;
}
That's the gist of it. Other things to do, as noted above would be:
Have ITunesStore implement UITableViewDataSource and then just directly handle the tableview datasource methods - if you do this you don't want to make iTunesStore a singleton. And you would set an instance of iTunesStore as the tableview's delegate, rather than the viewcontroller. There are pros and cons to such an approach.
I haven't shown any real async behavior or caching which this app is crying out for
This does show how to pull off some of your model responsibilities and separate some of the tableview data source concerns.
Hopefully this will help to give you some ideas about different directions you might explore.
Happy coding!
In terms of UITableViewController, typically all the roles Model, View and Controller (MVC) is played by your UITableViewController itself. That is the case with your code as well.
As Model - It supplies data to your table view.
As Controller - It controls the look and feel of the table like number of rows, sections, height and width of them etc., supplies data from model to table view
As View - Its view property holds the UITableView
Now, to adopt a different approach you could have Model separated out from your controller class. For that have a subclass from NSObject and have it set its state which could be used by Controller.
Hope this makes sense to you.
I'm using TBXML+HTTP to get XML data from a website. What I want to do is populate a UITableView with the processed data.
I've created a NSMutableArray that holds all the entries and, as far as that goes, everything is working fine.
The problem is when, after successfully fetching the data from the server and storing it in the array, I try to update the table to show the data, using reloadData.
It takes about 5 seconds to populate the table with 20 rows. The weird part is that the fetching is happening really fast, so the data is available right away. I do not understand what is taking so long. Here is some info from the log:
2012-03-17 18:46:01.045 MakeMyApp[4571:207] numberOfSectionsInTableView: 1
2012-03-17 18:46:01.047 MakeMyApp[4571:207] numberOfRowsInSection: 0
2012-03-17 18:46:01.244 MakeMyApp[4571:1f03] numberOfSectionsInTableView: 1
2012-03-17 18:46:01.245 MakeMyApp[4571:1f03] numberOfRowsInSection: 20
2012-03-17 18:46:01.245 MakeMyApp[4571:1f03] Ok, I'm done. 20 objects in the array.
2012-03-17 18:46:01.246 MakeMyApp[4571:1f03] Finished XML processing.
2012-03-17 18:46:06.197 MakeMyApp[4571:1f03] cellForRowAtIndexPath:
As you can see, it fires the pair numberOfSectionsInTableView:/numberOfRowsInSection: two times: the first is when the view loads, the second is when I do [self.tableView reloadData];
You see that, in less than a second, the array is populated with the 20 objects and all the work processing the XML is done. How is cellForRowAtIndexPath: fired only 5 seconds later?
Here are some parts of the code that might help finding the problem:
- (void)createRequestFromXMLElement:(TBXMLElement *)element {
// Let's extract all the information of the request
int requestId = [[TBXML valueOfAttributeNamed:#"i" forElement:element] intValue];
TBXMLElement *descriptionElement = [TBXML childElementNamed:#"d" parentElement:element];
NSString *description = [TBXML textForElement:descriptionElement];
TBXMLElement *statusElement = [TBXML childElementNamed:#"s" parentElement:element];
int status = [[TBXML textForElement:statusElement] intValue];
TBXMLElement *votesCountElement = [TBXML childElementNamed:#"v" parentElement:element];
int votesCount = [[TBXML textForElement:votesCountElement] intValue];
// Creating the Request custom object
Request *request = [[Request alloc] init];
request.requestId = requestId;
request.description = description;
request.status = status;
request.votes_count = votesCount;
[requestsArray addObject:request];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
appListCell *cell = (appListCell *)[tableView dequeueReusableCellWithIdentifier:#"appListCell"];
Request *request = [requestsArray objectAtIndex:indexPath.row];
cell.appIdLabel.text = [NSString stringWithFormat:#"#%d", request.requestId];
cell.appTextLabel.text = request.description;
cell.appVotesLabel.text = [NSString stringWithFormat:#"%d votes", request.votes_count];
return cell;
}
Thanks a lot!
EDIT:
- (void)traverseXMLAppRequests:(TBXMLElement *)element {
int requestCount = 0;
TBXMLElement *requestElement = element->firstChild;
// Do we have elements inside <re>?
if ((requestElement = (element->firstChild))) {
while (requestElement) {
[self createRequestFromXMLElement:requestElement];
requestCount++;
requestElement = [TBXML nextSiblingNamed:#"r" searchFromElement:requestElement];
}
}
[self.tableView reloadData];
NSLog(#"Ok, I'm done. %d objects in the array.", [requestsArray count]);
}
EDIT 2: I've tried fetching the information of just 1 row, instead of 20, and the delay is exactly the same.
I know this is a really old question, but I've just come across the exact same issue and think I might know the cause. Might help someone who stumbles across this same question...
I suspect that your reloadData call is happening on a background thread as opposed to the main thread. What you describe is the exact effect of this - the code runs but there is a long delay (5-10 secs) in updating the user interface.
Try changing the reloadData call to:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
You are calling reloadData on a thread other than the mainThread
Make sure to reload your tableView's data on main thread like in the below:
OBJ-C:
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
Swift 3:
DispatchQueue.main.async {
self.tableView.reloadData()
}
Where does:
- (void)createRequestFromXMLElement:(TBXMLElement *)element
get called? Sound like you might be making 20 web requests to get that file contents. That would definitely make things slow (as opposed to doing one request to get the entire XML file and parsing that locally).
Can you post your whole VC code?
I'm trying to make a chat. I've googled for samples and I followed several of them without success in this post I've asked for the first troubles I've found and I've applied every answer without success.
Which I'm trying is in an UIViewController I load to 2 subclasses of UIView with one UITableView each one. In one of the views I'll load the users list and in other the messages sended with a selected user from the first class.
First I've tried to use threading within each of the classes with the content but was crashing because memory warnings.
Now I'm using a NSTimer in the UIViewController that calls to update both classes, but still crashing.
This is the code:
-(void)chatStartUpdating
{
chatIsUpdating = YES;
chatLoop = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:#selector(backgroundChatUpdate) userInfo:nil repeats:YES];
}
-(void)chatStopUpdating
{
if (chatLoop == nil) return;
[chatLoop invalidate];
chatLoop = nil;
chatIsUpdating = NO;
}
-(void)updateChat
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[chatBar updateList:nil];
[chat messagesMustBeUpdated:nil];
[pool release];
}
-(void)backgroundChatUpdate
{
[self performSelectorOnMainThread:#selector(updateChat) withObject:nil waitUntilDone:NO];
//[self performSelectorInBackground:#selector(updateChat) withObject:nil];
}
If I run in background the list of messages become slow in scrolling, and after a few updates I start receiving mem warnings and the app crashes.
the methods Start and Stop updating are called from Main Thread when user pushes the chat button or some occurrence of the users list.
Anyone knows a good example of code to do something similar? Or point me to the right way?
Thanks in advance.
EDIT----
Those are the classes inside the 2 UIViews that retrieves data from remote API, parse the results in a class to contain it and populates the tableview with results:
-(void)updateList:(id)sender
{
isReading = YES;
users = [OTChatDataManager readUsers];
[list reloadData];
NSLog(#"ChatBar Was updated");
isReading = NO;
}
-(void)messagesMustBeUpdated:(id)sender
{
isReading = YES;
iSQLResult *newMessages = [OTChatDataManager readMessagesFromUser:fromUserId toUser:toUserId sinceLastMessageId:lastMessageId];
[self mergeMessages:newMessages];
[list reloadData];
[newMessages release];
isReading = NO;
}
All the properties of the 2 lists are declared as atomic, and I tried each solution proposed in the link of this post, GDC, Locks, etc...