3

I have a set of code that parses an XML file to find a user ID number. This ID number is sent to another view controller for a SOAP WSDL request. The problem is that the value is never set.

My two classes are LoginPageViewController and EMIMenuTableViewController.

I am trying to pass an integer value from LoginPageViewController to EMIMenuTableViewController.

Here is the header file for EMIMenuTableViewController:

#import <UIKit/UIKit.h>
@interface EMIMenuTableViewController : UITableViewController
@property (nonatomic) int userNumber;
@end

Here is the header file for LoginPageViewController:

#import <UIKit/UIKit.h>

@interface LoginPageViewController : UIViewController <NSXMLParserDelegate>

{
    NSXMLParser *xmlparser;

    NSString *classelement;
    NSMutableArray *titarry;
    NSMutableArray *linkarray;
    bool itemselected;
    NSMutableString *mutttitle;
    NSMutableString *mutstrlink;
    int userNumber;
    NSString *parsedString;
}

// Username and password text fields
@property (weak, nonatomic) IBOutlet UITextField *usernameTextField;
@property (weak, nonatomic) IBOutlet UITextField *passwordTextField;

// Logging in and registering
- (IBAction)loginButtonSelected:(id)sender;
@property (weak, nonatomic) IBOutlet UIButton *registerButton;

// Unwind segue IBAction for logging out
- (IBAction)logoutAndUnwind:(UIStoryboardSegue *)segue;

@end 

Here is where the value is set in LoginPageViewController:

- (void)parserDidEndDocument:(nonnull NSXMLParser *)parser {
EMIMenuTableViewController *controller = [[EMIMenuTableViewController alloc] init];
// If the user is authenticated, allow them to proceed with the segue
if([parsedString containsString:@"User Authenticated"]) {

    >>>controller.userNumber = userNumber<<<; - set here
    NSLog(@"User:%d", userNumber);
    [self performSegueWithIdentifier:@"login_success" sender:self];
    NSLog(@"Logged In");

    // If the server says that the user isn't found, notify the user to create an account.
} else if([parsedString containsString:@"User Not Found"]) {

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"User Not Found" message:@"Please create an account!" delegate:self cancelButtonTitle:@"Close" otherButtonTitles:nil, nil];
    [alert show];

    // If the server replies that the password is incorrect, notify the user.
} else if([parsedString containsString:@"Password Incorrect"]) {

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Incorrect Username/Password" message:@"Plese try again." delegate:self cancelButtonTitle:@"close" otherButtonTitles:nil, nil];
    [alert show];

} else {

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"An error has occurred!" message:@"Please check your network connection and try again." delegate:self cancelButtonTitle:@"close" otherButtonTitles:nil, nil];
    [alert show];

}

}

Here is the code that shows that the value isn't being set (this is in EMIMenuTableViewController):

NSLog(@"User: %d", _userNumber);

Also, here is the console output. You can see that the value is set in LoginPageViewController where I logged it, but when it is logged in EMIMenuTableViewController, it is 0.

2015-07-08 15:09:41.151 EMI[1971:173596] User:27808
2015-07-08 15:09:41.179 EMI[1971:173596] Logged In
2015-07-08 15:09:41.186 EMI[1971:173596] User: 0

I figured I was doing this right, but I can't figure out what is wrong. Am I setting the variable wrong?

Thanks.

Landon Haugh
  • 89
  • 1
  • 6
  • 2
    Your property isn't global, each instance of your class will have a separate userNumber property. The one you are setting is on an instance that goes away once your function finishes. You need to set it on the instance that you are segueing to. You can find more information about passing data to view controllers here: http://stackoverflow.com/questions/5210535/passing-data-between-view-controllers – dan Jul 08 '15 at 20:35

3 Answers3

4

EMIMenuTableViewController *controller = [[EMIMenuTableViewController alloc] init]; will create a new instance and in your code you are actually not using it meaning that your presenting some other instance of EMIMenuTableViewController.

you need to get a reference of that presented instance of EMIMenuTableViewController and then set userNumber on that instance only

if you are using storyboard with segue to present EMIMenuTableViewController the process is as follows

1)give a segue identifier for the above mentioned segue (here it is "segue_to_emimenutable")

2)declare a global variable called "userNumber" and in the parserDidEndDocument method update the userNumber

