Populate a UITableView with data retrieved from a NSURLConnection - ios

Basically I have a main screen that has a button on it, when you click on that button I want to load a list of users from a server and display them on the next screen in the tableview.
I can get the data with no issues, and pass it to my tableview with no issues - my problem lies with loading the data into the cells after I have received the data!
Processes exist like this:
Tap button
Starts NSURLConnection
Opens up UITableView on screen
Loads nothing
Data returns, adds to NSArray
Tableview Reload
In viewWillAppear - make local users NSArray equal received Data
Nothing loads.
If I then press back, then press the button again, all my cells are populated with the data I received before.
Thanks in advance for any help. I've been searching around for a while now :(
Edit:
When moving from main screen to the tableview
Note: getUsers sets up the NSURLConnection and starts the connection
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:#"selectUser"]) {
DDNetworkRequest *networkRequest = [[DDNetworkRequest alloc] init];
[networkRequest getUsers];
}
}
Tableview class:
Note: returnUserList just returns the array of data which is set as a variable within the network class
-(void)viewWillAppear:(BOOL)animated{
DDNetworkRequest *networkRequest = [[DDNetworkRequest alloc] init];
users = [networkRequest returnUserList];
}
Network Class:
note: returnUsers manipulates and then saves the NSArray variable
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSDictionary *jsonDictionary;
jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
[usersJSONDict isEqualToDictionary: jsonDictionary];
[self returnUsers:jsonDictionary];
DDUserMessage *userMessageTable = [[DDUserMessage alloc] init];
[userMessageTable.tableView reloadData];
}
** Edit2 **
returnUser method
-(void) returnUsers:(NSDictionary*)userDict{
userListArray = [userDict valueForKey:#"username"];
}

Without any code provided from your side, I guess u forgot this awesome operation inside the HTTP get request success block:
[self.tableView loadData];
M I RIGHT? ;)

After you receive the data, you have to pass it to the data source array, then call
[self.tableView reloadData];
Also make sure you call this line from the main thread. You cannot make changes to UI from another thread.

Related

Popup/Alert between two ViewController Segue

