ResignFirstResponder crash - ios

I`m trying to down keyboard before do a NSURLResquest and loading show...
[self.txtComentario resignFirstResponder];
crashes de app...I have already tried to resignFirstResponder inside loadingThread() too
-(void) loadingThread {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
[self.myLoadingView setFrame:CGRectMake(0, 0, 320, 568)];
[self.myLoadingImagem setFrame:CGRectMake(133, 250, 54, 9)];
[appDelegate.window addSubview:self.myLoadingView];
[appDelegate.window addSubview:self.myLoadingImagem];
[self.myLoadingView setHidden:NO];
[self animar];
}
-(IBAction)btnComentarClick:(id)sender {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
[self.txtComentario resignFirstResponder];
[NSThread detachNewThreadSelector:#selector(loadingThread) toTarget:self withObject:nil];
NSString* comentarios = [kBaseURL stringByAppendingPathComponent:kComentarios];
NSURL* url = [NSURL URLWithString:comentarios]; //1
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST"; //2
// NSDictionary *usuarioDict = [[NSDictionary alloc] initWithObjectsAndKeys:txtEmailCadastro.text, #"email", txtSenhaCadastro.text, #"senha", categorias, #"categorias", nil];
NSMutableDictionary* jsonable = [NSMutableDictionary dictionary];
safeSet(jsonable, #"idUsuario", appDelegate.usuarioLogado.identificador);
safeSet(jsonable, #"nomeUsuario", appDelegate.usuarioLogado.nome);
safeSet(jsonable, #"textoComentario", self.txtComentario.text);
safeSet(jsonable, #"idOcorrencia", _location._id);
NSData* data = [NSJSONSerialization dataWithJSONObject:jsonable options:0 error:NULL]; //3
request.HTTPBody = data;
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"]; //4
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { //5
if (!error) {
// NSArray* responseArray = #[[NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]];
NSLog(#"comentado");
dispatch_async(dispatch_get_main_queue(), ^(void){
self.txtComentario.text = #"";
[self.txtComentario resignFirstResponder];
});
}
}];
[dataTask resume];
[self listarComentarios];
}
EDIT: if I try to resignFirstesponder before NSThread, nothing happens, no crash but not keyboard Down..If i try inside loadingThread...the app crashes
the error inside NSThread is:
[UIKeyboardTaskQueue waitUntilAllTasksAreFinished] may only be called
from the main thread.'

It happens sometimes that asynchronous API requests won't call back to the delegate on the main thread. So, just try to ensure that you are on Main thread. If you aren't on it then try to switch to the main thread before you make any update to the UI.
if ([NSThread isMainThread]) {
NSLog(#"Yes, it is!");
} else {
NSLog(#"No, it's not. Please switch to main");
}

try to replace with this code
[self.view endEditing: YES];

Related

Activity Indicator while calling API

I want to display the activity indicator while waiting for the API to return. The problem is after all the result I get from API, then the spinner only display. The result I want is while waiting for API call, then the spinner will running.
I'm calling this method in here
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
[self startLoadingSpinner]
//Calling API...
[self stopLoadingSpinner]
}
Here is the method for the activity indicator
-(void)startLoadingSpinner {
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 70, 70)];
self.activityIndicator.opaque = YES;
self.activityIndicator.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.4f];
self.activityIndicator.center = self.view.center;
self.activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[self.activityIndicator setColor:[UIColor whiteColor]];
[self.view addSubview:self.activityIndicator];
[self.activityIndicator startAnimating];
}
This is how I stop the activity indicator
-(void)stopLoadingSpinner {
[self.activityIndicator performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:0.5];
}
Don't add activity indicators in tableview datasource method - numberOfRowsInSection .
Add these two functions calling in the same method where you are making an API call. Make an API call in ViewDidLoad, some life cycle method or in action methods.
Below is the example of using it.
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://httpbin.org/get"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
[self startLoadingSpinner]
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(#"Error: %#", error);
} else {
NSLog(#"%# %#", response, responseObject);
}
[self stopLoadingSpinner]
}];
[dataTask resume];
In Swift
func makeAPIRequest(to endPoint: String) {
// here you can showActivetyIndicator start progressing here
self.startLoadingSpinner()
Alamofire.request(endPoint).responseJSON{ response in
if let value = response.result.value {
let responseInJSON = JSON(value)
self._responseInJSON = responseInJSON
}
// here you can hide Your ActivetyIndicator here
self.stopLoadingSpinner()
}
}
My detailed answer is below
-(void)simpleGetResponse{
#try {
//Call the Activity Indicator show method here
[self startLoadingSpinner];
NSString *strURL = #"Your URL";
NSURL *urlStr = [NSURL URLWithString:strURL];
NSMutableURLRequest *mutaURL = [NSMutableURLRequest requestWithURL:urlStr];
[mutaURL setHTTPMethod:#"GET"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:mutaURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode == 200)
{
NSError *parseError = nil;
id response = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if(response != nil){
if([response isKindOfClass:[NSDictionary class]]){
NSLog(#"response is in dictionary format %#",response);
NSDictionary *dictRes = [response copy];
NSLog(#"The dictRes is - %#",dictRes);
}
else{
NSLog(#"response is in array format %#",response);
NSDictionary *arrRes = [response copy];
NSLog(#"The arrRes is - %#",arrRes);
}
dispatch_async(dispatch_get_main_queue(), ^{
//Call the Activity Indicator hidden method inside the dispatch_main_queue method
[self stopLoadingSpinner]
[yourTableView reloadData];
});
}
}
else
{
NSLog(#"Error");
}
}];
[dataTask resume];
}
#catch (NSException *exception) {
NSLog(#"%#", [exception description]);
}
#finally {
}
}

PrepareForSegue called before didSelectRowAtIndexPath

I am calling the following method in didSelectRowAtIndexPath.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath{
[self unreadMessageCounter];
}
In that method I am getting a value with parameter name "MsgCount". For that the written code is.
-(void) unreadMessageCounter{
NSUserDefaults *defaultUser=[NSUserDefaults standardUserDefaults];
NSString* username = [defaultUser objectForKey:KUserName];
NSString* password = [defaultUser objectForKey:KPassword];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showWithStatus:#"Loading..." maskType:SVProgressHUDMaskTypeGradient];
});
NSString *url3 ;
NSString *base_url=[[NSUserDefaults standardUserDefaults] objectForKey:#"BASE_URL"];
url3=[[NSString alloc]initWithFormat:#"%#%#? username=%#&password=%#&deviceUniqueId=%#",base_url,MESSAGE_COUNTER,username,password,[defaultUser objectForKey:KDeviceToken]];
[defaultUser synchronize];
NSURL *requestURL = [NSURL URLWithString:[url3 stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:requestURL];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *urlResponse, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)urlResponse;
NSLog(#"Response Code For Message Counter:: %ld", (long)[response statusCode]);
if(response){
NSMutableDictionary *returneDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSLog(#"Return Dict For Message Counter:: %#", returneDict);
if (returneDict != nil) {
if ([returneDict valueForKey:#"valueSet"]){
for (NSDictionary *dict in [returneDict valueForKey:#"valueList"]) {
_counterNumber = dict[#"MsgCount"];
NSLog(#"counter number %#", _counterNumber);
}
}
else{
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
});
}
}
else{
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
});
}
}
}];
[task resume];
}
After that I am passing that _counterNumber string to the next view controller with the help of prepareForSegue, for that the following code is.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if (![segue.identifier isEqualToString:#"Show Notification"]) {
UINavigationController *nav = [segue destinationViewController];
WelcomeScreenViewController *welcomeScreenViewController = (WelcomeScreenViewController *)nav.topViewController;
welcomeScreenViewController.counterString = _counterNumber;
}
}
It was going to the next view controller but on the first call, after didselectrowatindexpath instead of unreadMessageCounter method, prepareForSegue is getting call that's why the _counterNumber value I am getting nil, but on the second time when I am calling then It is working as usual. So plese help me in that case because I am not getting any clue.
First Disconnect your segue from cell to nextViewController. And make new segue from current ViewController to your nextViewController like below screenshot
And after processing you API that you are calling in didSelectRowAtIndexPath perform segue through code like below.
[self performSegueWithIdentifier: #"Show Notification" sender: self];

login and completion handler

I'm developing an app with a login page. When the app is launched, the login screen is shown, and you cannot access the app until you are connected. To connect to the app, you enter your username and your password. When you press the "connect" button, json data containing the username and password is sent to a web service, which check if the credentials exists. If they exists, the server send a json file containing "exists":"true"
The problem is that the code checking this Json file is in completionHandler of my NSURLSession, and the method return "NO" before the Json data is checked, so I can not connect to my app. As it's hard to explain, here is my code:
GSBconnexion.m:
#import "GSBconnexion.h"
#implementation GSBconnexion
-(bool)logConnexionWithUserName:(NSString *)username
password:(NSString *)password{
__block BOOL allowConnexion;
NSDictionary *connexion = #{
#"username": username,
#"password": password,
#"target": #"app"
};
NSError *error;
NSData *jsonLogData = [NSJSONSerialization dataWithJSONObject:connexion options:NSJSONWritingPrettyPrinted
error:&error];
if (! jsonLogData) {
NSLog(#"Got an error: %#", error);
}
NSData *logData = jsonLogData;
NSString *testString = [[NSString alloc] initWithData:logData encoding:NSUTF8StringEncoding];
NSString *logLength = [NSString stringWithFormat:#"%lu", (unsigned long)[testString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"http://192.168.5.133:1337/login"]];
[request setHTTPMethod:#"POST"];
[request setValue:logLength forHTTPHeaderField:#"Content-lenght"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:logData];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSDictionary *serverResponse = [NSJSONSerialization JSONObjectWithData:data options:
NSJSONReadingMutableContainers error:&error];
int canIConnect = [serverResponse[#"exist"] intValue];
NSLog(#"%d",canIConnect);
if (canIConnect == 1) {
NSLog(#"OKKK");
allowConnexion = YES;
NSString *sessionID = [[NSString alloc]initWithString:serverResponse[#"_id"]];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:sessionID forKey:#"SessionID"];
[userDefaults synchronize];
NSLog(#"ID Session:%#",[userDefaults objectForKey:#"sessionID"]);
}
else {
allowConnexion=NO;
}
}] resume];
NSLog(#"JSON envoyé: \n\n%#",testString);
return allowConnexion;
}
#end
GSBLoginController:
- (IBAction)connect:(id)sender {
connectButton.hidden = YES;
loading.hidden = NO;
UIViewController* homePage = [self.storyboard instantiateViewControllerWithIdentifier:#"homePage"];
GSBconnexion *login = [[GSBconnexion alloc]init];
NSString *username = [[NSString alloc]initWithFormat:#"%#",usernameTextField.text];
NSString *password = [[NSString alloc]initWithFormat:#"%#",pwdTextField.text];
BOOL authorized = [login logConnexionWithUserName:username password:password];
if (authorized) {
[self presentViewController:homePage animated:YES completion:nil];
}
else {
connectButton.hidden = NO;
loading.hidden=YES;
usernameTextField.text=#"";
pwdTextField.text=#"";
errorLabel.text = #"Connexion impossible, merci de réessayer.\nSi le problème persiste, veuillez contacter un administrateur.";
}
NSLog(authorized ? #"Yes" : #"No");
}
I hope you understood me, thanks for your help!
Simon
The problem is that you're expecting a return value from a method that is executing asynchronously. So basically return allowConnexion is happening immediately even though the dataTask is still ongoing in the background. Thus, you're relying on an incorrect value. Basically what you want to do is copy what is happening in the dataTask w/ a completion handler.
So you could say something like typedef void (^CompletionBlock) (BOOL isFinished);
Then change your login method to include the completion block as its last argument and return nothing:
-(void)logConnexionWithUserName:(NSString *)username
password:(NSString *)password
withCompletion:(CompletionBlock)completionBlock
Then inside of the dataTask's completionHandler call the completionBlock passing in the value of allowConnexion.
Finally once you've done all that in your login view controller you'll implement this new method, and inside of the completion block you can update your view accordingly. Its going to look something like this:
- (void)thingWithCompletion:(CompletionBlock)completionBlock
{
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(YES);
});
}
- (void)viewDidLoad {
[super viewDidLoad];
[self thingWithCompletion:^(BOOL isFinished) {
//update UI
}];
}
Be advised that since you're on a background thread and going to update UI on completion you're going to want to dispatch to the main queue as well. That is why the call to completionBlock(YES); is wrapped in the dispatch_async call.

Make periodic server requests with NSURLSessionDataTask and dispatch_source timer

Need some help and explanation, because i'm really stuck in my question. i need to make this:
1) I make one request to the server, get some response and then i want to make another request every 7 seconds(example). also get some response. if it satisfy several conditions -> stop timer and do some stuff.
Main problem is that timer never stops, despite the fact that all in all i get response right. i assume that i use GCD incorrectly. because in debug this code behaves really strange.
What i have done:
This is my request function(it became like this after i read about 50 links how to do similar things)
-(void)makeRequestWithURL:(NSString*)urlString andParams:(NSString*)params andCompletionHandler:(void(^)(NSDictionary *responseData, NSError *error))completionHnadler{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
request.HTTPMethod = #"POST";
request.HTTPBody = [params dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHnadler) {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
completionHnadler(nil, error);
});
} else {
NSError *parseError;
json = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&parseError];
dispatch_async(dispatch_get_main_queue(), ^{
completionHnadler(json, parseError);
});
}
}
}];
[postDataTask resume]; }
I create my timer like this:
dispatch_source_t CreateDispatchTimer(uint64_t interval,
uint64_t leeway,
dispatch_queue_t queue ,
dispatch_block_t block) {
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer) {
// Use dispatch_time instead of dispatch_walltime if the interval is small
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer; }
and called it like this:
-(void)checkForPassenger {
timerSource = CreateDispatchTimer(7ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if([self getNotificationsRequest] == YES) {
dispatch_source_cancel(timerSource);
} else {
NSLog(#"go on timer");
}
NSLog(#"Driver checked for passenger!");
}); }
this is the code of periodic response:
-(BOOL)getNotificationsRequest {
NSString *urlString = #"http://primetime.by/temproad/do";
NSString *params = [NSString stringWithFormat:#"event={\"type\": \"in.getNotifications\"}&session_id=%#",session_id];
[self makeRequestWithURL:urlString andParams:params andCompletionHandler:^(NSDictionary *responseData, NSError *error) {
if ([[responseData objectForKey:#"rc"] intValue] == 0) {
NSArray *temp_notifications = [responseData objectForKey:#"notifications"];
if (temp_notifications.count != 0) {
notification = [[Notification alloc] initWithNotification:[[responseData objectForKey:#"notifications"] objectAtIndex:0]];
}
}
}];
if (notification) {
return YES;
} else {
return NO;
} }
and this is what i do in main request:
[self makeRequestWithURL:urlString andParams:params andCompletionHandler:^(NSDictionary *responseData, NSError *error) {
if ([[responseData objectForKey:#"rc"] intValue] == 0) {
route = [[Route alloc] initWithData:responseData];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self checkForPassenger];
});
}
}];
NSLog(#"bye");
maybe explanation is bad so i can answer any question.
thx

Display loading message until fully parsing json iOS

Ok here's the question, how do I simulate loading message until I fully downloaded the data from the server. I have this problem as I can't pass the data to the next view controller when the properties to hold the data from the downloaded json is still nil. So, How can I simulate a loading message until I fully parsed the Json.
Here's my code to fetch data
-(void)fetchFeed
{
NSString *requestString = #"some website";
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.locations = jsonObject[#"someKey"];
NSLog(#"%#", self.locations);
}
];
[dataTask resume];
}
- (void)viewDidLoad
{
[super viewDidLoad];
MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:hud];
[hud show:YES];
[hud setLabelText:#"Loading..."];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[self fetchFeed]; //Network activity
dispatch_async(dispatch_get_main_queue(), ^{
//do stuff after json download
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
Please check out my answer here.. It pretty much does the same thing that you are looking for..
I have used MBProgressHUD to show the loading message.
Its as simple as
MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:hud];
[hud show:YES];
[hud setLabelText:#"Loading..."];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//Network activity
dispatch_async(dispatch_get_main_queue(), ^{
//do stuff after json download
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
for a more detailed answer check the link.
*************EDIT*******************
As you are using NSURLSession it allows you to perform background download operations. As per the code you posted, we don't to start a new thread using dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{})
Please try this..
- (void)viewDidLoad
{
[super viewDidLoad];
[self fetchFeed]; //Network activity
}
-(void)fetchFeed
{
MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:hud];
[hud show:YES];
[hud setLabelText:#"Loading..."];
NSString *requestString = #"some website";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
[[self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSDictionary * jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
self.locations = jsonObject[#"someKey"];
NSLog(#"%#", self.locations);
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
}] resume];
}
This should work.

Resources