1

I have two views, my first view class1.m and second view is class2.m. My second view is initialized as a popover when a button is pressed on a toolbar in the first view. I have an array in my second view, in which objects is added, if any of the rows is pressed. I'm trying to set up a KVO in my first view, so that i can access the allSelectedFocus array from the second view in my first view, but it's not working. I realize that i don't invoke removeObserver, but i don't know where to invoke it, without it removing the observer before it's used. If anybody know any better ways to do this, I'm open for suggestions, but if someone can get this to work, that would be really awesome too.

//class2.m

#import "class2.h"
#import "class1.h"

@implementation class2

@synthesize selectedFocus = _selectedFocus;
@synthesize focusArray = _focusArray;
@synthesize allSelectedFocus = _allSelectedFocus;

- (void)viewDidLoad
{
_focusArray = [[NSArray alloc]initWithObjects:@"Balance",@"Bevægelse",@"Elementskift",@"Vejrtrækning",@"Alle",nil];

[super viewDidLoad];

}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return _focusArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}


NSString *cellValue = [_focusArray objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;

return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
_selectedFocus = [[_focusArray objectAtIndex:indexPath.row] stringByAppendingString:@","];
if(![[self mutableAllSelectedFocus] containsObject:_selectedFocus])
   {
       //add object to array, if it's not already there
       [[self mutableAllSelectedFocus] addObject:_selectedFocus];
   }
else
    {
        //remove object from array, if it's already there
        [[self mutableAllSelectedFocus] removeObject:_selectedFocus];   
    }
 }

-(NSMutableArray *)allSelectedFocus
{
if(_allSelectedFocus == nil)
{
    _allSelectedFocus = [[NSMutableArray alloc]init];
}
return _allSelectedFocus;
}

-(NSMutableArray *)mutableAllSelectedFocus
{
return [self mutableArrayValueForKey:@"allSelectedFocus"];
}
@end

//class1.m

#import "class1.h"
#import "class2.h"

@implementation class1
- (void)viewDidLoad
{
[super viewDidLoad];

 if(_focusTag == nil)
{
    _focusTag = [[class2 alloc]init];
}

[_focusTag addObserver:self forKeyPath:@"selectedFocus" options:NSKeyValueObservingOptionNew context:NULL];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:@"allSelectedFocus"])
{
NSLog(@"%@", [object valueForKeyPath:keyPath]);
}
}
Niels Sønderbæk
  • 3,487
  • 4
  • 30
  • 43

2 Answers2

4

I suspect that this is either a function of the fact that NSArray objects are not observable or some broader violation of KVC compliance. Regardless, just implement manual change notification for NSArray objects and you should be fine. I just tested changes to NSArray objects (the adding of objects) and automatic notification did not take place, but when I added manual notification, it worked fine. (Though, curiously, NSKeyValueObservingOptionOld doesn't work as expected, showing the new value instead of the old value.) FYI, here is an example of the update method which adds something to my objects NSMutableArray, using manual notification:

- (void)addToMyArray:(id)obj
{
    [self willChangeValueForKey:@"myArray"];
    [_myArray addObject:obj];
    [self didChangeValueForKey:@"myArray"];
}

Update:

By the way, if you need NSKeyValueObservingOptionOld, you can do something like:

- (void)addToMyArray:(id)obj
{
    NSMutableArray *tempArray = [NSMutableArray arrayWithArray:_myArray];
    [tempArray addObject:obj];
    [self setMyArray:tempArray];
}

This way, you don't need manual notification, and you can retrieve both old and new values, but it also seems like an inefficient use of memory, so there are pros and cons.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
1

Just a heads up, at no point are you using an accessor method to set / get your properties. This means that KVO won't work. I believe KVO relies on the getting / setting of properties via accessors.

I'm not sure of what you're trying to accomplish with the application, but I put together some code that may be of help. I commented within the code so I won't be explaining it throughout this answer.

I'll start with class2 as you did:

#import <UIKit/UIKit.h>

@interface Class2ViewController : UITableViewController

@property (nonatomic, strong) NSArray *focusArray;
@property (nonatomic, strong) NSMutableArray *allSelectedFocus;

// This is a readonly property that will return a mutable array of the allSelectedFocus      property
// This gives you the ability to have automatic KVO if you add/remove using this property
// You won't have to wrap your calls to will/didChangeValueForKey:
@property (nonatomic, readonly, strong) NSMutableArray *mutableAllSelectedFocus;

@end


#import "Class2ViewController.h"
#import "Class1ViewController.h"


@implementation Class2ViewController

@synthesize focusArray = _focusArray;
@synthesize allSelectedFocus = _allSelectedFocus;


- (void)viewDidLoad
{
    [super viewDidLoad];

    // This is what you have 
    // FYI you are accessing the iVar directly, not sure if that matters in your app or not
    _focusArray = [[NSArray alloc] initWithObjects:@"Balance",@"Bevægelse",@"Elementskift",@"Vejrtrækning",@"Alle",nil];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Grab the string of interest from the _focusArray --> this is direct access again which I imagine is fine
    NSString *selectedFocus = [[_focusArray objectAtIndex:indexPath.row] stringByAppendingString:@","];

    // Use the new mutableAllSelectedFocus property to check if the array doesn't contain the string of interest
    if (![[self mutableAllSelectedFocus] containsObject:selectedFocus]) {

        // If it doesn't contain it, add it using the mutableAllSelectedFocus property
        [[self mutableAllSelectedFocus] addObject:selectedFocus];
    }

}

// This is getter that lazily instantiates your _allSelectedFocus array
- (NSMutableArray *)allSelectedFocus
{
    // Check to see if the backing iVar is nil
    if (_allSelectedFocus == nil) {

        // If it is, create an empty mutable array
        _allSelectedFocus = [[NSMutableArray alloc] init];
    }

    // return the array
    return _allSelectedFocus;
}

// This is our new property
- (NSMutableArray *)mutableAllSelectedFocus
{
    // mutableArrayValueForKey: returns a mutable array for the given key (property)
    // Allows us better KVO and efficiency with changing properties
    return [self mutableArrayValueForKey:@"allSelectedFocus"];
}

And now class 1:

#import "Class1ViewController.h"
#import "Class2ViewController.h"

@implementation Class1ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // If you are using ARC, this instance will be deallocated after -viewDidLoad
    // You will want to store this in an instance variable if you need to keep it around
    Class2ViewController *class2ViewController = [[Class2ViewController alloc] init];

    [class2ViewController addObserver:self
                           forKeyPath:@"allSelectedFocus"
                              options:NSKeyValueObservingOptionNew
                              context:NULL];
}

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

        NSLog(@"%@", [object valueForKeyPath:keyPath]);
    }
}

I'm not sure if this change in code will be helpful in your application. Two things I would do though would be read the Key-Value Coding and Key-Value Observing Guides if you haven't and read this post on to-many relationships and properties.

If I got something wrong, just leave a comment.

Good luck.

Brian Palma
  • 641
  • 1
  • 7
  • 13
  • I've updated my code above, but nothing is happening still. I want to NSLog() what changes, so i can see that it does change. What i eventually want to do is put this array into other array in class1. If i could just observe _selectedFocus and have that passed to my class1, once it changes, then i could put that value into an NSMutableArray in class1. So maybe there is a way to observe an NSString, which would work in my case? – Niels Sønderbæk Apr 22 '12 at 12:33