I have two ViewController and use a (tableview click) seque for opening the second ViewController.
My Problem is, the Second View Controller load much Data. So the time between switch is <> 10 Seconds. In this 10 Seconds the App freeze. Thats OK, but HOW can i insert a "Popup" or "Alert" Message like "Please Wait..." BEVOR . I have testing much tutorials for Popups and Alerts, but the Popup/Alter shows only, when the SecondView Controller is complete loaded. I will show the Message BEVOR the SecondViewController is compled loaded.
Example:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// IF i set here the ALERT, the Alter was only show, when the Second View Controller is complete loaded!
NSDictionary *rowVals = (NSDictionary *) [SearchNSMutableArray objectAtIndex:indexPath.row];
[self performSegueWithIdentifier:#"Foo" sender:self];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"Foo"]) {
// Get indexpath from Tableview;
NSIndexPath *indexPath = [self.SearchUITableView indexPathForSelectedRow];
// Get Data from Array;
NSDictionary *rowVals = (NSDictionary *) [self.SearchNSMutableArray objectAtIndex:indexPath.row];
// Destination View;
[MySecondViewController alloc];
MySecondViewController *MyView = (MySecondViewController *)segue.destinationViewController;
}
}
You are trying to fix the problem with the wrong solution. That solution is just as bad because the popup will also freeze for 10 seconds. What if you add more data and it takes 30 seconds or 10 minutes? Are you going to expect your users to see a dialog they can't dismiss for 10 minutes?
Are you fetching the data from the internet? If so you need to fetch your data asynchronously in the background.
If you're loading it from disk then there's too much being loaded that could possibly be displayed on one screen, you need to load only a small portion of it, and if that still takes a long time you need to load it asynchronously.
UPDATED -
You should have a model class for your application that is responsible for fetching the data from the internet.
Google Model View Controller to get some background information on what a Model is.
As soon as the app launches the model can start to download the data which needs to be down in the background (that's too big a topic to answer how to do that here).
The View controller can launch while the data is being downloaded and it can display a spinning activity indicator wheel or progress bar or dialog etc. while waiting. The important thing is the GUI will not freeze.
When the model has downloaded the data it needs to tell the view controller the data is now available, which it can do using NSNotification center.
There's lots for you to investigate and learn, to do it without GUI freezing it needs to be done properly, there's no shortcut, you have a lot to study.
#Martin,
i found a solution:
// Send the Request;
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
So the request are asynchrony. Thanks for your answer. Great +1

Problems reloading data in a tableView

I am trying to reload data in a tableview based on a users account permissions whenever they log in.
The two classes involved in this are:
mainViewController and menuViewController
Currently I am able to use
[self.tableView reloadData];
To reload the data when called within the viewWillAppear method. Which is no good for me since the user hasn't logged in when the view loads so there is no data to populate the table at this point.
I have created a method called populateTable in menuViewController.h which I am calling in the mainViewController.m file on button press using the following;
(IBAction)Reload:(id)sender {
menuViewController *mvc = [[menuViewController alloc]init];
[mvc populateTable];
}
This seems to work correctly as I have an NSLog within the populateTable method which executes. However the reloadData does not work.
Here is my populateTable method;
-(void)populateTable {
self.section1 = [NSMutableArray arrayWithObjects:#"test settings", #"test", #"test",#"Users and access",#"No-track IPs", nil];
self.section2 = [NSMutableArray arrayWithObjects:#"Rules", #"Channels",#"Goals",#"Pages", nil];
self.menu = [NSMutableArray arrayWithObjects:self.section1, self.section2, nil];
[self.tableView reloadData];
NSLog(#"Reloading data");
}
Can you guys help me out here, I have been staring at this all day and getting nowhere, thanks!
From my experience this is likely a problem with timing - the IBOutlet of self.tableView is not ready when you call reloadData on it (add an NSLog and see for yourself - it is nil when called).
To solve this, the populateTable method must be called within the UIViewController's viewDidLoad method. This guarantees that the outlets are not nil and that everything is ready for your data population.
Also, you should not instantiate your MenuViewController with [[MenuViewController alloc] init] but using the storyboard's instantiateViewControllerWithIdentifier.
Your problem is this line,
menuViewController *mvc = [[menuViewController alloc]init];
This creates a new instance of menuViewController, not the one you see on screen. You need to get a reference to the one you have, not create a new one. How you get that reference depends on how, when, and where your controllers are created.

How can I wait for a NSURLConnection delegate to finish before continuing?

I am trying to display my remotely configured settings in a modal view after the application launches. Everything is hooked up properly, but the view updates its labels before the configs object is updated from its NSURLConnection delegate methods.
I'm looking for a solution that will let the delegate methods finish before I try to update the view. I would rather not put the functionality in the delegate methods themselves so I can use the MYRemoteConfig in other situations.
I suspect the solution is obvious, but I've gone braindead from looking at this for too long.
In viewDidAppear{} in MYSettingsViewController.m
MYRemoteConfig* config = [[MYRemoteConfig alloc] init];
[configs updateSettings]; // I need these delegate methods to be done
// before the next line
self.customerLabel.text = configs.customer; // Updates with empty box
self.courseLabel.text = configs.course;
-
updateSettings{} in MYRemoteConfig.m
// code that gets uuid and sets up post request //
NSURLConnection* connection = [NSURLConnection connectionWithRequest:request
delegate:self];
[connection start];
NSLog(#"Connection should have started.");
then in connectionDidFinishLoading{}: (after appending data to local var)
// pull JSON objects into dictionary
[self updateProfile:settingsDictionary;
NSLog(#"%#", settingsDictionary); //works
updateProfile{}:
// code that sets config attributes in singleton object //
self.customer = [settings objectForKey:#"Customer"]; // I need this data
self.course = [settings objectForKey:#"Course"]; // in my view controller
You should make MYSettingsViewController the delegate of MYRemoteConfig controller, create a delegate protocol in MYRemoteConfig, and call the method you create in that protocol in the connectionDidFinishLoading method. The implementation of that method in MYSettingsViewController would then update the customer and course labels.

iOS: table view created before xml parsing complete

OK I'm hoping I'm missing something basic here - I am not very expert at this. It should be self-explanatory without example code:
I parse a web-hosted xml file consisting of a list of titles to be displayed in a tableView and associated URLs to pass to a webView when a cell is selected. The parsing happens in the tableView into a dictionary. If I parse on the main thread it works nicely but I'm worried about hanging the UI if the signal is poor. So I wrap the parsing call in a dispatch queue as per examples on here and now it presents an empty table. But if I go back up the view hierarchy and try again (it's embedded in a navigation controller) then it works, there is my table fully populated.
I'm assuming that by using a secondary thread somehow the table is created before the content array is populated. How do I get round this?
Thanks! Andrew
Implement the - (void)parserDidEndDocument:(NSXMLParser *)parser delegate method of NSXMLParser. And call reloadData of your tableView from that method.
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
dispatch_sync(dispatch_get_main_queue(), ^{
[yourTable reloadData];
});
}
Refer NSXMLParserDelegate
If you pares in a dispatch queue you have to update the UI on the main queue.
I am doing something similar. Here is my code:
dispatch_queue_t imgDownloaderQueue = dispatch_queue_create("imageDownloader", NULL);
dispatch_async(imgDownloaderQueue, ^{
NSString *avatarUrlString = [avatarImageDictionary objectForKey:#"url"];
avatarImage = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:avatarUrlString]]];
dispatch_sync(dispatch_get_main_queue(), ^{
id asyncCell = [self.tableView cellForRowAtIndexPath:indexPath];
[[asyncCell avatarImageView] setImage:avatarImage];
});
});

Passing a value from NSObject class back to UIViewController

I don't know why this is being so difficult but I can't get this to work. Here's my basic flow:
I have a UIViewController with a subview UIView which itself has a subview UIButton. Clicking the button instantiates a new instance of a NSObject called TwitterController, creates a NSURL for the twitter feed and then hands control over to TC to do the URLConnection and serialize the data returned.
Here's the relevant code in ViewController (Pruit_Igoe is me, feel free to follow though I don't post much : D) :
- (void) getTwitter {
//load new manager
twitterManager = [TwitterController new];
[twitterManager showTwitterFeed:vTwitterFeed:self];
NSURL* twitterFeedPath = [NSURL URLWithString: #"http://api.twitter.com/1/statuses/user_timeline.json?screen_name=Pruit_Igoe"];
[twitterManager getTwitterFeed:twitterFeedPath];
//toggle the twitter view
[self toggleView:vTwitterFeed];
[self toggleView:vContactCard];
}
showTwitterFeed dumps the objects in the view vTwitterFeed (button to close the view, images, etc.)
getTwitterFeed begins the NSURLConnection process
in TwitterController, I get the twitter feed and process it here:
- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection {
//do something with the data!
NSError *e = nil;
//parse the json data
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData: receivedData options: NSJSONReadingMutableContainers error: &e];
//dump it into an array
tweetArray = [[NSMutableArray alloc] init];
for(NSDictionary* thisTweetDict in jsonArray) {
NSString* tweet = [thisTweetDict objectForKey:#"text"];
[tweetArray addObject:tweet];
}
}
this all works fine, log tweetArray and all the text is there, log thisTweetDict and all the ton of data Twitter sends back is there. The problem is I want to pass tweetArray back to ViewController but I can't seem to figure out how to.
I've done the following:
Tried returning TweetArray from getTwitterFeed but it came back as null (my guess is the method returned the array before the connection had finished)
Tried to put it in UserDefaults but I keep getting null (same guess as above, but then I put it in connectionDidFinish and still null)
Tried to pass a reference to ViewController to TwitterController and then call a method in VC to pass the array to but in TwitterController I error out because it says my instance of VC doesn't recognize the selector. (It's there, I've triple checked).
I am sure this is simple and I am just being dense but could someone help me with this?
Edit: Here's how I tried to pass it back to VC:
I would pass VC to TC using this method (this is in VC)
[twitterManager showTwitterFeed:vTwitterFeed:self];
in VC.h I had a UIViewController* thisViewController
in VC.m in the showTwitterFeed:
- (void) showTwitterFeed : (UIView* ) theTwitterView : (UIViewController* ) theViewController {
thisViewController = theViewController;
//...other code to build view objects
then in
- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection {
...
for(NSDictionary* thisTweetDict in jsonArray) {
NSString* tweet = [thisTweetDict objectForKey:#"text"];
[tweetArray addObject:tweet];
}
[thisViewController getTwitterFeed:tweetArray]; //<--this would error out saying selector not available
back in VC.h
- (void) getTwitterFeed : (NSArray* ) theTwitterFeed;
and in VC.m
- (void) getTwitterFeed : (NSArray* ) theTwitterFeed {
NSLog(#"%#", theTwitterFeed);
}
You can't return it from getTwitterFeed because connectionDidFinishLoading has not been called yet.
What you need to do is set up a protocol in your TwitterController and make your ViewController the delegate of the TwitterController.
Then when connectionDidFinishLoading occurs and you save the twitter information you can call the function back to your delegate (the ViewController).
Create a function called something like twitterDataReceived:(NSDictionary *)tweetDict in the TwitterController protocol and call it in the connectionDidFinishLoading function: [self.delegate twitterDataReceived:thisTweetDict]; or if you just want to send the text make it twitterTextReceived:(NSString *)theTweet and call [self.delegate twitterTextReceived:tweet]; or use an array like twitterArrayReceived:(NSArray *)tweetArray and [self.delegate twitterArrayReceived:tweetArray];, or whatever you want to send back.
If you are unfamiliar with setting up a protocol and a delegate there are many questions available which will help you out, like this one:objective-c protocol delegates
You get an unrecognized selector because thisViewController is of type UIViewController which does not have this method defined (Although its not seen from the code you posted I am absolutely sure this is the case as you would get a compilation error when assigning thisViewController = theViewController and theViewController type is UIViewController)
So change thisViewController type to your customized view controller and also change the signature of
(void) showTwitterFeed : (UIView* ) theTwitterView : (UIViewController* ) theViewController
to be :
(void) showTwitterFeed : (UIView* ) theTwitterView : (<Your custom controller type>* ) theViewController

Resources