Search code examples
objective-ccocoakey-value-observing

KVO Method observeValueForKeyPath notifys me about the change, but I can't use it in viewDidLoad


I am working on this couple of days and I can't figure out what I am doing wrong.

First, I don't know if the KVO is the best way to pass the value of the clicked row info from one view controller to another view controller.

I have Main view, with 2 buttons only. Clicking on one of the buttons, I open MasterTableViewController, where I have the NSTableView with some records (one column only) from core data.

On click on the NSTableView, I am opening the DetailViewController, where I have only one label.

What I am trying is to change the label value, with the value of the clicked row in the NSTableView.

While I can print the value of the clicked row in the DetailViewController, I can't change the label value. I assume that notification comes before the viewDidLoad get called.

I have the following code:

In my MasterTableViewController.h file:

#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"

@interface MasterTableViewController : NSViewController

@property (nonatomic,strong) NSManagedObjectContext *mObjContext;
@property (weak) IBOutlet NSTableView *websitesTableView;
- (IBAction)tableViewDoubleClick:(id)sender;

@property (nonatomic,strong) NSString *cellValue;
@end

In my MasterTableViewController.m file:

#import "MasterTableViewController.h"
#import "DetailViewController.h"
@interface MasterTableViewController ()

@end

@implementation MasterTableViewController

-(void)awakeFromNib {
    [_websitesTableView setTarget:self];
    [_websitesTableView setDoubleAction:@selector(tableViewDoubleClick:)];
}



- (void)viewDidLoad {
    [super viewDidLoad];
    // Get the object managed context
    AppDelegate *appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate];
    self.mObjContext = appDelegate.managedObjectContext;

}


- (IBAction)tableViewDoubleClick:(id)sender {   
    NSInteger rowNumber = [_websitesTableView clickedRow];
    NSTableColumn *column = [_websitesTableView tableColumnWithIdentifier:@"websiteUrl"];
    NSCell *cell = [column dataCellForRow:rowNumber];

    _cellValue = [cell stringValue];

    MasterTableViewController *mtvc = [[MasterTableViewController alloc]initWithNibName:@"MasterTableViewController" bundle:nil];
    DetailViewController *dvc = [[DetailViewController alloc]initWithNibName:@"DetailViewController" bundle:nil];


    [mtvc addObserver:dvc
           forKeyPath:@"cellValue"
              options:NSKeyValueObservingOptionNew
              context:NULL];

    [mtvc setCellValue:_cellValue];
    [mtvc removeObserver:dvc forKeyPath:@"cellValue"];


    AppDelegate *appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate];
    [appDelegate changeViewController:2];
}

@end

Code for the DetailViewController.h is:

#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
@interface DetailViewController : NSViewController
@property (nonatomic, weak) AppDelegate *appDelegate;
@property (weak) IBOutlet NSTextField *detailLabel;
@property (nonatomic,strong) NSString *transferedLabelValue;
@property (nonatomic,strong) NSString *cellValue;

@end

Code for the DetailViewController.m is:

#import "DetailViewController.h"
#import "MasterTableViewController.h"
@interface DetailViewController ()

@end

@implementation DetailViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // if I uncomment this line, program crashes, because the value of the _transferedLabelValue is null here.
    //[_detailLabel setStringValue:_transferedLabelValue];

}

-(void)observeValueForKeyPath:(NSString *)keyPath
                 ofObject:(id)object
                   change:(NSDictionary *)change
                  context:(void *)context {
if ([keyPath isEqualToString:@"cellValue"]) {

    _transferedLabelValue = [change objectForKey:NSKeyValueChangeNewKey];
    // Here I get the value of the clicked cell, it is printed in the console. However if I try to change the label value here, doesn't work, since seems that label does not exist yet at this point....
    NSLog(@"Value in the observerValueForKeyPath is:%@",_transferedLabelValue);
}

}

@end

Code from the AppDelegate.h :

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>

@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

@property (weak) IBOutlet NSView *mainAppView;
@property (nonatomic,strong) NSViewController *mainAppViewController;

- (IBAction)showMasterViewButtonHasBeenClicked:(id)sender;
- (IBAction)showDetailViewButtonHasBeenClicked:(id)sender;
- (void)changeViewController:(NSInteger)tag;

@end

Here is only the relevant code from the AppDelegatge.m, the boiler plate code is omited.

#import "AppDelegate.h"
#import "MasterTableViewController.h"
#import "DetailViewController.h"

@interface AppDelegate ()

@property (weak) IBOutlet NSWindow *window;
- (IBAction)saveAction:(id)sender;

@end

@implementation AppDelegate

- (IBAction)showMasterViewButtonHasBeenClicked:(id)sender {
    NSInteger tag = [sender tag];
    [self changeViewController:tag];
}

- (IBAction)showDetailViewButtonHasBeenClicked:(id)sender {
    NSInteger tag = [sender tag];
    [self changeViewController:tag];
}

- (void)changeViewController:(NSInteger)tag {
    [[_mainAppViewController view]removeFromSuperview];

    switch (tag) {
        case 1:

            self.mainAppViewController = [[MasterTableViewController alloc]initWithNibName:@"MasterTableViewController" bundle:nil];
            [_mainAppView addSubview:[_mainAppViewController view]];
            [[_mainAppViewController view] setFrame:[_mainAppView bounds]];
            [[_mainAppViewController view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
            break;

        case 2:

            self.mainAppViewController = [[DetailViewController alloc]initWithNibName:@"DetailViewController" bundle:nil];
            [_mainAppView addSubview:[_mainAppViewController view]];
            [[_mainAppViewController view] setFrame:[_mainAppView bounds]];
            [[_mainAppViewController view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];

            break;

    }

}

Again, if the KVO and notification is not the best way to go, please let me know how I should "inform" the other view which row has been clicked and which is his value.

Regards, John


Solution

  • I manage to solve my problem with the KVO...My problem was that I was creating new instances of the view controllers in the MasterTableViewController...What I did was I created properties for the ViewControllers in the appDelegatge.h like this:

    @property MasterTableViewController *mtvc;
    @property DetailViewController *dvc;
    

    and then in the appDelegate.m :

    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
       _mtvc = [[MasterTableViewController alloc]initWithNibName:@"MasterTableViewController" bundle:nil];
       _dvc = [[DetailViewController alloc]initWithNibName:@"DetailViewController" bundle:nil];
    }
    

    Rest of the changes in the code are just replacing the MasterTableViewController and DetailViewController with _mtvc and _dvc...

    However...I had been warned that this solution is error prone, and that warning shows true. It happens from time to time that I get wrong value, not the clicked one. Once in 4-5 clicks, I get wrong value. What makes me confuse is that I make the function to work on double click, but it works on single click as well...But those things are for some other question I guess.