iOS Refresh button in View Controller Nav: reloading all tableViewCells created from parsed JSON when clicked - objective-c

I've got a fairly important conceptual issue that many people have asked about, but there isn't a readily available clear answer to be found by searching.
My application is simple: Several rows of TableViewCells populated with data from a parsed JSON feed. When a cell is clicked on, that cell's info is passed to a SecondViewController and displayed. The JSON feed is also stored to a .plist and in the case that the internet is not available, the TableViewCells are populated from the .plist.
This is all working great.
However, the last thing I need is a refresh button at the top of my FirstViewController to refresh the JSON feed, and all of the cells in the table with the new data from the new variables. However, I've encountered an issue with implementing this:
My original JSON call, and variables to populate the cells are located in the ViewDidLoad method. When the view loads, these variables are "set" and don't refresh. Further, I can move the JSON call and variables into viewWillLoad - which will refresh the table each time after clicking on a cell, and then clicking "back" to the firstViewController -- this will update the JSON and cells successfully, however it does impact the speed and makes the view controller "pause" when going back to the MainViewController, which makes calling my original JSON and setting my variables in viewWillLoad an unviable option.
I have created a reload button in ViewDidLoad, which is linked to an IBAction method "refresh":
Create Button Programitically in ViewDidLoad:
// Reload issues button
UIBarButtonItem *button = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh
target:self
action:#selector(refresh:)];
self.navigationItem.rightBarButtonItem = button;
[button release];
Action Method it's linked to:
- (IBAction)refresh:(id)sender {
myRawJson = [[NSString alloc] initWithContentsOfURL:[NSURL
URLWithString:#"http://www.yoursite.com/json.JSON"]
encoding:NSUTF8StringEncoding
error:nil];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary * myParsedJson = [parser objectWithString:myRawJson error:NULL];
// New updated dictionary built from refreshed JSON
allLetterContents = [myParsedJson objectForKey:#"nodes"];
// Log the new refreshed JSON
NSLog(#"You clicked refresh. Your new JSON is %#", myRawJson);
//Maybe use the notification center?? But don't know how to implement.
//[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refreshView:)
name:#"refreshView" object:nil];
//[[NSNotificationCenter defaultCenter] postNotificationName:#"refreshView"
object:nil];
}
[self.tableView reloadRowsAtIndexPaths:[self.tableView indexPathsForVisibleRows]
withRowAnimation:UITableViewRowAnimationNone];
[myRawJson release];
}
In the code above you can see that I'm re-calling the JSON each time the button is clicked and logging a message to console with the new JSON. This is working. I've even re-built a dictionary which is successfully adding the new content.
My question is: How can I make the tableViewCells "refresh" with this new data as well? Can I just make the button re-load the entire view controller - so it would call ViewDidLoad again? Do I need to re-think my apps structure, or move my original variables out of viewDidLoad?
I've been reading some posts on the NSNotificationCenter, but the implementation of this still baffles me, as I'm fairly new to iOS development.
Thanks~
Update:
It's still not updating. Here is my full refresh button code with [self.tableView reloadData]; called at the end of my IBAction.
- (IBAction)refresh:(id)sender {
[DSBezelActivityView newActivityViewForView:
self.navigationController.navigationBar.superview
withLabel:#"Loading Feed..." width:160];
myRawJson = [[NSString alloc] initWithContentsOfURL:[NSURL
URLWithString:#"http://site.com/mobile.JSON"]
encoding:NSUTF8StringEncoding
error:nil];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary * myParsedJson = [parser objectWithString:myRawJson error:NULL];
allLetterContents = [myParsedJson objectForKey:#"nodes"];
BOOL isEmpty = ([myParsedJson count] == 0);
if (isEmpty) {
NSString *refreshErrorMessage = [NSString
stringWithFormat:#"An internet or network connection is required."];
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Alert"
message: refreshErrorMessage
delegate:self
cancelButtonTitle:#"Close"
otherButtonTitles:nil];
[alert show];
[alert release];
allLetterContents = [NSMutableDictionary
dictionaryWithContentsOfFile:[self saveFilePath]];
//NSLog(#"allLetterContents from file: %#", allLetterContents);
} else {
NSLog(#"Your new allLetterContents is %#", allLetterContents);
// Fast enumeration through the allLetterContents NSMutableDictionary
for (NSMutableDictionary * key in allLetterContents) {
NSDictionary *node = [key objectForKey:#"node"];
NSMutableString *contentTitle = [node objectForKey:#"title"];
NSMutableString *contentNid = [node objectForKey:#"nid"];
NSMutableString *contentBody = [node objectForKey:#"body"];
// Add each Title and Nid to specific arrays
//[self.contentTitleArray addObject:contentTitle];
[self.contentTitleArray addObject:[[contentTitle
stringByReplacingOccurrencesOfString:#"&"
withString:#"&"] mutableCopy]];
[self.contentNidArray addObject:contentNid];
[self.contentBodyArray addObject:contentBody];
}
}
[self.tableView reloadData];
[DSBezelActivityView removeViewAnimated:YES];
[myRawJson release];
}
I'm configuring the cell at cellForRowAtIndexPath (Updated: Posted entire method):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
}
}
// Configure the cell.
cell.textLabel.text = [self.contentTitleArray objectAtIndex: [indexPath row]];
cell.detailTextLabel.text = [self.contentNidArray objectAtIndex: [indexPath row]];
return cell;
}
Setting it on didSelectRowAtIndexPath:
self.detailViewController.currentNodeTitle = [contentTitleArray
objectAtIndex:indexPath.row];
self.detailViewController.currentNodeNid= [contentNidArray
objectAtIndex:indexPath.row];
self.detailViewController.currentNodeBody = [contentBodyArray
objectAtIndex:indexPath.row];
So when clicking my refresh button the table should* refresh with the new json, but does not.. Am I missing a step?
Additionally this may not be important, but I'm changing the colors for every other row with:
// Customize the appearance of table view cells.
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row % 2)
{
[cell setBackgroundColor:[UIColor colorWithRed:221.0/255.0 green:238.0/255.0 blue:255.0/255.0 alpha:1]];
cell.textLabel.textColor = [UIColor colorWithRed:2.0/255.0 green:41.0/255.0 blue:117.0/255.0 alpha:1];
cell.detailTextLabel.textColor = [UIColor colorWithRed:2.0/255.0 green:41.0/255.0 blue:117.0/255.0 alpha:1];
} else [cell setBackgroundColor:[UIColor clearColor]];
}
Update

You need to call the reload method.
[self.tableView reloadData];
This will fire the dataSource and delegate events an will refresh the UITableView.
You can find more info in the UITableView Class Reference:
Call this method to reload all the data that is used to construct the table, including cells, section headers and footers, index arrays, and so on. For efficiency, the table view redisplays only those rows that are visible.

Related

Using UIButton to refresh the UItableViewCell content

There are two components, UItableView and a UIButton, in my app.
The UItableViewcell will load the data from remote database fulfilled by JSON.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *TableIdentifier = #"tableidentifier"
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TableIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:TableIdentifier] autorelease];
}
NSDictionary *voc_list=[listData objectAtIndex:indexPath.row];
NSLog(#"%#",voc_list);
cell.textLabel.text = [[(NSDictionary*)voc_list objectForKey:#"vocabulary_list"]objectForKey:#"Vocabulary"];
cell.detailTextLabel.text=[[(NSDictionary*)voc_list objectForKey:#"vocabulary_list"]objectForKey:#"Translation"];
cell.textLabel.font = [UIFont boldSystemFontOfSize:15];
return cell; }
However, I want to refresh all the table content when user press the button, and I try to implement the following code:
-(IBAction)historyPressed:(id)sender{
isToogle = !isToogle;
if(isToogle){
// Back to original table content
}else{
// Following codes will communicate with remote server and filter data to the app.
// The app go smooth here.
NSError *error = NULL;
NSDictionary *getStuID=[NSDictionary dictionaryWithObjectsAndKeys:student_id,#"Stu_ID", nil];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:getStuID options:NSJSONWritingPrettyPrinted error:&error];
[self sendTOcompareByJSON:jsonData];
//Following codes are trying to show/refresh the data on tableview, but the app will go crash.
CGPoint location = [sender locationInView:self.table];
NSIndexPath *indexPath = [self.table indexPathForRowAtPoint:location];
UITableViewCell *new_cell=[self.table cellForRowAtIndexPath:indexPath];
historyList_= [NSArray arrayWithArray:personalized_history];
NSDictionary *dic = [historyList_ objectAtIndex:indexPath.row];
new_cell.textLabel.text=[[(NSDictionary*)dic objectForKey:#"history_list"]objectForKey:#"Vocabulary"];
new_cell.detailTextLabel.text=[[(NSDictionary*)dic objectForKey:#"history_lsit"]objectForKey:#"Score"];
}
}
In the historypressed method just try to call [yourtableview reloaddata].. After your setting of all the cell content do reloaddata,it will refresh the tableview.
I dont know how you getting data from [self sendTOcompareByJSON:jsonData];. If its a sync call, to webserver, then you can just update your datasource (in ur case you are filling tableview using listData) just after this.So once the listData is updated with the new contents, then you should reload your tableview like this [self.table reloadData]
If its an async call to web server, then do update the datasource and reload the table on callback.
Hope this helps.

not able to click the search bar tableview cell

I've got a tableview containing array of names. The search bar works perfectly filtering the names in the table view.
The problem is the didSelectRowAtIndexpath is not getting fired when clicking the search tableview cell. Could you help me out?
What is the thing that I'm missing? Should I include any special delegate to involve search tableview cell click.
Below is the image and code.
-(void)search
{
nameArray = [[NSMutableArray alloc] init];
searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 160, 44)];
searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
searchDisplayController.delegate = self;
searchDisplayController.searchResultsDataSource = self;
self.tableViewFriendsList.tableHeaderView = searchBar;
}
- (void)searchDisplayController:(UISearchDisplayController *)controller
willShowSearchResultsTableView:(UITableView *)tableView
{
[tableView setRowHeight:70];
[tableView reloadData];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.tableViewFriendsList) {
NSString *friendsID =[[[self.friendsDictionary objectForKey:#"data"] objectAtIndex:indexPath.row] objectForKey:#"id"];
[[FacebookHelper sharedFacebookHelper] postOnWallWithDelegate:self andID:friendsID];
}
if (tableView == self.searchDisplayController.searchResultsTableView) {
NSLog(#"I ve come here");
NSString *friendsID =[friendsListIdArray objectAtIndex:indexPath.row];
[[FacebookHelper sharedFacebookHelper] postOnWallWithDelegate:self andID:friendsID];
}
}
You forgot to set
searchController.searchResultsDelegate = self;
I do something in one of my projects that may be of assistance:
// add gesture to detect when table view is being tapped so that keyboard may be dismissed
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissKeyboard)];
gestureRecognizer.cancelsTouchesInView = NO;
[self.tableView addGestureRecognizer:gestureRecognizer];
Moreover I am wondering why you have a search bar within a table cell. Would you mind posting a screen shot of it in your app? I am afraid you may be doing more work than is necessary.

How can i push a TableView on clicking a button and initialize the TableViewCell with NSDictionary?

Can anyone please help me about how to push a table view on clicking a button.
I want to load the messages from NSMutableArray to the table view cells and NSMutableArray is loaded with the data parsed from a URL..
-(IBAction)readMessages:(id)sender
{
// i want to push the tableview when clicking the button in relation with this method
// WHAT MUST I DO HERE?
}
Instead of asking a new question i liked to edit this one, since the matter is in the same aspect..
I now can create the tableview programatically, but i cant initialize its cells with the data i get from Json array. Here is my code:
NSString *str1=[#"?username=" stringByAppendingString:userNameField.text];
NSString *str2=[#"&password=" stringByAppendingString:passwordField.text];
NSString *str3=[str1 stringByAppendingString:str2];
NSString *str4 =[#"http://" stringByAppendingString:serverField.text];
NSURL *url=[NSURL URLWithString:[str4 stringByAppendingString:[#"/ipad/login.php" stringByAppendingString:str3]]];
//get the url to jsondata
NSData *jSonData=[NSData dataWithContentsOfURL:url];
if (jSonData!=nil) {
NSError *error=nil;
id result=[NSJSONSerialization JSONObjectWithData:jSonData options:
NSJSONReadingMutableContainers error:&error];
if (error==nil) {
NSDictionary *mess=[result objectForKey:#"message"];
NSDictionary *messContent=[mess valueForKeyPath:#"message"];
NSDictionary *messID=[mess valueForKeyPath:#"ID"];
NSString*key1=[ result objectForKey:#"key" ];
NSString *s1=[#"http://" stringByAppendingString:serverField.text];
NSString *s2=[s1 stringByAppendingString:#"/ipad/button.php"];
NSURL *url2=[NSURL URLWithString:[s2 stringByAppendingString:[#"?key=" stringByAppendingString:key1]]];
NSData *data2=[NSData dataWithContentsOfURL:url2];
id result2=[NSJSONSerialization JSONObjectWithData:data2 options:NSJSONReadingMutableContainers error:nil];
mesID = [NSMutableArray array];//saving meesage ID s to NSMutableArray
content = [NSMutableArray array];
// i logged here and it saves the data, now i want to display my data in table view
for (NSDictionary *data in mess) {
[mesID addObject:[data objectForKey:#"ID"]];
[content addObject:[data objectForKey:#"message"]];
[[NSUserDefaults standardUserDefaults] setObject:messID forKey:#"message"];
[[NSUserDefaults standardUserDefaults] setObject:messContent forKey:#"messContent"];
//messID will be saved as the Title of the cells and messContent will be displayed as the text area of that cell, opening in a new view
And this is the output, i want to set the titles of cells as ID and their content as text:
2012-01-17 16:26:59.873 ipad_Teslim[940:f803] MessID: (
1,
3
)
2012-01-17 16:26:59.875 ipad_Teslim[940:f803] Content: (
asdf,
"this is a test"
)
As i have mentioned in my code too, messID will be saved as the Title of the cells and messContent will be displayed as the text area of that cell, opening in a new view.. How can i do it now? Please Help me, there are a lot of tutorials there, i looked a lot too but couldn't break this problem.
Try this :
-(IBAction)readMessages:(id)sender {
SecondView *secondView =[[SecondView alloc] initWithNibName:#"SecondView" bundle:nil];
[self presentModalViewController:secondView animated:YES];
}
SecondView is your UIViewController subclass which hold a UITableView.
Q1: U no need to add a navigation to return back to ur main page.
When ever u use
[self.navigationController pushViewController:next animated:YES];
by defaults it will creates back navigation in the next view to push return back.
in case it doesn't created yet, Try the following code in next view:
- (void)viewDidLoad
{
[super viewDidLoad];
//To set the back buttin on leftside of Navigation bar
UIBarButtonItem *backButton = [[[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStyleDone target:self action:#selector(backclick:)] autorelease];
self.navigationItem.leftBarButtonItem = backButton;
}
- (IBAction)backclick:(id)sender //first declrared in .h file
{
// To goback to the previous view
[self.navigationController popViewControllerAnimated:YES];
}
If u have a navigation control and if u want to pushed by navigation try the following:
-(IBAction)readMessages:(id)sender {
NextView *next = [[NextView alloc]initWithNibName:#"NextView" bundle:nil];
[self.navigationController pushViewController:next animated:YES];
[next release];
}
if u dont have a navigation control and if u want to just display the next view, try the following:
-(IBAction)readMessages:(id)sender {
NextView *next =[[NextView alloc] initWithNibName:#"NextView" bundle:nil];
[self presentModalViewController:next animated:YES];
[next release];
}
if u are having sub view in the same class try the following:
-(IBAction)readMessages:(id)sender {
[self.view addsubview nextView];
}
Yes U can,
try this to create xib programitically in viewDidload:
UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(10, 10,300,460)];
view1.backgroundColor = [UIColor redColor];
[self.view addSubview:view1];
But better to create by using
following path in xcode menu:
File-> New -> NewFile -> UIViewControllerSubClass -> Next -> Next -> Create
Or simply Drag & drop an view from ur Interface Builder
Q2: U can initialize ur tableView cells with JSONArray:
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [JSONarray count]; //***********
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.textLabel.text = [JSONarray objectAtIndexIndexPath.row]; //***********
}

Accurate progress displayed with UIProgressView for ASIHTTPRequest in an ASINetworkQueue

Summary: I want to track the progress of file downloads with progress bars inside cells of a tableview.
I'm using ASIHTTPRequest in an ASINetworkQueue to handle the downloads.
It works, but the progress bars stay at 0%, and jump directly at 100% at the end of each download.
Details:
I set up my ASIHTTPRequest requests and ASINetworkQueue this way:
[Only an extract of my code]
- (void) startDownloadOfFiles:(NSArray *) filesArray {
for (FileToDownload *aFile in filesArray) {
ASIHTTPRequest *downloadAFileRequest = [ASIHTTPRequest requestWithURL:aFile.url];
UIProgressView *theProgressView = [[UIProgressView alloc] initWithFrame:CGRectMake(20.0f, 34.0f, 280.0f, 9.0f)];
[downloadAFileRequest setDownloadProgressDelegate:theProgressView];
[downloadAFileRequest setUserInfo:
[NSDictionary dictionaryWithObjectsAndKeys:aFile.fileName, #"fileName",
theProgressView, #"progressView", nil]];
[theProgressView release];
[downloadAFileRequest setDelegate:self];
[downloadAFileRequest setDidFinishSelector:#selector(requestForDownloadOfFileFinished:)];
[downloadAFileRequest setDidFailSelector:#selector(requestForDownloadOfFileFailed:)];
[downloadAFileRequest setShowAccurateProgress:YES];
if (! [self filesToDownloadQueue]) {
// Setting up the queue if needed
[self setFilesToDownloadQueue:[[[ASINetworkQueue alloc] init] autorelease]];
[self filesToDownloadQueue].delegate = self;
[[self filesToDownloadQueue] setMaxConcurrentOperationCount:2];
[[self filesToDownloadQueue] setShouldCancelAllRequestsOnFailure:NO];
[[self filesToDownloadQueue] setShowAccurateProgress:YES];
}
[[self filesToDownloadQueue] addOperation:downloadAFileRequest];
}
[[self filesToDownloadQueue] go];
}
Then, in a UITableViewController, I create cells, and add the name of the file and the UIProgressView using the objects stored in the userInfo dictionary of the request.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"fileDownloadCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"FileDownloadTableViewCell" owner:self options:nil];
cell = downloadFileCell;
self.downloadFileCell = nil;
}
NSDictionary *userInfo = [self.fileBeingDownloadedUserInfos objectAtIndex:indexPath.row];
[(UILabel *)[cell viewWithTag:11] setText:[NSString stringWithFormat:#"%d: %#", indexPath.row, [userInfo valueForKey:#"fileName"]]];
// Here, I'm removing the previous progress view, and adding it to the cell
[[cell viewWithTag:12] removeFromSuperview];
UIProgressView *theProgressView = [userInfo valueForKey:#"progressView"];
if (theProgressView) {
theProgressView.tag = 12;
[cell.contentView addSubview:theProgressView];
}
return cell;
}
The progress bar are all added, with the progress set to 0%.
Then, at end of download, they instantly jump to 100%.
Some of the download are very big (more than 40Mb).
I do not do anything tricky with threads.
Reading the forums of the ASIHTTPRequest, it seems I'm not alone, but I couldn't find a solution.
Am I missing something obvious? Is this a bug in ASI* ?
ASIHTTPRequest can only report progress if the server is sending Content-Length: headers, as otherwise it doesn't know how big the response will be. (ASINetworkQueue also sends HEAD requests at the start to try to figure out document sizes.)
Try collecting all the network traffic with charlesproxy or wireshark, see if these headers are present and/or what is happening with the HEAD requests.

Objective-C TableView Select Item Troubles

I'm having trouble having my app respond when an Item is selected in the table view. I'm running everything from my app delegate (the table functions that is like dataSource and TitleForHeaderAtSection etc) which are all being called fine. However it is not calling my selection method when I tap on a item in the list. I even put a NSLog to see just in case.
Here's my code: (its quite long and extensive and I know theres crap in there that doesn't need to be there but I put all of it just in case you needed it...)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Selected Row.", #"");
//Get the selected country
[tableView deselectRowAtIndexPath:indexPath animated:NO];
NSDictionary *dictionary = [data objectAtIndex:indexPath.section];
//NSArray *array = [dictionary objectForKey:#"My Wishlists"];
//NSString *selectedWishlist = [array objectAtIndex:indexPath.row];
//Initialize the detail view controller and display it.
WishlistDetailView *dvController = [[WishlistDetailView alloc] initWithNibName:#"WishlistDetailView" bundle:[NSBundle mainBundle]];
dvController.selectedWishlistId = [wishlistids objectAtIndex:indexPath.row];
NSLog(#"Selected row with wishlist id: %#", dvController.selectedWishlistId);
[[self navController] pushViewController:dvController animated:YES];
[dvController release];
dvController = nil;
}
The code compiles with NO errors.
Thanks for your persistant help!!
Christian Stewart
(by the way both of the selection allowed checkboxes are checked in Interface builder.)
tableView:didSelectRowAtIndexPath: is a UITableViewDelegate method. Is you controller the delegate of the table view?