14

I have two classes in my app class A and Class B. Both class A and B are instances of UIViewController. Class A has a button that when pushed pushes class B onto the stack. Class B has a string that class A would like to observe and update it's interface with as needed. I've been able to use: [self addObserver:self forKeyPath:@"name" options:0 context:NULL]; in class B to view the changes to the string. When i try and use the following in class A viewWillAppear method:

ClassB *b = [[ClassB alloc]init];
[b addObserver:self forKeyPath:@"name" options:0 context:NULL];

and add the method:

(void)observeValueForKeyPath:(NSString )keyPath ofObject:(id)object
                      change:(NSDictionary )change
                     context:(void )context

No action is fired when trying to view the updates made in B from A. I feel silly asking this question but how does KVO work between two classes in iOS? I know this should work.

yuji
  • 16,695
  • 4
  • 63
  • 64
fmcauley
  • 165
  • 1
  • 1
  • 7

1 Answers1

38

You can observe changes across different objects/classes. I think the problem is in the options parameter of addObserver:forKeyPath:options:context:.

There are various options for the type of observing you want to do. The KVO Guide is a good starting point, but you probably want NSKeyValueObservingOptionNew, which I use in the example below.

First, "name" should be a public property in ClassB.

Second, you probably don't need to add the observer to "b" in viewWillAppear in ClassA, because you don't need to add it everytime the ClassA view is going to appear. You just need to add the observer once, when you create the ClassB view. Once you've added the observer, the observeValueForKeyPath:ofObject:change:context: method will be executed in ClassA, so you can do the update to the ClassA UI from there. You shouldn't need to do anything every time ClassA is about to appear.

In Class A, you should probably create ClassB just before you are going to push ClassB onto the controller stack, presumably in the event handler for some action the user took. Immediately after you create ClassB, add the observer in ClassA with the correct NSKeyValueObservingOption value.

If you just want to be notified whenever the public property "name" in ClassB is changed, then try this:

ClassB

@interface ClassB : UIViewController {
}

@property (retain) NSString* name;

- (void) aMethodThatModifiesName:(NSString*)newName;

@end


@implementation ClassB 

@synthesize name;

- (void) aMethodThatModifiesName:(NSString*)newName {
    // do some stuff
    self.name = newName;
}

@end

ClassA

@interface ClassA : UIViewController {
}

@property (nonatomic, retain) IBOutlet UILabel* nameLabel;

- (IBAction) someEventHandler:(id)sender;

@end

@implementation ClassA

- (IBAction) someEventHandler:(id)sender {
    ClassB* b = [[ClassB alloc]init];
    [b addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    [self.navigationController pushViewController:b animated:YES];
    [b release];
}

- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
    if ([keyPath isEqual:@"name"]) {
        NSString* changedName = [change objectForKey:NSKeyValueChangeNewKey];
        // do something with the changedName - call a method or update the UI here
        self.nameLabel.text = changedName;
    }
}

@end
nekno
  • 19,177
  • 5
  • 42
  • 47
  • This is probably the issue. You need to use the options parameter to indicate what notifications you want. By passing in 0, you aren't asking to be notified of anything. – benzado Nov 10 '10 at 22:38
  • 6
    If this helps anybody, I had a problem where my observeValueForKeyPath message wasn't firing, even though I wrote it out almost exactly the same as nekno. You *HAVE* to change the property using self.name = newvalue, *NOT* just name = newvalue. – Mirkules Mar 12 '11 at 00:35
  • Thanks for pointing this out Mirkules. Seems like observed value has to be changed by invoking the setter method. But what is about observing changes in readonly properties? – Christoph Jun 12 '12 at 15:25
  • @ChristophHalang Declare the property as readonly in the interface that is in the header file, but then re-declare it as readwrite in an interface extension in the main file. Then you can use the setters from within your main file, but not outside of it. Unfortunately this isn't really bulletproof if you want to keep people out of your class, because your readonly ivars *will* have setters that can be checked for and called at runtime. – Matt Mc Jul 21 '13 at 01:26