Updating text in a UILabel from the appDelegate

Discussion in 'iOS Programming' started by l0uismustdie, Dec 17, 2010.

  1. l0uismustdie macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #1
    Hello. I am trying to display some text in a UILabel I have created. The situation is as follows: I have a viewController (AccountCreation) which is connected with a xib file and has a bunch of text input boxes and a button to connect to a server and attempt to create an account. What I am trying to do is display the result of the account creation (success or failure) in a label on this view. The way I have this set up is once the "create account" button is pressed my program heads to my appDelegate who launches the URL connection protocols I've established which then returns to my appDelegate (this is where I would like to update the label). Some code that might be relevant:

    Code:
    @interface AccountCreation : UIViewController {
    	IBOutlet UITextField	*username,*email,*password,*confirmPassword;
    	IBOutlet UILabel		*errorLabel;
    	BOOL					moveViewUp;
    	CGFloat					scrollAmount;
    }
    -(IBAction)createAccount:(id)sender;
    -(void)scrollTheView:(BOOL)movedUp;
    -(void)updateError;
    @property(nonatomic,retain)UITextField	*username;
    @property(nonatomic,retain)UITextField	*email;
    @property(nonatomic,retain)UITextField	*password;
    @property(nonatomic,retain)UITextField	*confirmPassword;
    @property(nonatomic,retain)UILabel		*errorLabel;
    @end
    
    And in my appDelegate:
    Code:
    -(void)createAccountAttempt:(NSData *)data
    {
    	AccountCreation	*accountController = [[AccountCreation alloc] init];
    
    	NSString		*accountResponse=[[NSString alloc]initWithData:data encoding:NSASCIIStringEncoding];
    	NSString		*errorLog;
    	NSRange			start,end;
    	
    	NSLog(@"account creation response: %@",accountResponse);
    	start=[accountResponse rangeOfString:@"<error>"];
    	
    	//error with account creation
    	if(start.location>0)
    	{
    		start=[accountResponse rangeOfString:@"<error_msg>"];
    		start.location+=[@"<error_msg>" length];
    		end=[accountResponse rangeOfString:@"</error_msg>"];
    		errorLog=[accountResponse substringWithRange:NSMakeRange(start.location, end.location-start.location)];
    //		NSLog(@"errorLog: %@",errorLog);
    		accountController.errorLabel.text=errorLog;
    	}
    	
    	[accountResponse release];
    }
    
    I've noticed that my errorLabel is null inside of here. I've tried creating a method in the viewController to update the label.

    Thanks for anyone's help.
     
  2. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #2
    I think the problem is you've created a new instance of AccountCreation rather than trying to use the existing one.
     
  3. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #3
    You are definitely right...but I'm not sure the most effective way to fix this. What I just tried to do was declare an AccountCreation variable inside my appDelegate header file. Then when I am doing to present that view I initialize the AccountCreation variable and then hopefully could just use that throughout my delegate. However, this is giving me an error that I am trying to present a nil modal view. What I have changed:

    In the app delegate header:
    Code:
    AccountCreation		*accountController;
    ...
    @property(nonatomic,retain)IBOutlet AccountCreation	*accountController;
    
    Then later in the appDelegate.m
    Code:
    [accountController init];
    [viewController presentModalViewController:accountController animated:YES];
    
    Previously I had accountController declared like this:
    Code:
    AccountCreation	*accountController = [[AccountCreation alloc] init];
    [viewController presentModalViewController:accountController animated:YES];
    
    Thanks for your help!
    and this worked out to the point of displaying the view at least...
     
  4. ulbador macrumors 68000

    ulbador

    Joined:
    Feb 11, 2010
    #4
    Personally I would redesign the application a bit. Although you can use the application delegate as a sort of "central hub" for the application, I think it usually ends up complicating and confusing things.

    I guess the question is why are you using/abusing the app delegate in this fashion?

    Instead of:

    Button clicked
    App Delegate Does network stuff
    App Delegate Updates text field in original view controller


    Do:

    Button Clicked
    Create a custom object for network stuff
    original view controller updates text field from custom object data

    I know my first few apps (when I was just learning Objective C) abused the app delegate in this fashion. The end result is there is usually a way better way to do it. From what I see you are trying to centralize things a bit so you can reuse the code.
     
  5. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #5
    Haha...guess I am just an abusive programmer. So, this is my first iPhone app...and it's probably more complicated that a first app should be. The reason behind my abuse is that I have a number of callback functions associated with the URL connections that I originally thought should just be put in the appDelegate. So, there are 3 views total in my app (an initial log in screen, a create account screen, and a screen that displays results once you have successfully logged in).

    What you are suggesting is putting all the callback functions inside the view controllers and updating the labels from there?
     
  6. ulbador, Dec 18, 2010
    Last edited: Dec 18, 2010

    ulbador macrumors 68000

    ulbador

    Joined:
    Feb 11, 2010
    #6
    The scenario you mention is EXACTLY why in my first few apps I did what you did.

    Basically, you would move all your networking code into a custom object. Then you have two choices.

    The "proper" and "clean" way would be to use protocols and delegates. This would tell your networking object to use the callback methods in whatever view controller created the object, instead of the local ones. The nice part about this is that the callbacks for say, the Account Creation ViewController can be completely different and do different things than the callbacks for the Account Update ViewController.

    You basically define your networking callbacks in a centralized location, but do the full implementation a different location. This is probably pretty complicated especially since you said you are new, but once you do it once and understand it, you will be lightyears ahead of many other Objective C programmers.

    http://iphonedevelopertips.com/objective-c/the-basics-of-protocols-and-delegates.html

    The probably easier way for you is just just use NSNotificationCenter. Basically, when your networking code hits the callbacks, you post a notification. Before your view controller calls the networking code, you tell it to listen for this notification.

    http://iphonedevelopertips.com/cocoa/basics-of-notifications.html


    Although I'm sure 1000 people will point out the flaws of this, I am of the belief that the application delegate should be as minimal as possible. If you still do need a sort of "global" variable like that, you can use singletons.
     
  7. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #7
    Right on. I will look over those sources a little later. This portion isn't critical to my application right now. But I currently do have a separate URL object that handles all my URL connections and so on. For example when I am making a call to the server to create an account I do the following:
    Code:
    -(void)createAccount:(NSString *)URL
    {
    	URLRequest *rq = [[[URLRequest alloc]init]autorelease];	
    	[rq data:self requestSelector:@selector(createAccountAttempt:) withURL:URL andMethod:@"createAccount"];
    }
    
    Where createAccout: is run when a button in the AccountController view is pressed. This function is defined in the appDelegate but there is no reason I couldn't move this to the AccountController along with the createAccontAttempt:.

    This might help I think...I will try that out in a little bit and read through some of those sources. Thanks for your help!
     
  8. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #8
    And your networking code doesn't necessarily have to even be in viewControllers. You could set up a whole custom class to handle it, if you wanted to.
     
  9. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #9
    Hmmm...I'm not sure what you mean by that. But putting everything in my view controller works perfectly. I just moved all my URL connection functions into the view controller and go back to the app delegate only to dismiss the modal view. This is a much better strategy.

    Later in the code I am doing some really horrible things where I created a textView and to update the text repeatedly I append some stuff to a textLabel associated with the view, dismiss the modal view controller, then display it again (awful) but I can now use this same strategy there too.

    Thanks so much!
     
  10. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #10
    Okay. Which part confuses you?
     
  11. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #11
    I'm not sure what you mean by having a custom class to handle my network stuff. I thought that's what I was doing. I have a URLconnection class that handles all the connection stuff. Is that what you mean?

    Code:
    -(void)createAccount:(NSString *)URL
    {
    	URLRequest *rq = [[[URLRequest alloc]init]autorelease];	
    	[rq data:self requestSelector:@selector(createAccountAttempt:) withURL:URL andMethod:@"createAccount"];
    }
    
    This is running from my AccountController...URLRequest is my custom URL class...
     
  12. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #12
    Sorry, had a brain fart. Thought you meant NSURLRequest. I'll just move along then. :)
     
  13. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #13
    I have some more thoughts about this topic that you guys will likely be able to answer. When I am calling my URL class to make it's connection I am also having it do some work on some of the variables from my view (for example concatenating the email and password and doing an md5 hash of them). So, to manipulate the things that are currently in the view I am storing them in instance variables in my appDelegate and accessing them like that from the URL class. Some relevant codes:

    This is in the AccountController
    Code:
    -(void)createAccountButton:(id)sender{
    
    	vmAppDelegate *appDelegate=[[UIApplication sharedApplication]delegate];	
    	
    	//these will be used to check for availability and create user in URLrequest
    	appDelegate.uName=username.text;
    	appDelegate.email=email.text;
    	appDelegate.pWord=password.text;
    	appDelegate.confirmPWord=confirmPassword.text;
    	
    	//SUBJECET TO CHANGE BASED ON ACTUAL ADDRESS OF SERVER!
    	URLRequest *rq = [[[URLRequest alloc]init]autorelease];	
    	[rq data:self requestSelector:@selector(createAccountAttempt:) withURL:@"http://apt403.homelinux.com/iphoneproject/create_account.php" andMethod:@"createAccount"];
    }
    
    Then from the URL class I would do something like this:
    Code:
    if(method==@"createAccount")
    {
         vmAppDelegate	*appDelegate=[[UIApplication sharedApplication]delegate];
         NSString		*newURL=[NSString stringWithFormat:@"%@",url];
         NSString		*passwordHash=appDelegate.pWord;
    
         //hash concactination of password + email address
         passwordHash=[passwordHash stringByAppendingString:appDelegate.email];
         passwordHash=[self md5:passwordHash];
    		
         //add on email, hashed password, and username
         newURL=[newURL stringByAppendingString:[NSString stringWithFormat:@"?email_addr=%@&",appDelegate.email]];
         newURL=[newURL stringByAppendingString:[NSString stringWithFormat:@"passwd_hash=%@&",passwordHash]];
         newURL=[newURL stringByAppendingString:[NSString stringWithFormat:@"user_name=%@",appDelegate.uName]];
    
        theRequest = [[NSMutableURLRequest requestWithURL:[NSURL URLWithString:newURL]]retain];
    }
    
    It seems like I am wasting some active memory by having these instance variables but I can't think of an easier way to do this.
     
  14. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #14
    And by AccountController you are referring to the AccountCreation class, correct? Accuracy is important in getting help and, especially, in the code itself.
     
  15. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
  16. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #16
    What you are running into is a common need of what basically boils down to "sharing values between objects". There are a number of approaches to solving this including: storing those values in the appDelegate (not usually recommended, though), storing them in NSUserDefaults, using a singleton, creating properties for them in the destination class and setting their values prior to calling any methods in the destination class. (There's probably some other approaches I've missed). Hope that helps.
     

Share This Page