refresh the table in view controller by using refresh bar button - iphone

I want to add a refresh bar button in my view controller
I put in the viewdidload() this code:
UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:#selector(refreshTable)];
self.navigationItem.leftBarButtonItem = refreshButton;
and the refreshtable function is :
- (void) refreshTable
{
[self.tableView reloadData];
NSLog(#"table is refreshing ....");
}
this is a screenshot of the refresh button:
but it doesn't work, shall i add something else in the refreshtable function?, the table view doesn't refreshed!, the message "table is refreshing ...." appears everytime i click on the refresh button, but the table doesn't load new data!
when refreshing, can I have this icon somewhere?
If I had two table view in my view controller, and I want when I click on the refresh button, just the first table to be reloaded,
I put the same you code that you suggested, but it doesn't work here! should I do something else?

The issue is you added the data fetching logic in your viewDidLoad (according to your comments).
The viewDidLoad method will be called when the view is loading. It' won't be called unless you dismiss the view and present it again(it won't be calles if you call the reloadData it's just for reloading the UITableView).
When you call the reloadData the data source is still with old data, it is not updated.
So implement your refreshTable method like:
- (void) refreshTable
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://....../fastnews.php"]];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
}
-(void)fetchedData:(NSData *)responseData
{
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
fastResults = [json objectForKey:#"nodes"];
[self.tableView reloadData];
}

try this .
[self.tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:[tableView indexPathsForVisibleRows]
withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];

You need to update your dataSourceAaaay in refreshTable method before reload table.

Related

MBProgressHUD activity indicator doesn't show in viewDidAppear after segue is performed

I have two UITableViewControllers A and B, and this is what I'm trying to do when I click on a table view cell in A:
Prepare to segue from A to B by setting some of B's variables from A.
Perform segue from A to B.
B appears.
Display a "Loading" activity indicator with [MBProgressHUD][1].
In a background task, retrieve data from a URL.
If an error occurs in the URL request (either no data received or non-200 status code), (a) hide activity indicator, then (b) display UIAlertView with an error message
Else, (a) Reload B's tableView with the retrieved data, then (b) Hide activity indicator
However, this is what's happening, and I don't know how to fix it:
After clicking a cell in A, B slides in from the right with an empty plain UITableView. The MBProgressHUD DOES NOT SHOW.
After a while, the tableView reloads with the retrieved data, with the MBProgressHUD appearing very briefly.
The MBProgressHUD immediately disappears.
There doesn't seem to be an error with the way the background task is performed. My problem is, how do I display the MBProgressHUD activity indicator as soon as my B view controller appears? (And actually, how come it's not showing?) Code is below.
A's prepareForSegue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
B *b = (B *)[segue destinationViewController];
// Set some of B's variables here...
}
Relevant methods in B
- (void)viewDidAppear:(BOOL)animated {
[self startOver];
}
- (void)startOver {
[self displayLoadingAndDisableTableViewInteractions];
[self retrieveListings];
[self.tableView reloadData];
[self hideLoadingAndEnableTableViewInteractions];
}
- (void)displayLoadingAndDisableTableViewInteractions {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Loading";
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
self.tableView.userInteractionEnabled = NO;
}
- (void)hideLoadingAndEnableTableViewInteractions {
[MBProgressHUD hideHUDForView:self.view animated:YES];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.tableView.userInteractionEnabled = YES;
}
- (void)retrieveListings {
__block NSArray *newSearchResults;
// Perform synchronous URL request in another thread.
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
newSearchResults = [self fetchNewSearchResults];
});
// If nil was returned, there must have been some error--display a UIAlertView.
if (newSearchResults == nil) {
[[[UIAlertView alloc] initWithTitle:#"Oops!" message:#"An unknown error occurred. Try again later?" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
} else {
// Add the retrieved data to this UITableView's model. Then,
[self.tableView reloadData];
}
}
- (NSArray *)fetchNewSearchResults {
// Assemble NSMutableArray called newSearchResults from NSURLConnection data.
// Return nil if an error or a non-200 response code occurred.
return newSearchResults;
}
I think you have to call [self hideLoadingAndEnableTableViewInteractions]; after newSearchResults = [self fetchNewSearchResults]; You are retrieving data in another thread which means -startOver will continue executing after calling [self retrieveListings]; and will hide the HUD right away. Also because you are updating the display you have to make sure you are doing that on the main thread. See example
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
});
When B appears, it displays a plain and empty UITableView, but does not display the MBProgressHUD even if the task does begin in the background (and yet, the MBProgressHUD is called to show before that). Hence, my solution is to show the MBProgressHUD in viewDidLoad, which precedes viewWillAppear.
- (void)viewDidLoad {
// ...
[self displayLoadingAndDisableUI];
}
I set up two additional boolean properties to B--one in .h, called shouldStartOverUponAppearing, and one in a class extension in .m, called isLoadingAndDisabledUI. In startOver, I added the following lines:
- (void)startOver {
if (!self.isLoadingAndDisabledUI) {
[self displayLoadingAndDisabledUI];
}
}
The check is done so that startOver doesn't display another MBProgressHUD when it has already been displayed from viewDidLoad. That is because I have a third view controller, called C, that may call on B's startOver, but doesn't need to call viewDidLoad just to display the MBProgressHUD.
Also, this is how I defined viewDidAppear:
- (void)viewDidAppear:(BOOL)animated {
if (self.shouldStartOverUponAppearing) {
[self startOver];
self.shouldStartOverUponAppearing = NO;
}
}
This way, startOver will only be invoked IF B appeared from A. If B appears by pressing "Back" in C, it will do nothing and only display the old data that was there.
I think that this solution is FAR from elegant, but it works. I guess I'll just ask for a better approach in a separate SO question.
I have used a common method for MBProgressHUD.
#import "MBProgressHUD.h" in AppDelegate.h also following methods.
- (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title;
- (void)dismissGlobalHUD;
In AppDelegate.m add following methods.
- (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {
[MBProgressHUD hideAllHUDsForView:self.window animated:YES];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.window animated:YES];
hud.labelText = title;
return hud;
}
- (void)dismissGlobalHUD {
[MBProgressHUD hideHUDForView:self.window animated:YES];
}
How to use?
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication]delegate];
//Show Indicator
[appDel showGlobalProgressHUDWithTitle:#"Loading..."];
//Hide Indicator
[appDel dismissGlobalHUD];
Hope this helps.

Call function only after reloadData has finished

I have a tableView and need to perform a function once the tableView has been reloaded. How do I know if reloadData has finished? Lets say I have methodA that populates the tableView, and once [tableView1 reloadData] has been completed, I want to call methodB. Can anyone help me with this? I've been searching for hours... Thank you!
- (void) methodA
{
NSString *URLa = [NSString stringWithFormat:#"http://www.website.com/page.php?
v1=%#&v2=%#&v3=%#",v1, v2, v3];
NSURL *url = [NSURL URLa];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
(void) [[NSURLConnection alloc] initWithRequest:request delegate:self];
[tableView1 reloadData];
}
You can add a method/category on UITableView or even subclass UITableView:
-(void)reloadDataAndWait:(void(^)(void))waitBlock {
[self reloadData];//if subclassed then super. else use [self.tableView
if(waitBlock){
waitBlock();
}
}
And you need to use it as
[self.tableView reloadDataAndWait:^{
//call the required method here
}];
The problem is that you call reloadData immediately after starting the URL request. That does not make sense because the request has not been completed at that point.
The correct way would be to call reloadData and methodB in connectionDidFinishLoading, after you have updated your data source with the response from the URL request. At that point you know if the number of rows is zero or not, and there is no need to wait for the table view update to complete.
Once I get updated data from server for my table.
I use following code snip to Hide progress bar when table reload gets complete.
[UIView animateWithDuration:0 animations:^{
[your_table_view reloadData];
} completion:^(BOOL finished)
{
//Table reload completed TODO here
//Hide progress bar etc.
}];

Trying to reload a UItableview whenever my plist is updated?

I have a plist that is populated by the user within my app. I am trying to display the contents of that plist in a UITableView and it wont display until after the app is relaunched which means its not updating. Here is what I have:
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
NSString *path = [[NSBundle mainBundle] pathForResource:#"foodList" ofType:#"plist"];
arrayFood = [[NSMutableArray alloc]initWithContentsOfFile:path];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView reloadData];
NSString *path = [[NSBundle mainBundle] pathForResource:#"foodList" ofType:#"plist"];
arrayFood = [[NSMutableArray alloc]initWithContentsOfFile:path];
}
Thanks in advance!
From what i see, you should be calling
[self.tableView reloadData]
after you load the data from plist to foodArray in viewDidAppear...
Whenever you update the pList file simply call the self.tableView reloadData. Also, make sure that the data source you are using for your UITableView is updated. This datasource can be an NSDictionary, NSArray, NSMutableArray etc. The reloadData method of the tableView will trigger the cellforRowIndexPath method where you will create the UITableViewCell.
The best way to update the UITableView with newest data is to overwrite the setter method of data array.
For example your data array is arrayFood so you may have its property as strong or say retain
#property(nonanatomic,stron) NSMutableArray *arrayFood;
then you should overwrite its setter method.
-(NSMutableArray *)setArrayFood:(NSMutableArray *)array
{
//set your data array here and simply call reloadData
[self.tableView reloadData];
}
in that way your tableView always get update whenever your data array changed.
Also use NSNotifications for plist change and update your arrayFood there.
Using NSNotificationCenter may help you here.
In the ViewController that updates your plist, place this code when this update happens:
[[NSNotificationCenter defaultCenter] postNotificationName:#"plistDidUpdate" object:self];
In the ViewController you'll want to add this code to viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(plistDidUpdate:) name:#"plistDidUpdate" object:nil];
Also, add this function in the same ViewController:
-(void)plistDidUpdate:(id)sender{
[self.tableView reloadData];
}

UIActivityIndicatorView not spinning till I go back to Parent UITableView

I have an App using UITableViews and fetching data from a server. I am attempting to put a UIActivityIndicatorView on the Parent UITableView, so it spins while the Child UITableView is loading. I have the UIActivityIndicatorView all hookedup through Interface Builder, etc.
-(void)spinTheSpinner {
NSLog(#"Spin The Spinner");
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[spinner startAnimating];
[NSThread sleepForTimeInterval:9];
[self performSelectorOnMainThread:#selector(doneSpinning) withObject:nil waitUntilDone:NO];
[pool release];
}
-(void)doneSpinning {
NSLog(#"done spinning");
[spinner stopAnimating];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
NSMutableString *tempText = [[NSMutableString alloc] initWithString:[categoryNumber objectAtIndex:indexPath.row]];
Threads *viewController = [[Threads alloc] initWithNibName:#"newTable" bundle:nil tagval:tempText SessionID:PHPSESSID];
[self.navigationController pushViewController:viewController animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[viewController release];
}
So, when I push the Child UITableView onto the screen, the UIActivityIndicatorView (spinner) just sits there on the Parent UITableView. If I go to the Child UITableView and then quickly go back to the Parent View, I can catch the UIActivitIndicatorView in the act of spinning. The NSLogs are showing at the correct times - when I first tap the cell in the Parent UITableView, the "Spin The Spinner" appears in the Log. 9 seconds later, I get "done spinning" in the Log, but the spinner never spins unless I pop back to the Parent UITableView in time.
Ideas of why this is not working properly?
Perhaps the sleepForTimeInterval is blocking the animation.
Try removing the sleepForTimeInterval and replace performSelectorOnMainThread with a call to performSelector:withObject:afterDelay:.
let the ui display it outside thread:
[NSObject dispatchUI:^{
[spinner startAnimating];
}];

What's with [UITableView reloadData]?

I have an application that has a UITableView. This UITableView is populated by an NSMutableArray being held (as a property) in the appDelegate. You can think of this as an email window. It lists messages in a subclassed UITableViewCell. When a new message appears, I have all the code done which downloads the message, adds the data to the appDelegate's NSMutableArray which holds all of the messages. This code is working fine.
Now, once the new message is downloaded and added to the array, I am trying to update my UITableView using the following code, however - the UITableView's delegate functions do not get called.
The odd thing is when I scroll my UITableView up and down, the delegate methods finally get called and my section headers DO change (they show the message count for that section). Shoudn't they update in real-time and not wait for my scrolling to trigger the refresh? Also, the new cell is never added in the section!!
Please Help!!
APPDELEGATE CODE:
[self refreshMessagesDisplay]; //This is a call placed in the msg download method
-(void)refreshMessagesDisplay{
[self performSelectorOnMainThread:#selector(performMessageDisplay) withObject:nil waitUntilDone:NO];
}
-(void)performMessageDisplay{
[myMessagesView refresh];
}
UITableViewController Code:
-(void) refresh{
iPhone_PNPAppDelegate *mainDelegate = (iPhone_PNPAppDelegate *)[[UIApplication sharedApplication] delegate];
//self.messages is copied from appDelegate to get (old and) new messages.
self.messages=mainDelegate.messages;
//Some array manipulation takes place here.
[theTable reloadData];
[theTable setNeedsLayout]; //added out of desperation
[theTable setNeedsDisplay]; //added out of desperation
}
As a sanity check, have you verified that theTable is not nil at that point?
You could try putting a delay on the reloadData call - I had a similar problem when I was trying to get my tableview to update when reordering cells, except that the app crashed if I called reloadData during it.
So something like this might be worth a try:
Refresh method:
- (void)refreshDisplay:(UITableView *)tableView {
[tableView reloadData];
}
and then call it with (say) a 0.5 second delay:
[self performSelector:(#selector(refreshDisplay:)) withObject:(tableView) afterDelay:0.5];
Hope it works...
If you call reloadData from within a dispatched method, make sure to execute it on the main queue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0), ^(void) {
// hard work/updating here
// when finished ...
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self.myTableView reloadData];
});
});
..same in method form:
-(void)updateDataInBackground {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0), ^(void) {
// hard work/updating here
// when finished ...
[self reloadTable];
});
}
-(void)reloadTable {
dispatch_async(dispatch_get_main_queue(), ^(void) {
[myTableView reloadData];
});
}
Have you tried setting a breakpoint in your refresh method just to be sure your messages array has the correct content before calling reloadData?