I have a UITableView in a ViewController. The first time I run my application, table is empty. The table is filled, when I go through other views and I back to the view(which has a uitable). I use following solution, but it does not work. I put this part of code in ViewWillAppear and ViewDidLoad, but this is not working.
dispatch_async(dispatch_get_main_queue(), ^{
[self getthetablecontentfromserver];
[self.tableView reloadData]; // to reload selected cell
});
Anyone knows how can I populate the content of my table for the first time?
here it is the getthetablecontentfromserver method:
-(void)getthetablecontentfromserver{
NSURL *url = [NSURL URLWithString:#"http://exampledomain.com/data"];
config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
[[session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
data= [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
[self.tableView reloadData];
}
] resume];
}
You should put your call to getthetablecontentfromserver on a background thread. Once that completes send a NSNotification back to the viewController to call reloadData.
Related
I am trying to load image in my banner as well as URL for that banner and everything is just fine except it apears after a few seconds (10+ sec)
First I was thinking that it may be connection speed but if I hardcoded the line where image needs to apear it apears immediatly.
Here is what I am doing right now.
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:adUrl completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
adJson = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
adImageURL = [NSString stringWithFormat:#"%#", adJson[#"sponsor"][#"sponsor_image"]];
adUrlString = [NSString stringWithFormat:#"%#", adJson[#"sponsor"][#"sponsor_url"]];
[UIApplication sharedApplication].networkActivityIndicatorVisible=NO;
// Set adImage
[[self adBanner]setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:adImageURL]]]];
// Ad TapGuestures to adImage
UITapGestureRecognizer *adTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(adTapMethod)];
[[self adBanner]setUserInteractionEnabled:YES];
[[self adBanner]addGestureRecognizer:adTap];
}];
[dataTask resume];
As I said, if I do:
[[self adBanner]setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"example.com/img.png"]]]];
and put that code out of NSURLSessionDataTask it apears in no second.
What am I doing wrong and how to get image apear as fast as possible?
The only delays would be from connection & then from the device updating it's display. Remember that the direct call with dataWithContentsOfURL forces the main queue to wait on the image before doing anything. If you execute it with a NSURLSession it would naturally take a bit longer since it isn't set as a high priority.
You should include setImage: as such.
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:adUrl completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
adJson = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
adImageURL = [NSString stringWithFormat:#"%#", adJson[#"sponsor"][#"sponsor_image"]];
adUrlString = [NSString stringWithFormat:#"%#", adJson[#"sponsor"][#"sponsor_url"]];
[UIApplication sharedApplication].networkActivityIndicatorVisible=NO;
// Set adImage
dispatch_async(dispatch_get_main_queue(), ^{
[[self adBanner] setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:adImageURL]]]];
// Ad TapGuestures to adImage
UITapGestureRecognizer *adTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(adTapMethod)];
[[self adBanner] setUserInteractionEnabled:YES];
[[self adBanner] addGestureRecognizer:adTap];
});
}];
[dataTask resume];
I am working on a project where I call one method from another. In the 2nd method I fetch data from a server using a NSURLSession. When the 2nd method returns the NSData to the first method, the data is converted into JSON and then returned to the viewcontroller that made the inital call on the first method. The problem I am having is that the first method is returning a null object because the NSData doesn't load fast enough. I'm not sure what to do about it.
Here is the code:
Method 1
-(NSDictionary*)returnJsonDictionaryFromUrl:(NSURL*)url {
NSData *data = [self makeHttpRequestWithUrl:url];
//NSLog(#"Data is: %#", data);
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions error:&error];
return json;
}
Method 2
-(NSData*)makeHttpRequestWithUrl:(NSURL*)url {
if (!_data) {
_data = [[NSData alloc]init];
}
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:url
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
_data = data;
}] resume];
return _data;
}
Thanks in advance for any suggestions!
So blocks by default are skipped over in execution and queued up (sometimes on other threads). This means when you're returning a variable that you had just set in a block, you should assume the block has not been executed and any variables you set inside it will not be set until much later.
The best way to handle this is to pass in the completion block to the method. So instead of having it return the NSData pointer, instead make it a void and simply pass in the completion block to that. This will be your request method:
-(void)makeHttpRequestWithUrl:(NSURL*)url completion:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completion {
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:url
completionHandler:completion] resume];
}
And you'll call it like this:
[object makeHttpRequestWithUrl:url completion:^(NSData *data, NSURLResponse *response, NSError *error) {
// handle error
// use data
}
If you really must do a synchronous web request NSURLSession is not the right tool. For this we have NSURLConnection sendSynchronousRequest:returningResponse:error:. And if you don't even care about the HTTP response you also can use NSData dataWithContentOfURL:.
But you really shouldn't use those, especially on the main thread as this will cause your app to crash if the network request takes too long. Embrace the asynchronous nature of network requests and handle your data in the completion block as kpsharp suggests in his answer.
I am trying to fetch a JSON-feed but somehow the command is never executed. I have placed a NSLog just before the session gets called and that actually gets output on the console. The NSLog later "test" never gets output. I can't find out where the problem is. Another JSON request works just fine. Here is the code:
NSLog(#"fetchClassified started!");
// connect to webserver and ask for the feed
NSURL *url = [NSURL URLWithString:#"http://test.server/services/rest/v1/interface2?id=22"];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
// create a task that transfers the feed from the server
NSURLSessionTask *dataTask = [self.session dataTaskWithRequest:req
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data
options:0 error:nil];
NSLog(#"test %#", jsonObject);
self.classified = jsonObject[#"tasks"];
NSLog(#"%#", self.classified);
// put the output on the main queue (UI has to run always on main thread)
dispatch_async(dispatch_get_main_queue(), ^{
self.textView.text =self.classified;
});
}
];
[dataTask resume];
Thank you in advance for any hint on this!
JoeFryer solved it. self.session was nil.
I'm relatively new to iOS development but I'm working on an application to get a better understanding of development. I'm working with a web service and want to check the credentials a user enters. To do this I am making a simple get request with their credentials and then checking the http status for 200. Here is my code below:
-(BOOL)checkCredentials:(NSString *)username withPassword:(NSString *)password{
NSString *requestString = #"SOME URL";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSData *userPasswordData = [[NSString stringWithFormat:#"%#:%#", username, password] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
NSString *authString = [NSString stringWithFormat:#"Basic %#", base64EncodedCredential];
NSURLSessionConfiguration *sessionConfig=[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.HTTPAdditionalHeaders=#{#"Authorization":authString};
self.session=[NSURLSession sessionWithConfiguration:sessionConfig];
__block BOOL success = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(!error){
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
success = YES;
}
}
NSMutableDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonObject);
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[dataTask resume];
return success;
}
I was going to use a semaphore to wait for the block to complete so I can check the status code and then return. But first it seems like my code just hangs, and I think that because I don't have a release, but that's not allowed with ARC. I'm not sure why it's hanging. Is there a better way to wait for the block to complete (without a semaphore) so I can return whether my credentials are valid?
Also is there a better way to pass the username and password so that it's not possible for someone to spoof the username and password?
Any help would be greatly appreciated.
Think simple!
Make your own completionHandler so that you won't deal with the return anymore, the caller will take the responsibility of result verification instead.
There's one thing you need to keep in mind, that if you want to modify anything related to UI (User Interface), you need to dispatch your completion block to main queue or you will get unexpected behavior, see more detail here.
Change your return type to void and add a completion block:
-(void)checkCredentials:(NSString *)username withPassword:(NSString *)password completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))myCompletion
{
NSString *requestString = #"http://google.com";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// Here you return exactly what the NSURLSessionDataTask downloaded
// and pass it to the caller as an another completion block
myCompletion(data, response, error);
}];
[dataTask resume];
}
Caller's code, I assume that self is the caller:
[self checkCredentials:#"" withPassword:#"" completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(!error){
// Result verification's here
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
NSLog(#"SUCESS");
}
}
}];
You code stops waiting for a semaphore and [dataTask resume] is never executed.
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); <=== waits here
[dataTask resume]; <=== never reached
I'd suggest not using the semaphore here. Do the work in your block instead.
As to username/password. If you worry about spoofing then SSL layer on top of HTTP is the answer.
This is a really dangerous pattern, because this call is going to block until the network request completes. If this is on the main thread, your app will stop responding and the watchdog may kill you.
That warning aside, the reason the block doesn't complete is because the network task is never started. You trap on your semaphore before you call resume, so your task never runs. I would also, personally use a dispatch_group to do the waiting.
To make it better, you would need to rewrite it asynchronously. Basically have your app continue to function, maybe disable the inputs, until the call completes, then run a block to re-enable them, or show an error:
// Assume your login button and whatever are exposed as properties here
self.loginButton.enabled = NO;
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(!error){
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
success = YES;
}
}
NSMutableDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonObject);
// Need to be back on the main queue, the call is complete
self.loginButton.enabled = YES;
}];
[dataTask resume];
Or, just to keep it the way you have it, but resolve the immediate issue, re-order your trap so that it happens after the task resumes:
[dataTask resume];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); // might want to time out here instead of waiting forever
return success;
In my application I'm calling a method asynchronously by a button press. The screen segues to a different view controller which is a table view.
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
{
NSData *data = [[NSData alloc] initWithContentsOfURL:location];
NSArray *array = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
self.yearBucket = [NSMutableArray array];
for (NSDictionary * dict in array) {
Year *year = [[Year alloc ]init];
year.yearName =[dict objectForKey:#"Year"];
year.speeches = [dict objectForKey:#"Speeches"];
[self.yearBucket addObject:year];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
...
The problem is that the UI moves into a blank table view, since the data takes time to load. How can I display and alert view on the next screen that dynamically displays the loading of the data and can is dismissed as soon as the view refreshes back to the main thread.
I would suggest you display an MVProgressHUD on your view whilst you load the data and then perform a [tableview reloadData] in order to update the data shown in the table.
You should probably also display a network activity indicator. This is simply good practise when network connectivity is occurring. I presume if your app relies on an internet connection you are checking for connectivity before allowing the user to begin a request?