Update the UI of the View Controller and then dismiss it - ios

I have a view controller, that loads some an array. While everything is loading, I need to present another view controller (with the UIProgressView) and update it's UI (the progress property of a UIProgressView) and then dismiss and present first vc with downloaded data. I'm really struggling on it and I've tried delegation, but nothing worked for me.
- (void)viewDidLoad
{
[super viewDidLoad];
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"downloaded"]) {
} else {
NSLog(#"First time Launched");
ProgressIndicatorViewController *progressVC = [ProgressIndicatorViewController new];
progressVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self syncContacts];
[self presentViewController:progressVC animated:YES completion:nil];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"downloaded"];
[progressVC release];
}
}
sync contacts method:
- (void)syncContacts
{
NSLog(#"Sync data");
NSMutableArray *allContacts = [ContactsOperations getAllContactsFromAddressBook];
NSInteger allContactsCount = [allContacts count];
if (allContactsCount > 0) {
for (ContactData *contact in allContacts) {
NSMutableArray *phoneNumbersArray = [[NSMutableArray alloc] init];
NSString *nospacestring = nil;
for (UserTelephone *tel in [contact.abonNumbers retain]) {
NSArray *words = [tel.phoneNumber componentsSeparatedByCharactersInSet :[NSCharacterSet whitespaceCharacterSet]];
NSString *nospacestring = [words componentsJoinedByString:#""];
[phoneNumbersArray addObject:nospacestring];
}
contact.abonNumbers = phoneNumbersArray;
if (phoneNumbersArray != nil) {
NSLog(#"NOT NULL PHONENUMBERS: %#", phoneNumbersArray);
}
NSDictionary *dataDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:contact.abonNumbers, #"phoneNumbers", contact.contactName, #"fullName", [NSNumber numberWithBool:contact.isBlackList], #"blacklist", [NSNumber numberWithBool:contact.isIgnore], #"ignore", contact.status, #"status", nil];
NSLog(#"dictionary: %#", dataDictionary);
NSError *error;
NSData *postData = [NSJSONSerialization dataWithJSONObject:dataDictionary options:0 error:&error];
NSLog(#"POST DATA IS : %#", postData);
NSMutableURLRequest *newRequest = [self generateRequest:[[NSString stringWithFormat:#"%#c/contacts%#%#", AVATATOR_ADDR, SESSION_PART, [[ServiceWorker sharedInstance] SessionID]] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding] withHTTPMethod:#"POST"];
[newRequest setHTTPBody:postData];
[newRequest setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
//__block NSMutableData *newData;
[NSURLConnection sendAsynchronousRequest:newRequest queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
if (!connectionError) {
NSDictionary *allData = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"alldata from contacts: %#", allData);
//NSInteger errorCode = [[allData objectForKey:#"CommandRes"] integerValue];
//if (errorCode == 0) {
NSInteger remoteId = [[allData objectForKey:#"contactId"] integerValue];
contact.remoteId = remoteId;
NSLog(#"remote id is from parse content : %d", remoteId);
[[AvatatorDBManager getSharedDBManager]createContactWithContactData:contact];
} else {
NSLog(#"error");
}
}];
//Somewhere here I need to update the UI in another VC
[phoneNumbersArray release];
[dataDictionary release];
}
} else {
}
}
generate request method:
- (NSMutableURLRequest *)generateRequest:(NSString *)urlString withHTTPMethod:(NSString *)httpMethod
{
NSLog(#"url is :%#", urlString);
NSURL *url = [NSURL URLWithString:urlString];
request = [NSMutableURLRequest requestWithURL:url];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[request setHTTPMethod:httpMethod];
return request;
}
ProgressViewController is just an empty VC with the progress bar. No code yet.

In the view controller that will display the progress view expose a method like this...
- (void)updateProgress:(float)progress;
Its implementation will look like this...
- (void)updateProgress:(float)progress {
[self.progressView setProgress:progress animated:YES];
}
On the main view controller you need to execute the long-running process on a background thread. Here's viewDidLoad for the main view controller. This example code uses a property for the progress view controller (you may not require this) and assumes your are in a navigation controller...
- (void)viewDidLoad {
[super viewDidLoad];
// Create and push the progress view controller...
self.pvc = [[ProgressViewController alloc] init];
[self.navigationController pushViewController:self.pvc animated:YES];
// Your long-running process executes on a background thread...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Your long-running process goes here. Wherever required you would
// call updateProgress but that needs to happen on the main queue...
dispatch_async(dispatch_get_main_queue(), ^{
[self.pvc updateProgress:progress];
});
// At the end pop the progress view controller...
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
});
}

Related

How to call my view controller method into appdelegate.m?

I am having action for login button in view controller but i have to use some condition in appdelegate.m that if user logged in already then viewcontroller login action method will fire and if not logged in then only login page will open?
Please help me
in AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([[NSUserDefaults standardUserDefaults]boolForKey:#"IsFirstTime"])
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
HomePageVC *lvc = [storyboard instantiateViewControllerWithIdentifier:#"HomePageVC"];
[(UINavigationController *)self.window.rootViewController pushViewController:lvc animated:NO];
}
else
{
[[NSUserDefaults standardUserDefaults]setBool:YES forKey:#"IsFirstTime"];
[[NSUserDefaults standardUserDefaults]synchronize];
}
return YES;
}
in viewcontroller.m
- (IBAction)Login:(id)sender
{
[self.indicator startAnimating];//The ActivityIndicator Starts Animating Here
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:BaseUrl#"login"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[request addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"*/*" forHTTPHeaderField:#"Accept"];
[request setHTTPMethod:#"POST"];
NSString *mapData = [NSString stringWithFormat:#"userName=gautam.kar#eyeforweb.com&userPassword=1234567&api_key=ZWZ3QDEyMw==&api_password=456789"];
NSData *postData = [mapData dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
[request setHTTPBody:postData];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error == nil)
{
NSString *text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data = %#",text);
NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSLog(#"jsondic= %#",jsonDic);
NSDictionary *userDataDic = [jsonDic objectForKey:#"record"];
[DataModel setEmailAdd:[userDataDic objectForKey:#"emailAdd"]];
[DataModel setName:[userDataDic objectForKey:#"Name"]];
[DataModel setCity:[userDataDic objectForKey:#"city"]];
[DataModel setCountry:[userDataDic objectForKey:#"country"]];
[DataModel setRegistrationID:[userDataDic objectForKey:#"registrationID"]];
[DataModel setPhoneNo:[userDataDic objectForKey:#"phoneAdd"]];
[DataModel setState:[userDataDic objectForKey:#"state"]];
[DataModel settimeZone:[userDataDic objectForKey:#"timezone"]];
[DataModel setDisclaimer:[userDataDic objectForKey:#"disclaimer"]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.indicator stopAnimating];//The ActivityIndicator Stops Animating when Response Arrives
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"text= %#",text);
NSError *error = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
[self checkUserSuccessfulLogin:json];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.indicator stopAnimating];
});
NSLog(#"Error : %#",error.description);
}
}];
[postDataTask resume];
}
- (void)checkUserSuccessfulLogin:(id)json
{
// NSError *error;
NSDictionary *dictionary = (NSDictionary *)json;
if ([[dictionary allKeys] containsObject:#"login"])
{
if ([[dictionary objectForKey:#"login"] boolValue])
{
NSString *strID = [[NSUserDefaults standardUserDefaults] stringForKey:#"textField1Text"];
NSString *strPWD = [[NSUserDefaults standardUserDefaults] stringForKey:#"textField2Text"];
[[NSUserDefaults standardUserDefaults] setValue:[dictionary objectForKey:#"user_id"] forKey:#"CurrentUserLoggedIn"];
NSString *strUser = [[NSUserDefaults standardUserDefaults] stringForKey:#"CurrentUserLoggedIn"];
[[NSUserDefaults standardUserDefaults]synchronize];
[self saveLoginFileToDocDir:dictionary];
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
HomePageVC *vc = [mainStoryboard instantiateViewControllerWithIdentifier:#"HomePageVC"];
[self.navigationController pushViewController:vc animated:YES];
}
else
{
NSLog(#"Unsuccessful, Try again.");
UIAlertView *alertLogin = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Wrong Username Or Password" delegate:self cancelButtonTitle:#"cancel" otherButtonTitles:nil];
[alertLogin show];
}
}
}
- (void)saveLoginFileToDocDir:(NSDictionary *)dictionary
{
NSArray *pListpaths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *pListdocumentsDirectory = [pListpaths objectAtIndex:0];
NSString *path = [pListdocumentsDirectory stringByAppendingPathComponent:#"Login.plist"];
BOOL flag = [dictionary writeToFile:path atomically:true];
if (flag)
{
NSLog(#"Saved");
}
else
{
NSLog(#"Not Saved");
}
}
- (NSDictionary *)getLoginFileFromDocDir
{
NSArray*pListpaths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString*pListdocumentsDirectory = [pListpaths objectAtIndex:0];
NSString *path = [pListdocumentsDirectory stringByAppendingPathComponent:#"Login.plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
return dict;
}
What you need is not to check your controller in AppDelegate.m, even though that is what you're asking.
Your real problem is " how can I access data from two different places ? ".
Right now, you're telling AppDelegate he "knows" your view controllers. It shouldn't.
What you need is one (actually a lot more but you'll learn that with time) new class, that handles the Login calls and state, and all that is login related.
Call that class the... LoginManager.
In that class, you could have some methods, like Login() or Logout(), or anything you would like.
Now you have an external source of data, your login manager knows everything he musts knows about the login. You should even add some properties, like a boolean IsLoggedIn or anything you might need.
And that source of data is what AppDelegate needs to know. Not the controllers. With that kind of architecture, EVERYONE that needs the login information can access it from that class (which could / should be a singleton class, look it up on the internet, its very easy.
In your viewcontroller, you can simply do Loginmanager.login, and in appdelegate, you can check .isloggedin.
That helps you a lot, because you don't have to instantiate view controllers in appdelegate, which is really a lot of work. You're splitting the work and the tasks between classes, which is what a good programmer does. Remember, your class should have only one job, not more, not less. Your VC handles the user inteface, not the webservic calls, not the login, nothing. If it does, it means you need to create another class :)
Once you've implemented all that (read my answer as many times as necessary to make sure you understand), you'll have no problem accessing that kind of data in other place of your app.
Note that you shouldn't abuse singleton classes or static classes (especially static), but again, you'll probably make many mistakes and learn from them, like we all did when we started.
Create your ViewController Object like below,
viewcontroller *objYourVC=[[viewcontroller alloc]init];
Now call method from Appdelegate like below:
[objYourVC functionToBeCalled:nil];
OR
[objYourVC functionToBeCalled:self];
Example,
if(AlreadyLogin){
//call viewcontroller method
viewcontroller *objYourVC=[[viewcontroller alloc]init];
[objYourVC functionToBeCalled:nil];
}

Getting delay to see next view controller ,see detail in post?

I have one login screen after that it will move to next view controller which have i have used some networks like http,json to get data from server. when i enter login username/password then if i click login button its getting delay to 8 seconds after that only it moving to next view controller.Still that my login screen alone showing for 8 seconds and then only it move to next view controller.
Here my login controller.m:
#implementation mainViewController
- (void)viewDidLoad {
[super viewDidLoad];
_username.delegate = self;
_password.delegate = self;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults boolForKey:#"reg"]) {
NSLog(#"no user reg");
_logBtn.hidden = NO;
}
}
- (void)viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:animated];
[super viewWillAppear:animated];
_username.text = nil;
_password.text = nil;
}
- (IBAction)LoginUser:(id)sender {
if ([_username.text isEqualToString:#"sat"] && [_password.text isEqualToString:#"123"]) {
NSLog(#"Login success");
[self performSegueWithIdentifier:#"nextscreen" sender:self];
}
else {
NSLog(#"login was unsucess");
// Alert message
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"wrong"
message:#"Message"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *actionOk = [UIAlertAction actionWithTitle:#"Ok"
style:UIAlertActionStyleDefault
handler:nil];
[alertController addAction:actionOk];
[self presentViewController:alertController animated:YES completion:nil];
}
}
Here my nextcontroller.m
- (void)viewDidLoad {
[super viewDidLoad];
//for search label data
self.dataSourceForSearchResult = [NSArray new];
//collection of array to store value
titleArray = [NSMutableArray array];
// here only i am getting data from server
[self getdata];
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
[self.collectionView reloadData];
}
Help me out. If my question din't understand.I can tell more about my post. And in my nextcontroller.m [self getdata] is i am getting data from server url.Thanks
My get data:
-(void)getdata {
NSString *userName = #“users”;
NSString *password = #“images”;
NSData *plainData = [password dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64String = [plainData base64EncodedStringWithOptions:0];
base64String=[self sha256HashFor: base64String];
NSString *urlString = #"https://porterblog/image/file”;
NSMutableURLRequest *request= [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:#"GET"];
NSString *authStr = [NSString stringWithFormat:#"%#:%#", userName, base64String];
NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding];
NSString *authValue = [NSString stringWithFormat:#"Basic %#", [authData base64EncodedStringWithOptions:0]];
[request setValue:authValue forHTTPHeaderField:#"Authorization"];
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *str = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
NSError * error;
self->arrayPDFName = [[NSMutableArray alloc]init];
NSDictionary *jsonResults = [NSJSONSerialization JSONObjectWithData:returnData options:NSJSONReadingMutableContainers error:nil];
NSDictionary *dictOriginal = jsonResults[#“birds”];
[titleArray addObject:[NSString stringWithFormat:#" birds(%#)”, dictOriginal[#"count"]]];
NSDictionary *dictOriginal2 = jsonResults[#"owl”];
[titleArray addObject:[NSString stringWithFormat:#" Owl(%#)”, dictOriginal2[#"count"]]];
NSDictionary *dictOriginal3 = jsonResults[#"pensq”];
[titleArray addObject:[NSString stringWithFormat:#" Pensq(%#)”, dictOriginal3[#"count"]]];
NSDictionary *dictOriginal4 = jsonResults[#“lion”];
[titleArray addObject:[NSString stringWithFormat:#" lion(%#)”, dictOriginal4[#"count"]]];
NSArray *arrayFiles = [NSArray arrayWithObjects: dictOriginal, dictOriginal2, dictOriginal3, dictOriginal4, nil];
NSLog(#"str: %#", titleArray);
for (NSDictionary *dict in arrayFiles) {
NSMutableArray *arr = [NSMutableArray array];
NSArray *a = dict[#"files"];
for(int i=0; i < a.count; i ++) {
NSString *strName = [NSString stringWithFormat:#"%#",[[dict[#"files"] objectAtIndex:i] valueForKey:#"name"]];
[arr addObject:strName];
}
[arrayPDFName addObject:arr];
}
NSString *errorDesc;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory1 = [paths objectAtIndex:0];
NSString *plistPath = [documentsDirectory1 stringByAppendingPathComponent:#"SampleData.plist"];
NSString *error1;
returnData = [ NSPropertyListSerialization dataWithPropertyList:jsonResults format:NSPropertyListXMLFormat_v1_0 options:0 error:&error];
if(returnData ) {
if ([returnData writeToFile:plistPath atomically:YES]) {
NSLog(#"Data successfully saved.");
}else {
NSLog(#"Did not managed to save NSData.");
}
}
else {
NSLog(#"%#",errorDesc);
}
NSDictionary *stringsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistPath];
}
EDITED:
`- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
self.dataSourceForSearchResult = [NSArray new];
titleArray = [NSMutableArray array];
//Background Tasks
[self getdata];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
[self.collectionView reloadData];
self.navigationItem.hidesBackButton = YES;
});
});
}`
You're getting your data using main thread you need do to that in background then invoke the code you need (as i see is reload collectionView)
I assume that because you didn't show the getdata method code
If that the case you can use this code:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Tasks
[self getdata];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
[self.collectionView reloadData];
});
});
It's mean that your VC will show immediately but the collectionView fill after you finish load the data, you can put some old data while loading like Facebook app (you see latest retrieved posts until finish loading].
Edit:
In your code you replace viewdidload method in nextController with next code:
- (void)viewDidLoad {
[super viewDidLoad];
//for search label data
self.dataSourceForSearchResult = [NSArray new];
//collection of array to store value
titleArray = [NSMutableArray array];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Tasks
[self getdata];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
[self.collectionView reloadData];
});
});
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
}

How to pass data to the View Controller using asynchronous NSURLConnection

I have View Controller where I get data from web, parse Json, and pass string to another View Controller. If I use synchronous NSURLConnection, everything works just fine.
But if I switch to the asynchronous, then method (void)prepareForSegue:(UIStoryboardSegue *) calls before parsing Json data which I got from web.
Just jump over _jsonArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil] method. Any thoughts? Thank you in advance for your help. Here is my code:
-(void)getClothInfo {
NSString *allowedClothSizeToServer = [_foreignSizeToServer stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *getDataURL = [NSString stringWithFormat:#"http://xsdcompany.com/jsoncloth.php?foreignSize=%#",allowedClothSizeToServer];
NSURL *url = [NSURL URLWithString:getDataURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler: ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
[self showAlertWithMessage2:#"Server is Unavialable"];
} else {
_jsonArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
//Loop trough our jsonArray
for (int i=0; i<_jsonArray.count; i++) {
//Create our size object
_usSizeFromServer = [[_jsonArray objectAtIndex:i] objectForKey:#"usSizeCloth"];
}
}
}];
}
- (IBAction)getIt:(id)sender {
// Validate data
if ([self validData] == NO)
{
return;
}
[self getClothInfo];
[self showNextViewController];
}
-(void) showNextViewController {
[self performSegueWithIdentifier:#"GetCLothInfo" sender:nil];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
ResultViewController *resultViewController = [segue destinationViewController];
resultViewController.foreignSizeToResult = [[NSString alloc] initWithFormat:#"%# size for %# is %#", [_pickerProcessor selectedCountry].countryName, [_pickerProcessor selectedCloth].clothName, [_pickerProcessor selectedSize].sizeName];
resultViewController.dataForUsSize = [[NSString alloc] initWithFormat:#"Your US size for %# is %#", [_pickerProcessor selectedCloth].clothName, _usSizeFromServer];
}
You have two options. You could call showNextViewController from the completion block inside the getClothInfo method. Or better, add a completion block parameter to your getClothInfo method and call that from the completion block for the NSURLConnection.
Something like this:
-(void)getClothInfo:(void ^(void))completion {
NSString *allowedClothSizeToServer = [_foreignSizeToServer stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *getDataURL = [NSString stringWithFormat:#"http://xsdcompany.com/jsoncloth.php?foreignSize=%#",allowedClothSizeToServer];
NSURL *url = [NSURL URLWithString:getDataURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler: ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
[self showAlertWithMessage2:#"Server is Unavialable"];
} else {
_jsonArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
//Loop trough our jsonArray
for (int i=0; i<_jsonArray.count; i++) {
//Create our size object
_usSizeFromServer = [[_jsonArray objectAtIndex:i] objectForKey:#"usSizeCloth"];
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
}
}];
}
- (IBAction)getIt:(id)sender {
// Validate data
if ([self validData] == NO)
{
return;
}
[self getClothInfo:^ {
[self showNextViewController];
}];
}
It seems like you want your json data to be downloaded before you segue, in that case the synchronous NSURLConnection makes sense
When you make an asynchronous NSURLConnection call, it means that the subsequent code will be executed ( in this case the performSegue).
It would help if you could explain what your expected behavior is
Register for notification when response is obtained from the connection using
[[NSNotificationCenter defaultCenter] postNotificationName:#"ResponseObtained" object:_jsonArray];
in the second view controller add observer for notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleResponse:)
name:#"ResponseObtained"
object:nil];
You can access _jasonArray in handleResponse method with
- (void)handleResponse:(NSNotification *)notif{
NSDictionary *result = [notif object]; }

UI hanging on background rss parsing

I'm trying to create a simple rss reader. The code works okay, except the UI hangs when the feeds are being updated. I thought I cobbled together the code to get the feed and parse it on a background queue while updating the UI on the mainQueue, but the table hangs pretty badly. Code below:
-(void)refreshFeed2
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (NSString *feed in _feeds) {
// iterate over all feeds
NSLog(#"feed=%#", feed);
NSURL *url = [NSURL URLWithString:feed];
// Create url connection and fire request
NSURLConnection *conn = [[NSURLConnection alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
(void)[conn initWithRequest:request delegate:self];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ([data length] == 0 && error == nil) {
// handle empty response
} else if (error != nil) {
// handle error
NSLog(#"Error %#", [error localizedDescription]);
} else if ([httpResponse statusCode] == 200) {
// data present and no errors
[queue addOperationWithBlock:^{
// parse feed on queue
RXMLElement *rss = [RXMLElement elementFromXMLData:data];
RXMLElement *rssChild = [rss child:#"channel"];
RXMLElement* title = [rssChild child:#"title"];
NSArray* items = [[rss child:#"channel"] children:#"item"];
NSMutableArray* result=[NSMutableArray array];
for (RXMLElement *e in items) {
// iterate over the articles
RSSArticle* article = [[RSSArticle alloc] init];
article.sourceTitle = [title text];
article.articleTitle = [[e child:#"title"] text];
article.articleDescription = [[e child:#"description"] text];
article.articleUrl = [NSURL URLWithString: [[e child:#"link"] text]];
NSString *articleDateString = [[e child:#"pubDate"] text];
article.articleDate = [NSDate dateFromInternetDateTimeString:articleDateString formatHint:DateFormatHintRFC822];
if (article.articleUrl != NULL) {
[result addObject:article];
}
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// update table on mainQueue
for (RSSArticle *article in result) {
// iterate over articles
int insertIdx = [_allEntries indexForInsertingObject:article sortedUsingBlock:^(id a, id b) {
RSSArticle *entry1 = (RSSArticle *) a;
RSSArticle *entry2 = (RSSArticle *) b;
return [entry1.articleDate compare:entry2.articleDate];
}];
[_allEntries insertObject:article atIndex:insertIdx];
[self.LeftTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:insertIdx inSection:0]]
withRowAnimation:UITableViewRowAnimationFade];
}
}];
}];
}
}];
// Stop refresh control
[refreshControl endRefreshing];
}
}
Code that calls refreshFeed2:
- (void)viewDidLoad {
[super viewDidLoad];
self.allEntries = [NSMutableArray array];
self.feeds = [NSArray arrayWithObjects:
#"http://feeds.washingtonpost.com/rss/politics",
#"http://rss.cnn.com/rss/cnn_allpolitics.rss",
#"http://www.npr.org/rss/rss.php?id=1012",
#"http://www.slatedigital.com/support/feeds/rss_kb.php?s=fd5aa35e773dc3177b85a2126583f002",
nil];
}
//add refresh control to the table view
refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self
action:#selector(refreshInvoked:forState:)
forControlEvents:UIControlEventValueChanged];
NSString* fetchMessage = [NSString stringWithFormat:#"Fetching Articles"];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:fetchMessage
attributes:#{NSFontAttributeName:[UIFont fontWithName:#"Helvetica" size:11.0]}];
[self.LeftTableView addSubview: refreshControl];
[self refreshInvoked:self forState:UIControlStateNormal];
}
-(void) refreshInvoked:(id)sender forState:(UIControlState)state {
NSOperationQueue *refreshQueue = [[NSOperationQueue alloc] init];
[refreshQueue addOperationWithBlock:^{
[self refreshFeed2];
}];
}
Any help?
Thanks!
Can you try this? replace
[self refreshInvoked:self forState:UIControlStateNormal];
by
[self performSelectorOnBackground:#selector(refreshFeed2) withObject:nil];
and replace the same instead of
-(void) refreshInvoked:(id)sender forState:(UIControlState)state {
[self performSelectorOnBackground:#selector(refreshFeed2) withObject:nil ];
}

iOS: EXC_BAD_ACCESS Error NSString length and setHTTPBody

When I start the simulator and the application starts and I click on the UI I get EXC_BAD_ACCESS for NSString *strLength = [NSString stringWithFormat:#"%d", [postStr length]]; and for [req setHTTPBody:[_postStr dataUsingEncoding:NSUTF8StringEncoding. I dont know why this happens. If I uninstall the app but keep the simulator open and run it again I get no errors. Any help would be great. Code is below.
#import "LocavoreRetroAPIAdapter.h"
//Class extention declares a method that is private to the class
#interface LocavoreRetroAPIAdapter ()
-(NSMutableURLRequest *)initRequest:(NSURL *)url method:(NSString *)method;
#end
#implementation LocavoreRetroAPIAdapter
//Called when this class is first initialized
-(id) initWithName:(NSString *)postStr webService:(NSString *)webService spinner: (UIActivityIndicatorView *)spinner{
if(self = [super init]){
_postStr = postStr;
_baseURL = #"http://base/api/";
_webService = webService;
_spinner = spinner;
_result = nil;
}
return self;
}
//Request to Locavore API restful web services
-(void) conn:(NSString *)method{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block NSDictionary *resultBlock = nil;
dispatch_sync(concurrentQueue, ^{
/* Download the json here */
//Create webservice address
NSString *webService = [_baseURL stringByAppendingString:_webService];
//Create the url
NSURL *url = [NSURL URLWithString:webService];
//Create error object
NSError *downloadError = nil;
//Create the request
NSMutableURLRequest *req = [self initRequest:url method:method];
if(req != nil){
//Request the json data from the server
NSData *jsonData = [NSURLConnection
sendSynchronousRequest:req
returningResponse:nil
error:&downloadError];
NSError *error = nil;
id jsonObject = nil;
if(jsonData !=nil){
/* Now try to deserialize the JSON object into a dictionary */
jsonObject = [NSJSONSerialization
JSONObjectWithData:jsonData
options:NSJSONReadingAllowFragments
error:&error];
}
//Handel the deserialized object data
if (jsonObject != nil && error == nil){
NSLog(#"Successfully deserialized...");
if ([jsonObject isKindOfClass:[NSDictionary class]]){
resultBlock = (NSDictionary *)jsonObject;
//NSLog(#"Deserialized JSON Dictionary = %#", resultBlock);
}
else if ([jsonObject isKindOfClass:[NSArray class]]){
NSArray *deserializedArray = (NSArray *)jsonObject;
NSLog(#"Deserialized JSON Array = %#", deserializedArray);
} else {
/* Some other object was returned. We don't know how to deal
with this situation, as the deserializer returns only dictionaries
or arrays */
}
}
else if (error != nil){
NSLog(#"An error happened while deserializing the JSON data.");
}else{
NSLog(#"No data could get downloaded from the URL.");
[self conn:method];
}
}
});
dispatch_sync(dispatch_get_main_queue(), ^{
/* Check if the resultBlock is not nil*/
if(resultBlock != nil){
/*Set the value of result. This will notify the observer*/
[self setResult:resultBlock];
[_spinner stopAnimating];
}
});
});
}
//Configure the request for a post/get method
- (NSMutableURLRequest *)initRequest:(NSURL *)url method:(NSString *)method{
//Create the request
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
//Get the string length
NSString *strLength = [NSString stringWithFormat:#"%d", [_postStr length]];
//Specific to requests that use method post/get
//Configure the request
if([method isEqualToString:#"POST"]){
[req addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content- Type"];
[req addValue:strLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPMethod:#"POST"];
}else if([method isEqualToString:#"GET"]){
[req addValue:#"text/xml; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[req addValue:strLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPMethod:#"GET"];
}else{
return nil;
}
//Set the HTTP Body
[req setHTTPBody:[_postStr dataUsingEncoding:NSUTF8StringEncoding]];
//Return the request
return req;
}
//Called when this object is destroyed
- (void)dealloc {
NSLog(#"DEALLOC LocavoreRetroAPIAdapter");
[super dealloc];
[_baseURL release];
[_result release];
}
#end
Familiarize yourself the with memory management and object lifetime rules. Your code is crashing because you do not retain (or copy) the arguments within your init... method, and they are being deallocated. Change your init... method to:
-(id) initWithName:(NSString *)postStr webService:(NSString *)webService spinner: (UIActivityIndicatorView *)spinner{
if(self = [super init]){
_postStr = [postStr copy];
_baseURL = #"http://base/api/";
_webService = [webService copy];
_spinner = [spinner retain];
}
return self;
}
Be sure to release the three instance variables you are now copying or retaining in your dealloc method. Also call [super dealloc] as the last step in that method but that's not the source of your problem right now.
//Called when this object is destroyed
- (void)dealloc {
NSLog(#"DEALLOC LocavoreRetroAPIAdapter");
[_postStr release];
[_webService release];
[_spinner release];
[_result release];
[super dealloc];
}
Notice I removed the call to [_baseURL release] from your dealloc as you did not retain it so you do not own the object. If you didn't create an object with alloc or new, and didn't call retain or copy on it, then you don't own the object, so you must not release it.

Resources