I've put the code below in, and it looks close but I can't figure out why it's not doing the actual refresh. It has the "Pull to refresh" and the updated text displaying properly, but it's not updating the actual data. Am I missing something obvious, or do I have it misplaced or something?
I edited to add the self tableview call to reload the data. Still no luck.
FINAL EDIT___User below solved it with calling the data feed.
- (void)viewDidLoad
{
[super viewDidLoad];
UIRefreshControl *refresh = [[UIRefreshControl alloc] init];
refresh.attributedTitle = [[NSAttributedString alloc] initWithString:#"Pull to refresh"];
[refresh addTarget:self action:#selector(refreshmytable:) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refresh;
NSURLSessionConfiguration *config =
[NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config
delegate:self
// delegate:nil
delegateQueue:nil];
[self fetchFeed];
}
- (void)refreshmytable:(UIRefreshControl *)refreshControl{
[self fetchFeed]; //Added 12:12 9.16.14
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Updating"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMM d, h:mm a"];
NSString *updated = [NSString stringWithFormat:#" Last Update: %#", [formatter stringFromDate:[NSDate date]]];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:updated];
[refreshControl endRefreshing];
[self.tableView reloadData]; //Added this 11:32 9.16.14
}
- (void)fetchFeed
{
NSString *userEID = MAP_getUsername();
//NSLog(userEID);
NSString *requestString1 = [#"URL" stringByAppendingString:userEID];
NSString *requestString2 = #"&status=pending";
NSString *requestString = [requestString1 stringByAppendingString:requestString2];
//NSLog(requestString);
/*NSString *requestString = #"http://URL";
*/
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithRequest:req
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
self.changeList = jsonObject[#"List"];
//self.changeList=nil; //tried to add here to remove duplicate data
NSLog(#"%#", self.changeList);
//- add code here to populate BNRItemStore with the change order list.
// - following code should be rewritten in fetchFeed that will load BNRItemStore.
if (self.changeList.count>0) {
for (int i = 0; i < self.changeList.count; i++) {
NSDictionary *coItem = self.changeList[i];
[[BNRItemStore sharedStore]
addItemWithApproverEid:coItem[#"approverEid"]
assignmentGroup:coItem[#"assignmentGroup"]
changeOrder:coItem[#"changeOrder"]
subcategory:coItem[#"subCatagory"]
title:coItem[#"title"]
];
}
}
//NSLog(#"sizeof(NSInteger) = %#", #(sizeof(NSInteger)));
//- end comment
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
//self.changeList=nil; //trying to null out list for refresh non duplicate data
// NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// NSLog(#"%#", json);
}];
[dataTask resume];
}
You are not fetching the new data. You have a method/message call fetchFeed that you call in the viewDidLoad but you never call it in the refresh method/message. I assume that if you refresh, then you need to fetch new data. Call `[self fetchFeed];' before reloading the table view. If you are fetch the data asynchronously, then you need to have the table view reload in the completion block when fetching the new data is complete.
You need to call to reloadData somewhere in refreshmytable method for the table view to update the data
- (void)refreshmytable:(UIRefreshControl *)refreshControl{
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Updating"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMM d, h:mm a"];
NSString *updated = [NSString stringWithFormat:#" Last Update: %#", [formatter stringFromDate:[NSDate date]]];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:updated];
[refreshControl endRefreshing];
[self.tableView reloadData]
}
Related
Doing a check to get the current time and date from Google. The first option works although not the best way to do this as it's using a depreciated method and waiting for everything to finish with the synchronous method is not good UX.
-(NSDate*)timeAndDateFromWeb{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:[NSURL URLWithString:#"https://google.co.uk"]];
[request setHTTPMethod:#"GET"];
NSHTTPURLResponse *httpResponse = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&httpResponse error:nil];
NSString *dateString = [[httpResponse allHeaderFields] objectForKey:#"Date"];
DebugLog(#" *** GOOGLE DATE: %# ****",dateString);
if (httpResponse){
hasDataConnection = YES;
}
else{
hasDataConnection = NO;
}
// Convert string to date object
NSDateFormatter *dateformatted = [NSDateFormatter new];
[dateformatted setDateFormat:#"E, d MMM yyyy HH:mm:ss zzz"];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en"];
[dateformatted setLocale:locale];
return [dateformatted dateFromString:dateString];
}
Trying to adapt it is almost there although I'm returning nil for my date string: [dateformatted dateFromString:dateString];
NSURL *url = [NSURL URLWithString:#"https://google.co.uk"];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSHTTPURLResponse *httpResponse = nil;
NSString *dateString = [[httpResponse allHeaderFields] objectForKey:#"Date"];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
hasDataConnection = NO;
//NSLog(#"\n\n ----> Not connected Error,%#", [error localizedDescription]);
}
else {
//NSLog(#"\n\n -----> connected: %#", [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
hasDataConnection = YES;
}
}];
// Convert string to date object
NSDateFormatter *dateformatted = [NSDateFormatter new];
[dateformatted setDateFormat:#"E, d MMM yyyy HH:mm:ss zzz"];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en"];
[dateformatted setLocale:locale];
DebugLog(#" *** GOOGLE DATE: %# ****",[dateformatted dateFromString:dateString]);
return [dateformatted dateFromString:dateString];
When you're switching from sync to async, can't return value the same way as before.
When you call sendAsynchronousRequest method, it starts a background task, but your thread continues work instantly. That's why both httpResponse and dateString are null.
So instead of that you should change your return type to void, because you can't return result instantly, and add a callback, which will be run when the job is done. And process your formatting in the completion of the task too:
- (void)timeAndDateFromWeb:(void (^)(NSDate *))completion {
NSURL *url = [NSURL URLWithString:#"https://google.co.uk"];
NSURLSessionTask *task = [NSURLSession.sharedSession
dataTaskWithURL:url
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
// hasDataConnection = NO;
//NSLog(#"\n\n ----> Not connected Error,%#", [error localizedDescription]);
}
else if ([response isKindOfClass:NSHTTPURLResponse.class] ) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSString *dateString = [[httpResponse allHeaderFields] objectForKey:#"Date"];
// Convert string to date object
NSDateFormatter *dateformatted = [NSDateFormatter new];
[dateformatted setDateFormat:#"E, d MMM yyyy HH:mm:ss zzz"];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en"];
[dateformatted setLocale:locale];
// DebugLog(#" *** GOOGLE DATE: %# ****",[dateformatted dateFromString:dateString]);
completion([dateformatted dateFromString:dateString]);
//NSLog(#"\n\n -----> connected: %#", [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
// hasDataConnection = YES;
}
}];
[task resume];
}
And so the result of this method will be returned to the block, same as result of the background task.
Don't forget, that it will be called on the background thread, same as the download task completion block. So if you wanna change some UI you need to move back to the main thread:
[self timeAndDateFromWeb:^(NSDate *date) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", date);
// UI changes
});
}];
Alternatively you can move it to the main thread inside your function, right before returning the result:
NSDate *date = [dateformatted dateFromString:dateString];
dispatch_async(dispatch_get_main_queue(), ^{
completion(date);
});
I'm stuck with an issue on how to best load a couple of UILabels asynchronously.
Here is my cellForRowAtIndexPath method:
UPDATE:
Based on the answer below, I've made changes:
Here is my new cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Retrieve cell
NSString *cellIdentifier = #"BuildingItem";
BuildingsTableViewCell *cell = [[tableView dequeueReusableCellWithIdentifier:cellIdentifier] initWithFrame:CGRectMake(10, 10,580, 100)];
// Get the area to be shown
Buildings *item = _feedItems[indexPath.row];
NSURL *MyURL = [NSURL URLWithString:item.buildingPointerImage];
UIImage *placeholder = [UIImage imageNamed:#"placeholder"];
NSString *path = [MyURL absoluteString];
NSString *key = [path MD5Hash];
[cell.buildingImageView loadImageFromURL:(NSURL*)MyURL placeholderImage:(UIImage*)placeholder cachingKey:(NSString*)key];
cell.buildingName.text = item.buildingPointerName;
cell.buildingYear.text = item.buildingPointerYear;
NSString *URL = #"http://rets.miamiresidential.com/ios/condos/buildings.php?action=get_range";
NSString *streetNumberURL = [NSString stringWithFormat:#"&street_number=%#",item.buildingStreetNumber];
NSString *streetNameURL = [NSString stringWithFormat:#"&street_name=%#",item.buildingStreetName];
NSString *ZipcodeURL = [NSString stringWithFormat:#"&zipcode=%#",item.buildingZipcode];
NSString *P1 = [URL stringByAppendingString:streetNumberURL];
NSString *P2 = [P1 stringByAppendingString:streetNameURL];
NSString *P3 = [P2 stringByAppendingString:ZipcodeURL];
NSURL *jsonFileUrl = [NSURL URLWithString:P3];
NSLog(#"%#",P3);
[cell.buildingSalesRange loadSalesRangeFromURL:(NSURL*)jsonFileUrl];
[cell.buildingRentalsRange loadRentalsRangeFromURL:(NSURL*)jsonFileUrl];
cell.layer.borderColor = [[UIColor whiteColor]CGColor];
cell.layer.backgroundColor = [[UIColor clearColor]CGColor];
cell.layer.borderWidth = 2;
return cell;
}
and here is my new .m file:
#import "PriceRanges.h"
#import <objc/runtime.h>
#implementation UILabel(Prices)
-(void) loadSalesRangeFromURL:(NSURL*)url {
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
NSNumberFormatter *currencyStyle = [[NSNumberFormatter alloc] init];
[currencyStyle setNumberStyle:NSNumberFormatterCurrencyStyle];
[currencyStyle setLocale:locale];
[currencyStyle setMaximumFractionDigits:0];
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:url];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (error)
{
NSLog(#"Error,%#", [error localizedDescription]);
}
else
{
NSArray *priceArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
NSNumber *number = [priceArray valueForKeyPath:#"sales.number"];
NSString *lowest = [priceArray valueForKeyPath:#"sales.lowest"];
NSDecimalNumber *lowestDecimal = [NSDecimalNumber decimalNumberWithString:lowest];
NSString *lowestPrice = [currencyStyle stringFromNumber:lowestDecimal];
if (![number isEqualToNumber:[NSNumber numberWithInt:0]]) {
dispatch_async(dispatch_get_main_queue(), ^{
UILabel *labelFromData = [[UILabel alloc] init];
[labelFromData setText:[NSString stringWithFormat:#"%# for Sale from %#",number,lowestPrice]];
if (labelFromData) {
if ([self.text isEqualToString:labelFromData.text]) {
} else {
dispatch_async(dispatch_get_main_queue(), ^{
self.text = labelFromData.text;
});
}
}
self.text = [NSString stringWithFormat:#"%# for Sale from %#",number,lowestPrice];
});
}
};
}];
}
-(void) loadRentalsRangeFromURL:(NSURL*)url {
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
NSNumberFormatter *currencyStyle = [[NSNumberFormatter alloc] init];
[currencyStyle setNumberStyle:NSNumberFormatterCurrencyStyle];
[currencyStyle setLocale:locale];
[currencyStyle setMaximumFractionDigits:0];
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:url];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (error)
{
NSLog(#"Error,%#", [error localizedDescription]);
}
else
{
NSArray *priceArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
NSNumber *number = [priceArray valueForKeyPath:#"rentals.number"];
NSString *lowest = [priceArray valueForKeyPath:#"rentals.lowest"];
NSDecimalNumber *lowestDecimal = [NSDecimalNumber decimalNumberWithString:lowest];
NSString *lowestPrice = [currencyStyle stringFromNumber:lowestDecimal];
if (![number isEqualToNumber:[NSNumber numberWithInt:0]]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setText:[NSString stringWithFormat:#"%# for Rent from %#",number,lowestPrice]];
});
}
}
}];
}
#end
As you might see, I have two different methods, but both still send the async request everytime the tables are scrolled.
I guess I am not clear on what to do now?
That's the problem with not having a data model independent of your UI, and a good example of when Model-View-Controller makes sense. You'll need another layer (preferably a separate class) responsible for keeping all the data you've fetched from the network, deciding when it's old enough to discard, etc. The cells should populate themselves from the cached data that the Model keeps, and if the data's not yet present, the model fetches it asynchronously and then notifies the View Controller when the new data arrive. If the cells are still visible, they get populated. If they've scrolled offscreen, been reused, etc, then the data updates don't result in any immediate UI change.
i've made following Asynchronous request, the problem is that its empty i've tried in the bottom NSLog the fixtures where its empty. I've checked that the nsstring home, away, league and so on returns values and it does. How come the values are not added to the fixtures NSMutableArray
[ProgressHUD show:#"Loading..."];
NSURL *url = [NSURL URLWithString:#"API_URL"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
int subObjects = ((NSArray *)jsonResult[#"match"]).count;
for (int i = 0; i <= subObjects-1; i++) {
NSString *date = [NSString stringWithFormat:#"%# %#",[[[jsonResult valueForKey:#"match"] valueForKey:#"playdate"] objectAtIndex:i], [[[jsonResult valueForKey:#"match"] valueForKey:#"time"] objectAtIndex:i]];
NSString *identifier = [[NSLocale currentLocale] localeIdentifier];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setTimeZone: [NSTimeZone timeZoneWithName:#"US/Arizona"]];
[df setLocale:[NSLocale localeWithLocaleIdentifier:identifier]];
[df setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate *myDate = [df dateFromString:[NSString stringWithFormat:#"%#", date]];
NSArray *items = [[NSString stringWithFormat:#"%#", myDate] componentsSeparatedByString:#" "];
NSString *home = [[[jsonResult valueForKey:#"match"] valueForKey:#"hometeam"] objectAtIndex:i];
NSString *away = [[[jsonResult valueForKey:#"match"] valueForKey:#"awayteam"] objectAtIndex:i];
NSString *league = [[[jsonResult valueForKey:#"match"] valueForKey:#"league"] objectAtIndex:i];
[fixtures addObject:
[[NSMutableDictionary alloc] initWithObjectsAndKeys:
items[0], #"date",
items[1], #"time",
home, #"home",
away, #"away",
league, #"league",
nil]];
[sections addObject:
[[NSMutableDictionary alloc] initWithObjectsAndKeys:
items[0], #"date",
nil]];
}
}
];
[self.theTableView reloadData];
[ProgressHUD dismiss];
NSLog(#"%#", fixtures);
The problem is that the request is an asynchronous function
If the function is asynchronous, the function will create another thread and return immediately to execute the next line after the one that invoked the asynchronous function. Meanwhile the new thread will execute some code and, eventually execute the block passed as parameter, and finally the thread is killed and doesn't exist any more.
This means that
NSLog(#"%#", fixtures);
will most likely be executed before the sendAsynchronousRequest has finished it's job, that's why it is returning nil.
Everything you need to do to process the downloaded information should happen inside the completionHandler block, including the call to [self.theTableView reloadData];
It is a non-blocking operation. It means that by calling this method it returns immediatelly while the actual request is performing somewhere in background and then calls the handler block on queue specified in queue parameter.
You should reload tableview from the completion-handler block.
// 1 before request
NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
// 3 request completed
// some processing
...
[self.theTableView reloadData];
[ProgressHUD dismiss];
}
// 2 immediate return
update
Although you passing the main queue as queue parameter the handler block will be performed on next run loop iteration after you reloading table and logging the values.
// current run loop iteration
NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
// next run loop iteration
}
// current run loop iteration
if(!fixtures) {
fixtures = [[NSMutableArray alloc] init];
}
[fixtures addObject:#{
#"date": items[0],
#"time": items[1],
#"home": home,
#"away": away,
#"league": league
}];
if(!sections) {
sections = [[NSMutableArray alloc] init];
}
[sections addObject:#{
#"date": items[0]
}];
[self.theTableView reloadData];
[ProgressHUD dismiss];
NSLog(#"%#", fixtures);
I'm trying to progressHUD in the Asynchronous request, but it does not seem to work probably. What i want is it to show the progessHUD until the Asynchronous request is done. at the moment it is not showing in the beginning, but after 3 sec it is showing for 0.1 second or something and after that the Asynchronous request is completed. What am i doing wrong, to achieve that the progessHUD is shown when the viewisloaded to the Asynchronous request is done?
As you can see below i've added progressHUD show and dismiss in the viewDidLoad and in the firstRequest method.
Viewdidload:
- (void)viewDidLoad
{
[super viewDidLoad];
[ProgressHUD show:#"Please Wait..."];
buttonLogin = [[UIBarButtonItem alloc] initWithTitle:#"Login" style:UIBarButtonItemStyleBordered target:self action:#selector(actionLogin)];
buttonLogout = [[UIBarButtonItem alloc] initWithTitle:#"Logout" style:UIBarButtonItemStyleBordered target:self action:#selector(actionLogout)];
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
self.theTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, screenWidth, screenHeight-160) style:UITableViewStylePlain];
self.theTableView.dataSource = self;
self.theTableView.delegate = self;
[self.view addSubview:self.theTableView];
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
self.navigationController.navigationBar.titleTextAttributes = #{NSForegroundColorAttributeName : [UIColor whiteColor]};
fixtures = [[NSMutableArray alloc] init];
sections = [[NSMutableArray alloc] init];
sortedArray = [[NSMutableArray alloc] init];
[self firstRequest];
self.bannerView = [[GADBannerView alloc] initWithFrame:CGRectMake(0.0, self.view.frame.size.height-100, GAD_SIZE_320x50.width, GAD_SIZE_320x50.height)];
self.theTableView.backgroundColor = [UIColor colorWithRed:243/255.0f green:243/255.0f blue:247/255.0f alpha:1.0f];
self.view.backgroundColor = [UIColor colorWithRed:243/255.0f green:243/255.0f blue:247/255.0f alpha:1.0f];
[self checkAuthStatus];
[ProgressHUD dismiss];
}
the request:
-(void)firstRequest
{
NSURL *url = [NSURL URLWithString:#"URL"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
[ProgressHUD show:#"Please Wait..."];
jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
int subObjects = ((NSArray *)jsonResult[#"match"]).count;
for (int i = 0; i <= subObjects-1; i++) {
NSString *date = [NSString stringWithFormat:#"%# %#",[[[jsonResult valueForKey:#"match"] valueForKey:#"playdate"] objectAtIndex:i], [[[jsonResult valueForKey:#"match"] valueForKey:#"time"] objectAtIndex:i]];
NSString *identifier = [[NSLocale currentLocale] localeIdentifier];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setTimeZone: [NSTimeZone timeZoneWithName:#"US/Arizona"]];
[df setLocale:[NSLocale localeWithLocaleIdentifier:identifier]];
[df setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate *myDate = [df dateFromString:[NSString stringWithFormat:#"%#", date]];
NSArray *items = [[NSString stringWithFormat:#"%#", myDate] componentsSeparatedByString:#" "];
NSString *home = [[[jsonResult valueForKey:#"match"] valueForKey:#"hometeam"] objectAtIndex:i];
NSString *away = [[[jsonResult valueForKey:#"match"] valueForKey:#"awayteam"] objectAtIndex:i];
NSString *league = [[[jsonResult valueForKey:#"match"] valueForKey:#"league"] objectAtIndex:i];
[fixtures addObject:
[[NSMutableDictionary alloc] initWithObjectsAndKeys:
items[0], #"date",
items[1], #"time",
home, #"home",
away, #"away",
league, #"league",
nil]];
[sections addObject:
[[NSMutableDictionary alloc] initWithObjectsAndKeys:
items[0], #"date",
nil]];
}
NSArray *copy = [sections copy];
NSInteger index = [copy count] - 1;
for (id object in [copy reverseObjectEnumerator]) {
if ([sections indexOfObject: object inRange: NSMakeRange(0, index)] != NSNotFound) {
[sections removeObjectAtIndex: index];
}
index--;
}
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"self" ascending:NO];
NSArray *descriptors = [NSArray arrayWithObject: descriptor];
NSArray* reverseTheArray = [[sections valueForKey:#"date"] sortedArrayUsingDescriptors:descriptors];
reversedArray = [[reverseTheArray reverseObjectEnumerator] allObjects];
[self.theTableView reloadData];
[ProgressHUD dismiss];
}
];
}
You should show ProgressHUD before sending a request.
Your code should look like this.
-(void)firstRequest
{
[ProgressHUD show:#"Please Wait..."];
NSURL *url = [NSURL URLWithString:#"URL"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
// request processing stuff
...
[ProgressHUD dismiss];
}
];
Explanation. When you call sendAsynchronousRequest... the request itself performed somewhere in background thread. The network operation may take some time, 1-5-10 seconds. Your completionHandler will be executed once the request completes (after the delay). So you should show ProgressHUD before sending a request. Then start the request. And dismiss ProgressHUD at the end of your completionHandler block after everything is processed.
Update
There is one more issue I noticed in your code. viewDidLoad method is called only once on view controller when its view is loaded but at this point the view itself is not presented on screen. So you will not actually see ProgressHUD called from viewDidLoad. You may be interested in viewWillAppear and viewDidAppear methods if you want to handle when view is presented on screen.
I assume your view controller is designed to show data retrieved from web api. I believe the best option is to call your api in viewWillAppear.
- (void)viewDidLoad
{
[super viewDidLoad];
buttonLogin = [[UIBarButtonItem alloc] initWithTitle:#"Login" style:UIBarButtonItemStyleBordered target:self action:#selector(actionLogin)];
buttonLogout = [[UIBarButtonItem alloc] initWithTitle:#"Logout" style:UIBarButtonItemStyleBordered target:self action:#selector(actionLogout)];
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
self.theTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, screenWidth, screenHeight-160) style:UITableViewStylePlain];
self.theTableView.dataSource = self;
self.theTableView.delegate = self;
[self.view addSubview:self.theTableView];
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
self.navigationController.navigationBar.titleTextAttributes = #{NSForegroundColorAttributeName : [UIColor whiteColor]};
fixtures = [[NSMutableArray alloc] init];
sections = [[NSMutableArray alloc] init];
sortedArray = [[NSMutableArray alloc] init];
self.bannerView = [[GADBannerView alloc] initWithFrame:CGRectMake(0.0, self.view.frame.size.height-100, GAD_SIZE_320x50.width, GAD_SIZE_320x50.height)];
self.theTableView.backgroundColor = [UIColor colorWithRed:243/255.0f green:243/255.0f blue:247/255.0f alpha:1.0f];
self.view.backgroundColor = [UIColor colorWithRed:243/255.0f green:243/255.0f blue:247/255.0f alpha:1.0f];
[self checkAuthStatus];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self firstRequest];
}
-(void)firstRequest
{
[ProgressHUD show:#"Please Wait..."];
NSURL *url = [NSURL URLWithString:#"URL"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
// request processing stuff
...
[ProgressHUD dismiss];
}
];
}
My app parses information from a Rails app using JSON. I'm looking for a way to load the JSON asynchronously, but I'm having trouble getting my code to work with examples I have found because of the complexity of my code. What do I have to do to make my JSON load asynchronously? Thanks.
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *upcomingReleaseURL = [NSURL URLWithString:#"http://obscure-lake-7450.herokuapp.com/upcoming.json"];
NSData *jsonData = [NSData dataWithContentsOfURL:upcomingReleaseURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
NSArray *upcomingReleasesArray = [dataDictionary objectForKey:#"upcoming_releases"];
//This is the dateFormatter we'll need to parse the release dates
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
NSTimeZone *est = [NSTimeZone timeZoneWithAbbreviation:#"EST"];
[dateFormatter setTimeZone:est];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en_US"]]; //A bit of an overkill to avoid bugs on different locales
//Temp array where we'll store the unsorted bucket dates
NSMutableArray *unsortedReleaseWeek = [[NSMutableArray alloc] init];
NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] init];
for (NSDictionary *upcomingReleaseDictionary in upcomingReleasesArray) {
//We find the release date from the string
NSDate *releaseDate = [dateFormatter dateFromString:[upcomingReleaseDictionary objectForKey:#"release_date"]];
//We create a new date that ignores everything that is not the actual day (ignoring stuff like the time of the day)
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components =
[gregorian components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:releaseDate];
//This will represent our releases "bucket"
NSDate *bucket = [gregorian dateFromComponents:components];
//We get the existing objects in the bucket and update it with the latest addition
NSMutableArray *releasesInBucket = [tmpDict objectForKey:bucket];
if (!releasesInBucket){
releasesInBucket = [NSMutableArray array];
[unsortedReleaseWeek addObject:bucket];
}
UpcomingRelease *upcomingRelease = [UpcomingRelease upcomingReleaseWithName:[upcomingReleaseDictionary objectForKey:#"release_name"]];
upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:#"release_date"];
upcomingRelease.release_price = [upcomingReleaseDictionary objectForKey:#"release_price"];
upcomingRelease.release_colorway = [upcomingReleaseDictionary objectForKey:#"release_colorway"];
upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:#"release_date"];
upcomingRelease.thumb = [upcomingReleaseDictionary valueForKeyPath:#"thumb"];
upcomingRelease.images = [upcomingReleaseDictionary objectForKey:#"images"];
[releasesInBucket addObject:upcomingRelease];
[tmpDict setObject:releasesInBucket forKey:bucket];
}
[unsortedReleaseWeek sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSDate* date1 = obj1;
NSDate* date2 = obj2;
//This will sort the dates in ascending order (earlier dates first)
return [date1 compare:date2];
//Use [date2 compare:date1] if you want an descending order
}];
self.releaseWeekDictionary = [NSDictionary dictionaryWithDictionary:tmpDict];
self.releaseWeek = [NSArray arrayWithArray:unsortedReleaseWeek];
}
One simple approach is to use NSURLConnection's convenient class method sendAsynchronousRequest:queue:error.
The following code snippet is an example how to load a JSON from a server, and where the completion handler executes on a background thread which parses the JSON. It also performs all recommended error checking:
NSURL* url = [NSURL URLWithString:#"http://example.com"];
NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest addValue:#"application/json" forHTTPHeaderField:#"Accept"];
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse* response,
NSData* data,
NSError* error)
{
if (data) {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
// check status code and possibly MIME type (which shall start with "application/json"):
NSRange range = [response.MIMEType rangeOfString:#"application/json"];
if (httpResponse.statusCode == 200 /* OK */ && range.length != 0) {
NSError* error;
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (jsonObject) {
dispatch_async(dispatch_get_main_queue(), ^{
// self.model = jsonObject;
NSLog(#"jsonObject: %#", jsonObject);
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
//[self handleError:error];
NSLog(#"ERROR: %#", error);
});
}
}
else {
// status code indicates error, or didn't receive type of data requested
NSString* desc = [[NSString alloc] initWithFormat:#"HTTP Request failed with status code: %d (%#)",
(int)(httpResponse.statusCode),
[NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode]];
NSError* error = [NSError errorWithDomain:#"HTTP Request"
code:-1000
userInfo:#{NSLocalizedDescriptionKey: desc}];
dispatch_async(dispatch_get_main_queue(), ^{
//[self handleError:error]; // execute on main thread!
NSLog(#"ERROR: %#", error);
});
}
}
else {
// request failed - error contains info about the failure
dispatch_async(dispatch_get_main_queue(), ^{
//[self handleError:error]; // execute on main thread!
NSLog(#"ERROR: %#", error);
});
}
}];
Although, it appears somewhat elaborate, IMO this is a minimalistic and still naïve approach. Among other disadvantages, the main issues are:
it lacks the possibility to cancel the request, and
there is no way to handle more sophisticated authentication.
A more sophisticated approach needs to utilize NSURLConnection delegates. Usually, third party libraries do implement it in this manner, encapsulating the a NSURLConnection request and other relevant state info into a subclass of NSOperation. You may start with your own implementation, for example using this code as a template.
If you just want to get this only json data, you do not need to set up a lot of things.
use the code below. Create jsonParse method which gets a NSData Object.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *data = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:#"http://obscure-lake-7450.herokuapp.com/upcoming.json"]];
dispatch_sync(dispatch_get_main_queue(), ^{
[self jsonParse:data];
});
});
Download your data async as in this answer: Object-c/iOS :How to use ASynchronous to get a data from URL?
Then run it through the json parser.
To generically run code in a background thread you can use this method:
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Code here is run on a background thread
dispatch_async( dispatch_get_main_queue(), ^{
// Code here is run on the main thread (the UI thread) after your code above has completed so you can update UI after the JSON call has completed if you need to.
});
});
But remember that Apple does not allow you to update UI elements in a background thread. Also, they do not allow you to spawn more threads from a background thread, it must be done from the main thread.
NSString *urlstr=#"http://itunes.apple.com/in/rss/topsongs/limit=25/json";
NSMutableURLRequest *request=[[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:urlstr]];
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse* response,
NSData* data, NSError* error)
{
NSError *myError = nil;
NSDictionary *dic1 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&myError];
if (myError ==nil) {
NSDictionary*feed =[dic1 objectForKey:#"feed"];
NSArray*arrayofentry =[feed objectForKey:#"entry"];
for(NSDictionary *dic2 in arrayofentry) {
requestReply=[dic2 objectForKey:#"title"];
[arr1 addObject:requestReply];
}
[self.table reloadData];
}
}];
Try this code:
NSURL * inkURL = [NSURL URLWithString:#"your url"];
NSURLRequest * request = [[NSURLRequest alloc]initWithURL:inkURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * response, NSData * data, NSError * error) {
NSData * jsonData = [NSData dataWithContentsOfURL:inkURL];
NSDictionary * dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
self.inkArray = [dataDictionary objectForKey:#"users"];
}];