3)then in the prepare segue method pass that to the presenting instance of EMIMenuTableViewController

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"segue_to_emimenutable"]) {
    EMIMenuTableViewController *destinationViewController = segue.destinationViewController;
    destinationViewController.userNumber = self.userNumber;
}

}

if you are presenting any other method that storyboard with segue mention it in the question so that my answer can modified for that

Hashim MH
  • 1,576
  • 1
  • 15
  • 29
2

The instance controller that you created and set the value is not the one that is being used when the storyboard displays it..

You should do that in prepareForSegue..
1. Store the userNumber in a global property in LoginViewController.. 2. Implement prepareForSegue and set the value to your MenuTableViewController

Something like the below

    controller.userNumber = userNumber  //Delete this
  // Instead of the above line line,Store it in your property.. 
    self.userNumber = userNumber



- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
{
    if ([segue.identifier isEqualToString:@"login_success"]) {
        EMIMenuTableViewController *destViewController = segue.destinationViewController;
        destViewController.userNumber = self.userNumber;
    }
}
Subbu
  • 2,138
  • 14
  • 20
  • I tried this, and I got this error on the main thread: 2015-07-08 16:39:37.381 EMI[2220:210477] -[UINavigationController setIdNumber:]: unrecognized selector sent to instance 0x79b06c00 2015-07-08 16:39:37.384 EMI[2220:210477] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationController setIdNumber:]: unrecognized selector sent to instance 0x79b06c00' – Landon Haugh Jul 08 '15 at 21:41
  • @LandonHaugh, It appears from the error that the segue points to a navigationvc with your menu vc as its root. I've edited my answer accordingly. My answer (was) equivalent to this one in terms of its advice... we authored simultaneously. – danh Jul 08 '15 at 21:53
  • @danh Holy crap thank you man. I've been trying to get this to work for 6 hours today :( You're a life saver! – Landon Haugh Jul 08 '15 at 21:57
2

This line...

EMIMenuTableViewController *controller = [[EMIMenuTableViewController alloc] init];

creates an EMIMenuTable vc, and then this line

controller.userNumber = userNumber;

sets a property on it. But that controller lives for only milliseconds. It is immediately released by ARC when it goes out of scope.

Meanwhile, the performSegue creates a new copy of the EMIMenuTableVC, and that one never gets a chance to hear about the userNumber found in the parse.

So we need to get that value from the parse conveyed to the segue's destination view controller. A simple way of doing it would be to declare a userNumber as a property of the LoginVC also, e.g.

@interface LoginPageViewController : UIViewController <NSXMLParserDelegate>
// ...
// more formal than your from the EMIMenuTable vc, but equivalent...
@property (nonatomic, assign) NSInteger userNumber;

With that, your parser shouldn't build its own EMIMenuTable vc. Just set the property on self...

if([parsedString containsString:@"User Authenticated"]) {
    // ...
    self.userNumber = userNumber;
    // ...
    [self performSegueWithIdentifier:@"login_success" sender:self];

Now, implement prepare for segue, and set the value on the destination vc.

EDIT - changed this with the understanding that the segue points to a navigation view controller...

// be sure to import
#import "EMIMenuTableViewController.h"
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"login_success"]) {
        UINavigationController *navVC = (UINavigationController *)segue.destinationViewController;
        EMIMenuTableViewController *vc = (EMIMenuTableViewController *)navVC.viewControllers[0];
        // finally
        vc.userNumber = self.userNumber;
    }
}

A slightly funkier approach is to misuse the sender: parameter of the performSegue. Then you can skip the instance variable for LoginVC, and just pass the value directly like this...

[self performSegueWithIdentifier:@"login_success" sender:@(userNumber)];

This is weird, because the performSegue sender is a number. But it will work, since the type is the generic id. Your prepare method then looks the same, except...

    if ([segue.identifier isEqualToString:@"login_success"]) {
        UINavigationController *navVC = (UINavigationController *)segue.destinationViewController;
        EMIMenuTableViewController *vc = (EMIMenuTableViewController *)navVC.viewControllers[0];
        NSNumber *paramTrick = (NSNumber *)sender;
        vc.userNumber = [paramTrick intValue];
    }

But I guess I don't recommend this way, especially if you're just starting out.

danh
  • 62,181
  • 10
  • 95
  • 136