Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trial
James McCormack
12,659 PointsNavigation Controller-based Login requires 2 attempts
I'm going through the IOS videos but using my own project for trial which differs slightly (but helps me understand it a little better in my own way). With Tab Bar controllers and navigation controllers I get to a login and sign up form and can make use of parse.com as per the tutorials. When I register a new user it pops back to the root view controller. My issue is that when I login, the first attempt always re-displays the login view controller (even though the if/else statement in viewDidLoad of root should only perform segue to login on !currentUser). The second attempt works as expected. I've setup logging on the IF statement and can see that the first attempt pushes it back to use the segue. In fact, even a just clicking OK on the seconds attempt [with no text input] will error on the blank content but get me to the logged in root view.
Any ideas what might be making the first attempt still execute the segue push?
My guess is it doesn't wait for the login to be cached on the first attempt, and the round trip is too long in that time, hence a required 'refresh'.
Thanks
James McCormack
12,659 Points-
(void)viewDidLoad { [super viewDidLoad];
PFUser *currentUser = [PFUser currentUser];
if (currentUser) { NSLog(@"Current User: %@", currentUser); } else {
[self performSegueWithIdentifier:@"toLogin" sender:self]; NSLog(@"****** OFF TO LOGIN WE GO!! **********");
}
}
7 Answers
Stone Preston
42,016 PointsWell your view did load for your root view controller looks good. Try testing your sign up. If sign up works correctly it's probably a problem in your login controller.
James McCormack
12,659 PointsSign up works fine. However, I notice that when sign up returns to root with [self.navigationController popToRootViewControllerAnimated:YES]; (same command as with login) It doesn't call the viewDidLoad IF statement (in the logs), but goes straight to viewWillAppear.
Then again, login also works, its just not registered for some reason on the first attempt when it calls the viewDidLoad.
I've been looking at this for some time and can't see anything that might influence this behavior programmatically. I'll try adding an an activity indicator until the networking has been done and see if that helps.
I do live on the other side of the globe with relatively terrible internet speeds.
Thanks.
Ben Jakuben
Treehouse TeacherI think the issue may be that you are setting the currentUser variable on viewDidLoad when there is nobody logged in yet. Then when you log in and come back, viewDidLoad isn't being called because the view is already loaded and still in memory (we talk about this later in the course).
If you move your code to viewWillAppear it should work okay because viewWillAppear gets called every time the view controller is displayed on the screen, whereas viewDidLoad is not guaranteed to run every time.
James McCormack
12,659 PointsThanks Ben, Moved to viewWillAppear, but behavior remains the same. It takes only one attempt with credentials, but this still goes to login (i can see in the logs the 'else' for [self performSegueWithIdentifier:@"toLogin" sender:self] is processed) , whereby just clicking 'GO' at this point will take me to 'home' tableViewController (with popup error for incomplete fields - but navigates there all the same).
Any idea why (currentUser) would return false at this point to invoke the else, but not on the next click of 'GO'?
Ben Jakuben
Treehouse TeacherHmmm...not sure. Would you mind pasting in your new viewWillAppear method for reference?
James McCormack
12,659 Points- (void)viewWillAppear:(BOOL)animated
{
NSLog(@">>> Entering %s <<<", __PRETTY_FUNCTION__);
[super viewWillAppear:animated];
PFUser *currentUser = [PFUser currentUser];
if (currentUser)
{
NSLog(@"Current User: %@", currentUser);
}
else {
[self performSegueWithIdentifier:@"toLogin" sender:self];
NSLog(@"****** OFF TO LOGIN WE GO!! **********");
}
[self.tableView reloadData];
NSLog(@"<<< Leaving %s >>>", __PRETTY_FUNCTION__);
}
Ben Jakuben
Treehouse TeacherAh, I have a guess. It could be a timing issue. Can you post your login code? If your login is an asynchronous call, it might be returning a valid user after you're already back in this view controller. You can add a log statement in your block when returning from the login call to see when it returns. You might also want to delay your return to this view controller until you check for a successful login.
James McCormack
12,659 PointsGreat. I was thinking the same just didn't know how to best allow for that delay.
@implementation WOILoginViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
}
- (IBAction)login:(UIButton *)sender {
[self.processIndicator startAnimating];
NSString *userName = [self.userNameField.text stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *password = [self.passwordField.text stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([userName length] == 0 || [password length] == 0) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Oops!" message:@"Please complete all fields" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alertView show];
}
else {
[PFUser logInWithUsernameInBackground:userName password:password block:^(PFUser *user, NSError *error) {
if (error) {
UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@"Sorry!" message:[error.userInfo objectForKey:@"error"] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alerView show];
}
else {
NSLog(@">>> Entering %s <<<", __PRETTY_FUNCTION__);
[self.navigationController popToRootViewControllerAnimated:YES];
NSLog(@">>> Leaving %s <<<", __PRETTY_FUNCTION__);
}
}];
[self.processIndicator stopAnimating];
}
}
@end
Ben Jakuben
Treehouse TeacherOkay, you are popping back from the block, so [PFUser currentUser] should work once you're back in viewWillAppear of your other view controller. What happens if you log that directly in viewWillAppear, like:
NSLog(@"%@", [PFUser currentUser]);
James McCormack
12,659 PointsSo, i used the log...
- (void)viewWillAppear:(BOOL)animated
{
NSLog(@">>> Entering %s <<<", __PRETTY_FUNCTION__);
[super viewWillAppear:animated];
NSLog(@"User is: %@", [PFUser currentUser]);
PFUser *currentUser = [PFUser currentUser];
if (currentUser)
{
NSLog(@"Current User: %@", currentUser);
}
else {
[self performSegueWithIdentifier:@"toLogin" sender:self];
NSLog(@"****** OFF TO LOGIN WE GO!! **********");
}
[self.tableView reloadData];
NSLog(@"<<< Leaving %s >>>", __PRETTY_FUNCTION__);
}
And get the log...
014-01-22 08:40:03.691 inOrda_New[729:60b] User is: (null)
James McCormack
12,659 PointsThen when I click 'Go' again, i get..
2014-01-22 08:42:57.455 inOrda_New[729:60b] User is: <PFUser:QOOCu57Qpe:(null)> {
FullName = "John Doe";
email = "jdoe@doe.com";
friendsRelation = "<PFRelation: 0x16da9700>(<00000000>.(null) -> _User)";
username = jdoe;
}
Ben Jakuben
Treehouse TeacherHmmm. At this point, would you mind zipping up your project and emailing it to us (help@teamtreehouse.com)? Might be easier for me to help troubleshoot with all the code in the context of the project.
James McCormack
12,659 PointsThanks Ben. Sent it now.
James McCormack
12,659 Pointssorry, its a bit messy here. Is there a better way to post it so its clean?
Ben Jakuben
Treehouse TeacherNo problem! Check out my edits - I added three backticks above and below your code as a "code fence" to make it display properly. The language ("objc") is optional.
James McCormack
12,659 PointsI have a workaround. It seems definitely a delay. I used the following in the viewWillAppear
if ([self respondsToSelector:@selector(loginHoldDown)]) {
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(loginHoldDown) userInfo:nil repeats:NO];
}
and for the method I moved the if/else to login segue (including the list PFQuery in the 'current user' condition) to after the timeout.
- (void)loginHoldDown {
PFUser *currentUser = [PFUser currentUser];
if (currentUser) {
NSLog(@"Current user: %@", currentUser.username);
PFQuery *query = [PFQuery queryWithClassName:@"Messages"];
[query whereKey:@"recipientIds" equalTo:[[PFUser currentUser] objectId]];
[query orderByDescending:@"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(@"Error: %@ %@", error, [error userInfo]);
}
else {
// We found messages!
self.messages = objects;
self.allLists = [NSMutableArray arrayWithObjects:self.messages,nil];
NSLog(@"Retrieved %lu messages", (unsigned long)[self.messages count]);
[self.tableView reloadData];
}
}];
}
else {
[self performSegueWithIdentifier:@"toLogin" sender:self];
}
}
This holds for 1 second on the 'home' view controller and allows the 'current user' to be identified.
Interestingly if i only set it to 0.5 it doesn't give it enough time and login segue kicks in.
Stone Preston
42,016 PointsStone Preston
42,016 PointsPost your view did load of your root view controller where the if else is