views:

620

answers:

2

Hi Fellow iPhone Developers,

I am an experienced software engineer but new to the iPhone platform. I have successfully implemented sub-classed view controllers and can push and pop parent/child views on the view controller stack. However, I have struck trouble while trying to update a view controller when an object is edited in a child view controller. After much failed experimentation, I discovered the key-value observer API which looked like the perfect way to do this. I then registered an observer in my main/parent view controller, and in the observer I intend to reload the view. The idea is that when the object is edited in the child view controller, this will be fired. However, I think that the observer is not being registered, because I know that the value is being updated in the editing view controller (I can see it in the debugger), but the observing method is never being called.

Please help!

Code snippets follow below.

Object being observed. I believe that this is key-value compliant as the value is set when called with the setvalue message (see Child View Controller below).

X.h:

@interface X : NSObject <NSCoding> {
    NSString    *name;
...
@property (nonatomic, retain) NSString *name;

X.m:

@implementation X

@synthesize name;
...

Main View Controller.h:

@class X;

@interface XViewController : UITableViewController {
 X *x;
...

Main View Controller.m:

@implementation XViewController

@synthesize x;
...
- (void)viewDidLoad {
    ...
    [self.x addObserver:self
            forKeyPath: @"name"
            options:        (NSKeyValueObservingOptionNew |
                             NSKeyValueObservingOptionOld)
            context:nil];

    [super viewDidLoad];
}
...
- (void)observeValueForKeyPath:(NSString *)keyPath
                    ofObject:(id)object
                    change:(NSDictionary *)change
                    context:(void *)context
{
    if ([keyPath isEqual:@"name"]) {
        NSLog(@"Found change to X");
    [self.tableView reloadData];
    }

    [super observeValueForKeyPath:keyPath
           ofObject:object
           change:change
           context:context];
}

Child View Controller.m: (this correctly sets the value in the object in the child view controller)

[self.x setValue:[[tempValues objectForKey:key] text] forKey:@"name"];
A: 

Did you check if you instantiated your x object in viewDidLoad() before calling addObserver:forKeyPath:options:context: ? Your x object must be already allocated/initialized.

A minor consideration. Since the context parameter is declared as (void *), you should pass NULL, not nil (a null object pointer which stands for id 0, while NULL stands for (void *) 0; they represent both the same thing, 0, but in Objective C you must distinguish among nil, NULL and Nil which represents a null class pointer).

unforgiven
Thank you for pointing out the flaw. I have discovered that I can get the event to fire if I instead put the observer on the child view controller instance variable property (!). That is, in the example above, when the edit button is pushed, I do this:Main View Controller.m ... ChildViewController.x = self.x; [ChildViewController.x addObserver:self forKeyPath: @"name" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)context:NULL]; [[self navigationController] pushViewController: ChildViewController animated:YES]; ...
Scott
A: 

While key value observing may work well in your application, using the notification center may be better for communicating between "major" view controllers. (A "major" view controller being the main controller for, say, an iPhone's full screen view.)

This ensures that both view controllers still exist when the communication happens. KVO is, it appears to me, designed to be used within a common context -- a single view controller or model.

Class X does appear to be KVC compliant. Though you do not show whether you declare an @property for it in your code. I'll assume that you do.

Why are you doing this:

[self.X setValue:[[tempValues objectForKey:key] text] forKey:@"name"];

in your child view controller rather than:

self.name = [[tempValues objectForKey:key] text];

(And did you really mean self.X above? self.X is not self.x.)

One other note: you do not need to call the superclass of your -[observeValueForKeyPath: ofObject:change:context:] method.

Andrew

adonoho
Thank you for pointing out that I do not need to call the superclass. Once I did get the event to fire, that was causing an exception. However, the code self.x.name = [[temp.. and [self.x setValue:... is actually functionally equivalent (I tested both). I am not sure if one is more efficient than the other, but thank you for showing me the alternative. My reading of the KVO documentation made me think that I needed the [self.x setValue... syntax.
Scott