0
votes

I'm having a lot of trouble with what seems like a very simple thing. I cannot update a UILabel programmatically from a Navigation-based iOS App. I don't want to use a button as this label is designed to report the status of an external system, and should update on launch. There is no need to make the user go though the extra step on touching the button if I don't have to.

The following is a somewhat exhaustive list of the steps I've taken. I'm sorry if some of this seems unnecessary, but in my experience even the smallest forgotten step can be the cause of the issue.

From a fresh Navigation-based App in Xcode here are the steps I'm taking:

  1. Replace UITableView with a generic UIView class
  2. Re-wire File's Owner's view outlet to the new UIView
  3. Add a UILabel to the center of the UIView, make the text centered, and leave the default text.
  4. Save and Exit Interface Builder
  5. RootViewController.h
    #import <UIKit>
    @interface RootViewController : UIViewController {
        UILabel *myLabel;
    }
    @property (nonatomic, retain) IBOutlet UILabel *myLabel;
    @end
  6. RootViewController.m
    #import "RootViewController.h"
    @implementation RootViewController
    @synthesize myLabel;
    ...
  7. Removed TableView stuff from RootViewController.m
  8. Wire IBOutlet myLabel to the Label in RootViewController.xib
  9. Save and Exit Interface Builder
  10. tempNavAppAppDelegate.m
    ...
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
    // Add the navigation controller's view to the window and display. [self.window addSubview:navigationController.view]; [self.window makeKeyAndVisible];
    RootViewController *rootViewCont = navigationController.visibleViewController; rootViewCont.myLabel.text = @"test"; NSLog(@"Label Text: %@", rootViewCont.myLabel.text);
    return YES; } ...
  11. Build/Run

The Label shows as "Label" not "test". And the log reports:tempNavApp[94186:207] Label Text: (null)

I've tried a number of different ways to get this done, but any help would be appreciated.

4

4 Answers

2
votes

The Journey

After discovering that my rootViewCont.myLabel was also nil, thanks to the help of mprudhom, I decided to test and see if I could assign myLabel.text a value in RootViewController.m's - (void)viewDidLoad method. It worked, I was able to change the text directly from the RootViewController. But while this proved my View Controller wasn't broken, it did not solve my initial desire to change the UILabel from tempNavAppAppDelegate.m.

Elliot H. then suggested that navigationController.visibleViewController wasn't actually returning a view controller. I had tested for the value of rootViewCont and it came back as a RootViewController, but Elliot's suggestion got me thinking about the app's lifecycle and when the different parts of my code was actually loaded up.

So I started printing an NSLog at each step of the launch process (application:didFinishLaunchingWithOptions:, applicationDidBecomeActive:, viewDidLoad, viewDidAppear:), and discovered to my surprise that [self.window makeKeyAndVisible]; does not mean that the view will load before application:didFinishLaunchingWithOptions: is complete.

With that knowledge in hand I knew where the problem was. The solution (or at least my solution) seems to be NSNotificationCenter. I have now registered for notifications in tempNavAppAppDelegate and I am broadcasting a notification in RootViewController's viewDidAppear: method.


The Pertinent Code

RootViewController.h:

@interface RootViewController : UIViewController {
    IBOutlet UILabel *myLabel;
}
@property (nonatomic, retain) UILabel *myLabel;
@end

RootViewController.m:

@implementation RootViewController
@synthesize myLabel;
- (void)viewDidLoad {
    [super viewDidLoad];
    NSParameterAssert(self.myLabel);
}
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"viewDidAppear" object:self];
}

tempNavAppAppDelegate.h:

@interface tempNavAppAppDelegate : NSObject  {
    UIWindow *window;
    UINavigationController *navigationController;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
- (void)viewDidAppearNotification:(id)notification;
@end

tempNavAppAppDelegate.m:

@implementation tempNavAppAppDelegate
@synthesize window;
@synthesize navigationController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self.window addSubview:navigationController.view];
    [self.window makeKeyAndVisible];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidAppearNotification:) name:@"viewDidAppear" object:nil];
    return YES;
}
- (void)viewDidAppearNotification:(id)notification
{
    NSString *noteClass = [NSString stringWithFormat:@"%@", [[notification object] class]];
    if ([noteClass isEqualToString:@"RootViewController"]) {
        RootViewController *noteObject = [notification object];
        noteObject.myLabel.text = @"Success!";
    }
}

1
votes

If this code is printing nil:

rootViewCont.myLabel.text = @"test";
NSLog(@"Label Text: %@", rootViewCont.myLabel.text);

Then almost certainly it is because rootViewCont.myLabel itself is nil. Try logging the value of rootViewCont.myLabel as well and you'll see.

Are you sure you wired up the label to your UILabel IBOutput declaration in Interface Builder? That's most commonly the problem.

I personally always assert all my expected outlets in viewDidLoad so that I catch early on when the outlets have been (accidentally or not) been decoupled in Interface Builder. E.g.:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSParameterAssert(rootViewCont.myLabel);
}
0
votes

your interface should look like this

#import <UIKit>
@interface RootViewController : UIViewController {
    // IBOutlet here...
    IBOutlet UILabel *myLabel;
}
@property (nonatomic, retain) UILabel *myLabel;
@end
0
votes

Is visibleViewController actually returning the view controller? My guess is since application:didFinishLaunchingWithOptions: hasn't returned yet, it's possible UINavigationController hasn't properly configured that property to return yet, even though you've added the navigation controller's subview to the view hierarchy, it's probably that visibleViewController isn't valid until after viewDidAppear: is called on the view controller in question.

Try having an IBOutlet to the RootViewController directly, or create it programmatically, and then assign the label text.

Just a general reminder: If an object is nil (in this case visibleViewController would be returning nil), and you send it a message, you won't crash, because messages to nil are valid and won't do anything. When you call the myLabel accessor on the rootViewCont object, if rootViewCont is nil, myLabel will return nil always.