Objective-C present modally segues programmatically - ios

I created a present modally segues in my storyboard like in the image below.
My First scene has a button with an action:
- (IBAction)loginButtonPressed:(id)sender {
[self Login];
}
which goes into this method:
- (void)Login
{
NSString *rawString = [self.idTextField text];
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
[self.idTextField setText:[rawString stringByTrimmingCharactersInSet:whitespace]];
[userName UserLogin:self.idTextField.text andPassWordExists:self.passwordTextField.text completionHandler:^(id responseObject, NSError *error) {
if (responseObject != nil) {
NSString *userN,*name;
NSArray *object = [responseObject objectAtIndex:0];
userN = [object valueForKey:#"userName"];
name = [object valueForKey:#"name"];
self.idTextField = nil;
self.passwordTextField = nil;
LHAppDelegate *appDelegate = (LHAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.loginSession setString:[userN lowercaseString]];
[appDelegate.nameOfUser setString:name];
}else{
[self CustomAlert:#"Sorry Login Failed, User and/or Passsword Incorrect"];
}
[indicatorView stopAnimating];
[indicatorView removeFromSuperview];
indicatorView = nil;
[loadingView removeFromSuperview];
loadingView = nil;
}];
}
What I am trying to do is program so when the user logins in they are present modally to the Tab Bar Controller.
Currently, when I click on the button with the wrong creds, I see my custom alert, but it still brings me to the Tab bar Controller.
Basically what I wanna do is put a condition around the present modally segues. Is this possible?
I found this piece of code:
[self performSegueWithIdentifier: #"MySegue" sender: self];
and I added it to my Login method:
- (void)Login
{
NSString *rawString = [self.idTextField text];
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
[self.idTextField setText:[rawString stringByTrimmingCharactersInSet:whitespace]];
[userName UserLogin:self.idTextField.text andPassWordExists:self.passwordTextField.text completionHandler:^(id responseObject, NSError *error) {
if (responseObject != nil) {
NSString *userN,*name;
NSArray *object = [responseObject objectAtIndex:0];
userN = [object valueForKey:#"userName"];
name = [object valueForKey:#"name"];
self.idTextField = nil;
self.passwordTextField = nil;
LHAppDelegate *appDelegate = (LHAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.loginSession setString:[userN lowercaseString]];
[appDelegate.nameOfUser setString:name];
[self performSegueWithIdentifier: #"MySegue" sender: self];
}else{
[self CustomAlert:#"Sorry Login Failed, User and/or Passsword Incorrect"];
}
[indicatorView stopAnimating];
[indicatorView removeFromSuperview];
indicatorView = nil;
[loadingView removeFromSuperview];
loadingView = nil;
}];
}
still when I login with the wrong creds and it still brings me to the Tab Bar Controller

In the above scenario it will always perform segue unaffected by server response because the segue is performed by button click.
To avoid this do it in the following way:
Delete the segue from button to the UITabBarController and make it
from view controller to the UITabBarController(drag from view
controller to other view conroller).
Now when your getting success in response make the following call
[self performSegueWithIdentifier:#"segueIdentifier" sender:self]
And if you want to pass parameters the perform the following function:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

One easy way of doing it would be to implement - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender from the NSSeguePerforming protocol.
If user was not logged in return NO, else return YES, and segue will be performed/user will be taken to the tab bar controller.
EDIT
Since you already have implemented logic for authorising user in login method, I suggest doing what #manish_kumar have suggested.

Related

iOS writeToURL delay or screen freeze, how to resolve the thread in the case

I have a complex operation, then when the operate complete,
It will save to NSData write to the file to preview.
I encounter a problem, when I click the button ,
the button action will start show MBProgress thing and async to complex operate in background.
When the file write success, It will go to prepareForSegue method pass value to destinationViewController.
I try to add thread, But I found my screen always freeze or can't write file success for stay this screen show the alert(I think it is operate is not complete, so get the BOOL is NO).
How to write the resolve in this case for show the MBProgress wait the operation complete , then navigation to next viewcontroller?
Thank you very much.
My Code below:
- (IBAction)fileBtnAction:(UIButton *)sender{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self doComplexOperateAction];
dispatch_async(dispatch_get_main_queue(), ^{
fileHud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
fileHud.label.text = #"file operating....";
fileHud.userInteractionEnabled = NO;
});
});
}
- (void) doComplexOperateAction{
....... do something.......
NSError *error;
writeFileSuccess = [resultFilie.fileContent writeToURL:previewFileUrl
options:NSDataWritingFileProtectionComplete
error:&error];
}
-(BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
if( [identifier isEqualToString:#"fileSuccessSegue"] ){
if( writeFileSuccess == YES ){
fileHud.hidden = YES;
fileHud = nil;
return YES;
}else{
dispatch_async(dispatch_get_main_queue(), ^{
msgAlertController.message = #"can't write file success";
[self presentViewController:msgAlertController animated:YES completion:nil];
fileHud.hidden = YES;
fileHud = nil;
});
return NO;
}
}
return NO;
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if( [[segue identifier] isEqualToString:#"fileSuccessSegue"] ){
.........
NSURL *fileURL = [NSURL fileURLWithPath:fileTmpPathString];
fileSuccessViewController *pfVC = [segue destinationViewController];
pfVC.filePathURL = fileURL;
}
}
I think It will be something like this:
#property (noatomic) BOOL uploading;
- (IBAction)fileBtnAction:(UIButton *)sender{
fileHud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
fileHud.label.text = #"file operating....";
fileHud.userInteractionEnabled = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self doComplexOperateAction];
dispatch_sync(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"fileSuccessSegue" sender:nil];
});
});
}
- (void) doComplexOperateAction{
self.uploading = YES;
....... do something.......
NSError *error;
writeFileSuccess = [resultFilie.fileContent writeToURL:previewFileUrl
options:NSDataWritingFileProtectionComplete
error:&error];
self.uploading = NO;
}
-(BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
if( [identifier isEqualToString:#"fileSuccessSegue"] ){
if( writeFileSuccess == YES ){
fileHud.hidden = YES;
fileHud = nil;
return YES;
}else{
msgAlertController.message = #"can't write file success";
[self presentViewController:msgAlertController animated:YES completion:nil];
fileHud.hidden = YES;
fileHud = nil;
});
return NO;
}
}
return !self.uploading;
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if( [[segue identifier] isEqualToString:#"fileSuccessSegue"] ){
.........
NSURL *fileURL = [NSURL fileURLWithPath:fileTmpPathString];
fileSuccessViewController *pfVC = [segue destinationViewController];
pfVC.filePathURL = fileURL;
}
}
I would advise you to rewrite this code. I do not like how you handle errors. Because writeFileSuccess have unsuspected value while you uploading content.
I couldn't understand if the file was written or no. Anyway, keep in mind that if you try to dispatch to main thread you should always first check if you are already on the main thread, because it can hang the application.
Example:
if([NSThread isMainThread]){
//perform gui operation
} else {
dispatch_async(dispatch_get_main_queue(), ^{
//perfrom gui operation
});
}
Edit: You should consider using dispatch_sync instead of dispatch_async for the main thread, since you have nested dispatches in - (IBAction)fileBtnAction:(UIButton *)sender:
[self doComplexOperateAction];
dispatch_**sync**(dispatch_get_main_queue(), ^{
fileHud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
fileHud.label.text = #"file operating....";
fileHud.userInteractionEnabled = NO;
});
});

Keyword Search in iOS

I am new to iOS. I have a UITextfield and a Keyword Search Button. When ever I want to search a keyword from a service and press enter. Tt should display the related searched keyword from a service. Please help me to fix this issue? TIA!
- (IBAction)KeywordSearchClicked:(id)sender {
NSMutableDictionary *dict=[[NSMutableDictionary alloc] init];
[self KeywordcallSignupProfileService:dict];
}
-(void)KeywordcallSignupProfileService:(NSMutableDictionary *)dict
{
[SVProgressHUD showWithStatus:#"" maskType:SVProgressHUDMaskTypeBlack]; // Progress
NSString * post = [[NSString alloc]initWithFormat:#"userId=%#&key_word%#",UserId,[dict objectForKey:#"key_word"]];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.amarbiyashaadi.com/service/amarbiya-service.svc/userKeywordSearch/"]];
RBConnect = [[RBConnection alloc]init];
RBConnect.delegate = self;
[RBConnect postRequestForUrl:url postBody:post];
}
#pragma mark - MRConnection Delegate Methods
- (void)jsonData:(NSDictionary *)jsonDict
{
[SVProgressHUD dismiss];
NSMutableArray *jsonArr;
NSMutableDictionary *userDict,*dict;
NSArray *arr=[jsonDict allKeys];
jsonArr=[jsonDict objectForKey:#"DataTable"];
if (jsonArr.count>0) {
// Save credentials in user defaults
matchesProfileArr=[jsonArr mutableCopy];
DisplayTableViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"DisplayTableViewController"];
[self presentViewController:vc animated:YES completion:nil];
}
else
{
NSString *error=#"Somthing Went Wrong";
[SVProgressHUD showErrorWithStatus:error];
}
}

Bool value not being sent to destination ViewController

I want to send a bool value, didAddNewItem, from my SearchViewController to MatchCenterViewController, and then run a function depending on the state of the bool value. I attempt to send a didAddNewItem value of YES to my destination, MatchCenterViewController, but it doesn't seem to send correctly, as the function below never runs.
Here's how I'm sending it from SearchViewController (edited to reflect Rob's answer):
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowMatchCenterSegue"]) {
_didAddNewItem = YES;
MatchCenterViewController *controller = (MatchCenterViewController *) segue.destinationViewController;
NSLog(#"we're about to set controller values before segueing to MC");
// Send over the matching item criteria
controller.itemSearch = self.itemSearch.text;
controller.matchingCategoryId = self.matchingCategoryId1;
controller.matchingCategoryMinPrice = self.matchingCategoryMinPrice1;
controller.matchingCategoryMaxPrice = self.matchingCategoryMaxPrice1;
controller.matchingCategoryCondition = self.matchingCategoryCondition1;
controller.matchingCategoryLocation = self.matchingCategoryLocation1;
controller.itemPriority = self.itemPriority;
[self.tabBarController setSelectedIndex:1];
}
}
And here's where I try to make use of it in the destination, MatchViewController:
- (void)viewDidAppear:(BOOL)animated
{
if (_didAddNewItem == YES) {
NSLog(#"well then lets refresh the MC");
// Start loading indicator
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
[self.view addSubview: activityIndicator];
[activityIndicator startAnimating];
// Disable ability to scroll until table is MatchCenter table is done loading
self.matchCenter.scrollEnabled = NO;
_matchCenterDone = NO;
// Add new item to MatchCenter Array with the criteria from the matching userCategory instance, plus the search term
[PFCloud callFunctionInBackground:#"addToMatchCenter"
withParameters:#{
#"searchTerm": self.itemSearch,
#"categoryId": self.matchingCategoryId,
#"minPrice": self.matchingCategoryMinPrice,
#"maxPrice": self.matchingCategoryMaxPrice,
#"itemCondition": self.matchingCategoryCondition,
#"itemLocation": self.matchingCategoryLocation,
#"itemPriority": self.itemPriority,
}
block:^(NSString *result, NSError *error) {
if (!error) {
NSLog(#"'%#'", result);
self.matchCenterArray = [[NSArray alloc] init];
[PFCloud callFunctionInBackground:#"MatchCenter3"
withParameters:#{}
block:^(NSArray *result, NSError *error) {
if (!error) {
_matchCenterArray = result;
[_matchCenter reloadData];
[activityIndicator stopAnimating];
// Reenable scrolling/reset didAddNewItem bool
_matchCenterDone = YES;
self.matchCenter.scrollEnabled = YES;
//_didAddNewItem = NO;
NSLog(#"Result: '%#'", result);
}
}];
}
}];
}
}
I made sure it was properly setup as a property in the headers of both ViewControllers, so I'm not sure why it's not setting the value in the destination VC correctly. I know for a fact that addToMatchCenter function is running correctly without error, so it should be working.
#property (assign) BOOL didAddNewItem;
In your prepareForSegue, you are calling callFunctionInBackground asynchronously, meaning that it is quite likely that the segue will finish and the new view controller will be presented well before you set didAddNewItem in the block of callFunctionInBackground.
I'd be inclined to change that destination controller to initiate this asynchronous request itself, but have it show a UIActivityIndicatorView (or something) to suggest that the dependent request has not yet been finished, and then in the block you can remove the activity indicator view and update the UI accordingly.

Delete all user data in UIViewController's after logout

When I login to my app, my app does push the ViewController XYZMainViewController, XYZMainViewController viewWillAppear:animated call method that makes a request to my API to retrieve the authenticated user data, at this time I update the text of a label to show the user name. When I logout the app, it returns me to the login ViewController, when I do login again with another user, XYZMainViewController label text contains the name of the previous user, without updating the label text.
XYZMainViewController.m
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self homeProfile];
}
- (void)homeProfile
{
[NXOAuth2Request performMethod:#"GET"
onResource:[NSURL URLWithString:#"http://{url}/users/userinfo"]
usingParameters:nil
withAccount:[XYZCommonFunctions user]
sendProgressHandler:nil
responseHandler:^(NSURLResponse *response, NSData *responseData, NSError *error){
NSDictionary *parsedData = [[NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error] objectForKey:#"data"];
_user = [parsedData objectForKey:#"user"];
[self.label setText:[NSString stringWithFormat:#"Welcome %#!", [_user objectForKey:#"username"]]];
}];
}
- (IBAction)logout:(id)sender {
XYZAppDelegate* appDelegate = (XYZAppDelegate*)[[UIApplication sharedApplication] delegate];
[appDelegate logout];
}
XYZAppDelegate.m
- (void)login
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *identifier = [prefs stringForKey:#"accountidentifier"];
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
NSString *viewIdentifier = #"WelcomeView";
if(identifier != nil){
NXOAuth2Account *account = [[NXOAuth2AccountStore sharedStore] accountWithIdentifier:identifier];
if(account != nil) {
viewIdentifier = #"MainView";
}
UIViewController *controller = [mainStoryboard instantiateViewControllerWithIdentifier: viewIdentifier];
[navigationController pushViewController:controller animated:NO];
return;
}
}
- (void)logout
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs removeObjectForKey:#"accountidentifier"];
[prefs synchronize];
for (NXOAuth2Account *a in [[NXOAuth2AccountStore sharedStore] accounts] ){
[[NXOAuth2AccountStore sharedStore] removeAccount:a];
}
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
[navigationController popToRootViewControllerAnimated:YES];
}
I need to reinitialize all data in XYZMainViewController.
Thank you.
Look like problem is related to fetching JSON Object. It is possible that everytime you have send same user to fetch user data. You are not using NSUserdefault object to display name, you are using value, which is return by JSON Object. According to me cause of error is "withAccount:[XYZCommonFunctions user]" line.
I would like to suggest, instead of using
-(void)viewWillAppear:(BOOL)animated {
you can use
- (void)viewDidLoad
so that your login action performed only when your LoginController loads,instead when LoginController appear.
New viewwillAppear look as given below -
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.label setText:#""];
}
and ViewDidLoad -
- (void)viewDidLoad
{
[super viewDidLoad];
[self homeProfile];
}
Also check your json response, whether you are getting response success or error.According to response need to handle.
Hope this helps.

iOS: Adding data to table view from a custom method

I have an iPhone app connects to a server using OAuth. On success, it fetches the a user from the server. Again, upon success, it adds an item to the array of objects that populates the table view. Here is the code that does this:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
if (editing) {
[super setEditing:YES animated:YES];
self.backButton = self.navigationItem.leftBarButtonItem;
UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(signInWithCatapult)];
self.navigationItem.leftBarButtonItem = leftButton;
} else {
[super setEditing:NO animated:YES];
self.navigationItem.leftBarButtonItem = self.backButton;
}
}
- (void)signInWithCatapult
{
[self signOut];
GTMOAuth2Authentication *auth = [self catapultAuthenticaiton];
NSURL *authURL = [NSURL URLWithString:#"https://oauth.lvh.me:3000/oauth/authorize"];
GTMOAuth2ViewControllerTouch *viewController;
viewController = [[GTMOAuth2ViewControllerTouch alloc] initWithAuthentication:auth
authorizationURL:authURL
keychainItemName:kCatapultKeychainItemName
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)];
[[self navigationController] pushViewController:viewController animated:YES];
}
- (GTMOAuth2Authentication *)catapultAuthenticaiton
{
NSURL *tokenURL = [NSURL URLWithString:kDoorkeeperTokenURL];
NSString *redirectURI = #"https://catapultcentral.com/iOSClientCallback";
GTMOAuth2Authentication *auth;
auth = [GTMOAuth2Authentication authenticationWithServiceProvider:#"Catapult Central"
tokenURL:tokenURL
redirectURI:redirectURI
clientID:kDoorkeeperClientID
clientSecret:kDoorkeeperClientSecret];
return auth;
}
- (void)signOut
{
}
- (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
finishedWithAuth:(GTMOAuth2Authentication *)auth
error:(NSError *)error
{
if (error != nil) {
#if DEBUG
NSLog(#"ERROR: %#", error);
#endif
} else {
NSURL *url = [NSURL URLWithString:#"https://api.lvh.me:3000/api/users/me"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
[fetcher setAuthorizer:auth];
[fetcher beginFetchWithDelegate:self didFinishSelector:#selector(currentUserFetcher:finishedWithData:error:)];
}
}
- (void)currentUserFetcher:(GTMHTTPFetcher *)fetcher
finishedWithData:(NSData *)data
error:(NSError *)error
{
if (error != nil) {
#if DEBUG
NSLog(#"ERROR: %#", error);
#endif
} else {
NSLog(#"Before: %#", self.accounts);
[self.tableView beginUpdates];
[self.accounts addObject:#"Success!!!"];
[self.tableView endUpdates];
// [self.tableView reloadData];
NSLog(#"After %#", self.accounts);
}
}
It's in the currentUserFetcher:finishedWithData:error: method that I add the object to the self.accounts mutable array. Now if I use this code it doesn't work:
[self.tableView beginUpdates];
[self.accounts addObject:#"Success!!!"];
[self.tableView endUpdates];
It fails at the line [self.tableView endUpdates]; with the following error message:
2013-03-28 08:56:21.040 Catapult for iOS[55012:c07] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2380.17/UITableView.m:1054
And on the endUpdates line, XCode is complaining saying Thread 1: breakpoint 1.3. Now, if I use this code, it works normally:
[self.accounts addObject:#"Success!!!"];
[self.tableView reloadData];
Now I suspect that it is failing because I add an object to the self.accounts instance variable but I don't actually add the cell. So my question is: How do I add a cell to the tableView from the currentUserFetcher:finishedWithData:error: method?
If you just override this method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
Calling [UITableView reloadData] should just work itself out. The UITableViewController will just ask the amount of data (cells) that are there (using "tableView:numberOfRowsInSection:") and is requesting the Cell for every indexPath using the first mentioned method.

Resources