views:

613

answers:

4

I'm trying my hand at some iPhone programming and I've run across something that may be fairly obvious to veterans but I'm not exactly sure why it's happening. I have two UIViewController classes, and I want to access a method from the other class. I have two NSObject classes associated with both of them in IB (with the Class file as UpdateClass for each), and I'm trying to create a class and call a method. Seems pretty easy, but the problem is that it's calling the method (according to NSLog) but it's not updating the labels. Here's my code:

//UpdateClass.h
#import <Foundation/Foundation.h>

@interface UpdateClass : NSObject {
    IBOutlet UILabel *lbl1;
    IBOutlet UILabel *lbl2;
}

@property (nonatomic, retain) IBOutlet UILabel *lbl1;
@property (nonatomic, retain) IBOutlet UILabel *lbl2;

- (void)updateLabels;

@end

//UpdateClass.m

#import "UpdateClass.h"

@implementation UpdateClass

@synthesize lbl1;
@synthesize lbl2;


- (void)updateLabels {
    NSString *someWord = @"Whatever"; // This could be anything
    NSLog(@"NSObject Update");
    [lbl1 setText:someWord];
    [lbl2 setText:someWord];
}


@end

//ViewController1.h
#import <UIKit/UIKit.h>
@class UpdateClass;
@interface ViewController1 : UIViewController {
IBOutlet UIButton *button1;
NSObject *UpdateClassObject;
UpdateClass *updateClass;
}
@property (nonatomic, retain) IBOutlet UIButton *button1;
@property (nonatomic, retain) NSObject *UpdateClassObject;
@property (nonatomic, retain) UpdateClass *updateClass;
- (void)updateLabels:(id)sender;
@end

//ViewController1.m
#import "ViewController1.h"
#import "UpdateClass.h"

@implementation ViewController1
@synthesize button1;
@synthesize UpdateClassObject;
@synthesize updateClass;

- (void)viewDidLoad {
    [super viewDidLoad];
    updateClass = [[UpdateClass alloc] init];
}

- (void)updateLabels:(id)sender; { //This is connected to TouchDown on button1
    NSLog(@"Calls UpdateLabels");
    [updateClass updateLabels]; //Calls the class method
}

//ViewController2.h
#import <UIKit/UIKit.h>
@class UpdateClass;
@interface ViewController2 : UIViewController {
IBOutlet UIButton *button2;
NSObject *UpdateClassObject;
UpdateClass *updateClass;
}
@property (nonatomic, retain) IBOutlet UIButton *button2;
- (void)updateLabels:(id)sender;
@end

//ViewController2.m
#import "ViewController2.h"
#import "UpdateClass.h"

@implementation ViewController2
@synthesize button2;
@synthesize UpdateClassObject;
@synthesize updateClass;

- (void)viewDidLoad {
    [super viewDidLoad];
    updateClass = [[UpdateClass alloc] init];
}

- (void)updateLabels:(id)sender; { //This is connected to TouchDown on button2
    NSLog(@"Calls UpdateLabels");
    [updateClass updateLabels]; //Calls the class method
}

So there is an NSObject hooked up in IB for both Views. There is a label on each view hooked up to the NSObject and the File's Owner (may not be necessary to hook them up to both). When the button is pressed (which is also hooked up properly in IB), the label is supposed to change to some string. NSLog reports that the methods are called, but the labels don't change. What's wrong here?

(note: there may be some small mistakes as I had to type out some of this because I don't have all the code with me at the moment).

A: 

If the NSLog() runs but the labels don't update, it's almost certainly that lbl1 and lbl2 are nil. They are almost certainly nil because they're not actually connected in IB, or you're running this code prior to -viewDidLoad running. In your design here, you must avoid -updateLabels until after the view has actually loaded and wired its outlets. Views load lazily, so these labels won't be created until right before displaying the view for the first time.

The short version is that you should probably be calling -updateLabels in -viewDidLoad.

Rob Napier
How should this be handled if the values change? Like, in a real program, the someWord string might be a changing variable. Is this a bad way to setup a program that changes the text of the labels fairly often?
If the text changes often, then you should probably use notifications to keep the UI elements up to date. There should be a Model object that has the data you're representing. When the data changes, it should post a notification. The ViewController, when it is visible (and only when it is visible), should observe that notification and update the label to match the new value. You should use -viewWillAppear/Disappear to subscribe and unsubscribe from the notifications. http://developer.apple.com/documentation/Cocoa/Conceptual/Notifications/ http://robnapier.net/blog/thoughts-nsnotifications-42
Rob Napier
A: 

OK, there's several aspects of this code that are confusing, so I'll start with the apparent distinction you are making between objects and classes.

What is the purpose of:

NSObject *UpdateClassObject;

What is it? How is it assigned (from a nib?)? From the name, it seems you simply want an instance of your UpdateClass class, which is the purpose of:

UpdateClass *updateClass;

You don't appear to be using the former so I'm not sure why it's there at all.

Now, on to the crux of your problem: You are calling [updateClass updateLabels] and your NSLog statement indicates that it is indeed being called. Which tells me that the most likely problem is both instances of your labels lbl1 and lbl2 are in fact nil. Remember, in Objective-C, sending a message (like updateText: in this case) to nil won't throw any errors, it will just fail silently.

So why are they nil? Well, they are declared as IBOutlets which makes me think you have a NIB file whose file owner is UpdateClass which does the actual wiring of some UILabels to your member variables. If this is correct, then you need to initialze updateClass with the NIB correctly:

updateClass = [[UpdateClass alloc] initWithNibName:@"UpdateClass" bundle:nil]; // or whatever the nib is called

But what you are actually doing is:

updateClass = [[UpdateClass alloc] init];

This means the class is never initialized from a NIB and your labels aren't ever being wired up.

One final point, using the word 'Class' in your class names is fairly redundant - its obviously a class. This can also lead to confusion - the instance variable you are storing your instance of UpdateClass in is called updateClass - to me, this would mean its actually storing an instance of an object whose type is Class (which you could get by calling [SomeClass class]).

Luke Redpath
A: 

You are sending the updateLabels message to the wrong UpdateClass.

You said you hooked up an instance of UpdateClass in InterfaceBuilder which has the connections to the labels set in IB too. Thus, when your view is loaded, it has the UpdateClass object injected already from the NIB.

In your viewDidLoad implementation you create a new instance of UpdateClass. That instance does not have the labels set (they are nil). Thus, once you send the updateLabels method to that object, that object has no labels to update.

Just throw out the declaration and allocation of updateClass in viewDidLoad and send the updateLabels message to UpdateClassObject. Consider renaming the member UpdateCLassObject to updateClass or even better, rename the class to something like updater. Also consider using a base class instead.

VoidPointer
A: 

A word on design here:

You seem to want to share some functionality among two or more view controller implementations. In this case, you may want to create a common base class that derives from UIViewController and derive your two views from that class. The BaseView would declare the outlets for common UI components which you can connect in IB.

@interface BaseUIViewController : UIViewController {
    IBOutlet UILabel *lbl1;
    IBOutlet UILabel *lbl2;
}

@property (nonatomic, retain) IBOutlet UILabel *lbl1;
@property (nonatomic, retain) IBOutlet UILabel *lbl2;

- (void)updateLabels;

@end

...

@interface ViewController1 : BaseUIViewController {
    IBOutlet UIButton *button1;

...

VoidPointer