How to navigate with popViewController - ios

I'm new to iOS development and I'm using parse.com as my backend.
I want to navigate from [View Controller 2] to [view Controller 3]
but is not working right.
Here is the code
http://i.stack.imgur.com/t6zZg.jpg
NSString *username = [self.usernameField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *password = [self.passwordField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *email = [self.emailField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([username length] == 0 || [password length] == 0 || [email length] == 0){
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Oops!" message:#"Make sure you enter a username, password , and email address!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show]; // This help to show your message successfully!
}
else {
PFUser *newUser = [PFUser user]; // This is a method to create new users
newUser.username = username;
newUser.password = password;
newUser.email = email;
[newUser signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Sorry!" message:[error.userInfo objectForKey:#"error"] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
}
else {
[self.navigationController popToViewController:_profilePhotoViewController animated:YES];
}
}];
}
}

According to the image you posted, VC3 is not displayed nor pushed to the controller before displaying VC2
This method popToViewController:animated: require that the controller is displayed before and in the controller stack in order to pop the stack back to it
You can achieve what you want by pop to root and then push your view
[self.navigationController popToRootViewControllerAnimated:NO];
[self.navigationController pushViewController:_profilePhotoViewController animated:YES];
Besure that _profilePhotoViewController is allocated before pushing it
.
.
UPDATE
In your storyboard set an identifier to your VC3 to be for example "VC3Identifier"
You can do this as you can see in the below image
Choose your controller
Show the utilities
show the identity inspector
Put 'VC3Identifier' in StoryBoard ID field
Then You will need to allocate a new instance of this controller and add it to the navigation controller
YourController *vc3 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC3Identifier"];
[self.navigationController setViewControllers:#[vc3] animated:YES];

OK, well, I am not totally sure how your code works and what will trigger the change of View, but in your code where you call this:
[self.navigationController popToViewController:_profilePhotoViewController animated:YES];
Is what you need to handle (assuming profilePhotoViewController is VC3).
Go to your Storyboard and ctrl+click+drag from the presenting view (the VC that you are starting from) and end up at VC 3. VC 3 should light up blue. Let go and you will see a drop down menu. Select "Push". This will create a segue for you to transition. Next you need to give it an identifier, so click on the segue that was just made and go to the Attributes Inspector and in the "Identifier" field type in "ProfilePictureSegue" and then press enter (or return).
Then go to the file that contains the code you posted and replace
[self.navigationController popToViewController:_profilePhotoViewController animated:YES];
With:
[self performSegueWithIdentifier:#"ProfilePictureSegue"];
And bob is your uncle. Run it.
EDIT
I looked at your picture again and it looks like your VC3 is in the wrong place. Create a segue from VC2 to VC 3, not from the Navigation Controller to VC3. Call the segue, as I explained above, in the VC2 .m file, and it will transition between.
If you need more explanations.

Related

How to move to 2nd ViewController after data validation complete

I'm creating a small app which verifies username and password from plist and shows user profile in other ViewController or (next screen).
I added a Login Button which validates username and password from user input with data stored in Plist(property list).
I also added a condition for show alert message when user entered wrong password or logged in successfully.
I created an another ViewController and connects with main ViewController via Login button.
my problem is when i clicked LOGIN button, it automatically moves to next view controller without checking username password and also not shows alert.
how to go next ViewController after validation completes and alert view done.
I need code in Objective-C
- (IBAction)submitButton:(UIButton *)sender {
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle]pathForResource:#"profileDetails" ofType:#"plist"]];
NSString *usernameentered = dict[#"user"][#"username"];
NSString *passwordentered = dict[#"user"][#"password"];
if ([self.username.text isEqualToString:usernameentered] && [self.password.text isEqualToString:passwordentered]) {
NSLog(#"login sucessfull");
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Attention"
message:#"LOGIN SUCCESS"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"OKAY" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
}else if ([self.username.text isEqual:#""] && [self.password.text isEqual:#""]) { UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Attention"
message:#"PLEASE ENTER Valid EMAIL AND PASSWORD"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"Okay ! Got it" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
}
}
I think you have use 'show' segue in tap of login button tap.you need to use custom segue for it. You can setup custom segue as below.
Step 1:
Give custom segue to current controller to target controll. You can also see in below image.
Step 2
- Give any unique identifier to segue. You can also see in below image.
Then use below code for redirection when credential is valid.
[self performSegueWithIdentifier:#"NextVC" sender:self];
"i created an another view controller and connects with main view controller via Login button"
If you're using segue to navigate then you should write code inside prepareForSegue or shouldPerformSegueWithIdentifier method. Otherwise you can navigate to ViewController through code inside submit button action.
Try writing this code inside shouldPerformSegueWithIdentifier method, it should work
- (IBAction)submitButton:(UIButton *)sender {
//Get stored username and password
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle]pathForResource:#"profileDetails" ofType:#"plist"]];
NSString *usernameentered = dict[#"user"][#"username"];
NSString *passwordentered = dict[#"user"][#"password"];
if ([self.username.text isEqual:nil] || [self.username.text isEqual:#""]) {
//Show alert for empty username
}
else if ([self.password.text isEqual:nil] || [self.password.text isEqual:#""]) {
//Show alert for empty username
}
else if (![self.username.text isEqualToString:usernameentered]) {
//Show alert for wrong username
}
else if (![self.password.text isEqualToString:passwordentered]) {
//Show alert for wrong password
}
else {
//All validation is right.
//Move to other Viewcontroller
UIStoryboard *main = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
NSString *Identifier = #"ViewController"; //Same as in storyboard
UIViewController *vc = [main instantiateViewControllerWithIdentifier:Identifier];
[self.navigationController pushViewController:vc animated:true]; //If you want to push
//[self.navigationController presentViewController:vc animated:YES completion:nil] //If you want to present
}
}

viewWillAppear UIAlertView not showing

I have two different view controllers, one for a dashboard and one for registration. I do not want the user to be able to interact with anything on the dashboard until the user logs in through an alertview. So every time the user navigates back to the dashboard or presses cancel and they are not logged in, I want the login alert to popup.
This works perfectly in all cases, including when the user hits the back button on the navigation bar in the registration view, but does not work when the user clicks OK on the alert in the registration page.
the dashboard view contains this code:
#property(strong) UIAlertView * alert;
//...
-(void)viewWillAppear:(BOOL)animated
{
user_email = [[NSUserDefaults standardUserDefaults] stringForKey:#"email"];
if ( user_email==nil ){
[self auto_login];
} else //...
}
-(void)auto_login
{
alert = [[UIAlertView alloc] initWithTitle:#"Login" message:nil delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Login",#"Forgot Password",#"Register",nil];
alert.alertViewStyle = UIAlertViewStyleLoginAndPasswordInput;
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
switch (buttonIndex) {
case 0:
{
self.debug.text = #"Cancel";
[self auto_login];
break;
}
//...
default:
{
self.debug.text = #"Register";
[self nav_register];
break;
}
}
}
-(void)nav_register
{
RegisterProfileController *rvc = [[RegisterProfileController alloc] init];
[self.navigationController pushViewController:rvc animated:YES];
}
The registration view controller contains this code:
-(void)catch_registration
{
NSString *response = [[NSString alloc] initWithData:self.httpdata encoding:NSASCIIStringEncoding];
if( [response isEqualToString:#"OK"] ){
UIAlertView *successAlert = [[UIAlertView alloc] initWithTitle:#"Success" message:#"..." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
successAlert.alertViewStyle = UIAlertViewStyleDefault;
[successAlert show];
}
else //...
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if ([alertView.title isEqualToString:#"Success"])
[self.navigationController popViewControllerAnimated:TRUE];
}
After debugging, I know that after clickedButtonAtIndex runs in the registration view controller, [alert show] runs in the dashboard view controller, and clickedButtonAtIndex does NOT run in the dashboard view controller, but no alert shows up.
Why isn't the alert showing or how can I debug this further?
If clickedButtonAtIndex "does NOT run" as you said, then the delegate might not be set correctly. In addition, you should likely move the code from viewWillAppear: to viewDidAppear: because the view is not in the view hierarchy at that point. Your solution might be a combination of these two issues.

Push view programmatically from UIAlertView button?

I would open a UIViewController from a UIAlertView button but it doesn't work.
When user terminate a game I show a UIAlertView with some text and two buttons: "Ok" to dismiss alert and "Score" to open the score page.
This is the actual UIAlertView:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"YOU WIN!"
message:[NSString stringWithFormat:#"You win a game in %d seconds!",seconds]
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:#"Score", nil];
[alert show];
and this is the code for push the view:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if([title isEqualToString:#"Score"]){
ScoreViewController *score = [[ScoreViewController alloc]init];
[[self navigationController] pushViewController:score animated:YES];
}
}
What I obtain is a completely black screen. Can someone help me? I can't figure it out what I'm doing wrong.
PS: ScoreViewController has a segue from the root view but of course I can't create a new one from storyboard because I want to perform segue programmatically, from the alert view button.
Hope I've been clear, any help will be very appreciate!
It looks like you're instantiating an instance of ScoreViewController and pushing onto your navigation controller. While this is a legitimate way of presenting another controller, it's different from performing a Storyboard segue.
First, obviously, you'll have to make sure you have a segue connecting your view controllers in the Storyboard. It sounds like you've made it that far, so next you'll want to make sure the segue has an identifier. Select the segue and set its identifier in the Attributes inspector. Then perform the segue in code:
[self performSegueWithIdentifier:#"YourIdentifier" sender:self];

Runtime error - "Nested Push animation" When popToRootViewController called

Im calling popToRootViewController in a UiViewController that is acting as a sign-in page. The method is called once the backend has authenticated the user and will now allow the user to access their account. When the root view Controller is shown again, I get the following two errors.
nested push animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
Here is the code for signing in a user.
- (IBAction)signIn:(id)sender
{
NSString *userName = [self.emailTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *password = [self.passwordTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (userName.length == 0 || password.length ==0){
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Invalid Login Credentials" message:#"Make sure you have entered a valid Username and Password" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alertView show];
}else{
[PFUser logInWithUsernameInBackground:userName password:password block:^(PFUser *user, NSError *error) {
if (error){
UIAlertView *errorAlertView = [[UIAlertView alloc]initWithTitle:#"Something went wrong" message:[error.userInfo objectForKey:#"error"] delegate:nil cancelButtonTitle:#"ok" otherButtonTitles:nil];
[errorAlertView show];
}else{
//dispatch_sync(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
//});
}
}];
}
}
The rootViewController is the initial view that the app launches into, and there it checks to see if there is a current user and if not the user will be sent to the login page. I do notice that by going into the Login page, there is a back arrow at the top of the navigation bar to go back to the rootViewController. Once The user signs in, there is now a back button in the same place that wants to take the user back to the LoginViewController which has been popped. I think this is where the issue is occurring.
This error is raised when you try to push several view controllers or pop more than one view controller. Are you sure the viewWillAppear method of your rootViewController is not attempting to push a view controller ? If so, you're attempting to push a view controller while another one is being popped. Hope this may help you!

Duplicate UIViewControllers on UINavigationController stack after application wakes

I just learnt IOS programming and am writing my first iPhone app.
My app provides info on an MKMapView, the view controller of which is the root view controller of a UINavigationController. If the mobile signal is bad i use mapViewDidFailLoadingMap:withError: to make the app push one of two different view controllers onto the navigation controller stack depending on what the user is doing. Code follows:
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error
{
NSLog(#"mapViewDidFailLoadingMap: %#", [error localizedDescription]);
[aiView stopAnimating];
if (mapTypeInt == 0) {
NSString *message =
[NSString stringWithFormat:#"Your signal is not currently
strong enough to download a map. Switching to table view."];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Maps Unavailable"
message:message
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
if (currentMode == #"retracingSteps")
{
RetraceViewController *rvc =
[[RetraceViewController alloc] initWithNibName:#"RetraceViewController" bundle:nil];
[[self navigationController] pushViewController:rvc animated:YES];
}
else{
TripTableViewController *ttvc = [[TripTableViewController alloc] init];
[[self navigationController] pushViewController:ttvc animated:YES];
}
}
else{
[self setMapType:0];
NSString *message = [NSString stringWithFormat:
#"Your signal is not currently strong enough to download a satellite map.
Switching to Standard Map view."];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Can't Use Satellite Maps"
message:message
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
}
}
Yesterday I tested the poor mobile signal in a rural valley and the correct second view controller was pushed onto the stack. What I then noticed when I locked the phone and rechecked the app after a few minutes was that on waking, the root view controller was displayed quickly followed by the view controller I expected. In effect what this did was to push an identical copy of the second view controller onto the stack. I discovered this when I had to tap the back button half a dozen times to get back to the root view controller.
What I would like the app to do on wake up is to immediately display the view controller that was live when the phone is locked rather than the root view controller. I do not know how to do this.
This is probably happened because even you lock the screen your app still receiving locations update (location services can run code in a real background mode), also when you push a view controller the previous one in the stack still exist and still receiving location update and doing everything has been designed to do, so even you push another controller, if something happen that for the implemented logic has to push another controller, this view controller has access to the navigation controller so has just to add to the stack another controller and push it (the navigation controller still the same for all the VC in the stack)
So what you have to do is stop to handle this cases when you push another controller and restart to do that when you pop back the controller
This bug was caused by the handler mapViewDidFailLoadingMap:withError: being called multiple times before control passed to the pushed view controller. I fixed the bug by making three changes to the handler method:
1) I removed the UIAlertView show and moved it to the pushed view controller;
2) I used a flag (initialized in viewDidAppear:) to return from the handler without executing the push if the handler was being called a second time;
3) I checked for the pushed view controller's existence before allocing it.
When all three of these changes were made, transitions between view controllers happened correctly. Code follows:
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error
{
[aiView stopAnimating];
if (mapViewFailed > 0) {
mapViewFailed ++;
return;
}
if (mapTypeInt == 0) {
[map setUserTrackingMode:MKUserTrackingModeNone];
[self removeAsNoticationsObserver];
if ([currentMode isEqualToString:#"retracingSteps"])
{
if (!rvc) {
rvc = [[RetraceViewController alloc] initWithNibName:#"RetraceViewController" bundle:nil];
}
[[self navigationController] pushViewController:rvc animated:YES];
}
else{
if (!ttvc) {
ttvc = [[TripTableViewController alloc] init];
}
[[self navigationController] pushViewController:ttvc animated:YES];
}
mapViewFailed ++;
}
else{
[self setMapType:0];
NSString *message = [NSString stringWithFormat:#"Your signal is not currently strong enough to download a satellite map. Switching to Standard Map view."];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Can't Use Satellite Maps"
message:message
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
}
}
No doubt a more elegant solution is available which I don't know about yet, being an iOS newb.

Resources