Observe changes of instance variable in Objective-c - objective-c

I have a UITableView with a property(strong, nonatomic)NSMutableArray *currentContent, I also have a property(strong, nonatomic)NSMutableArray *cellHeights to keep track of the cell heights cos the user could expand or collapse each cell. The self.currentContent is set by another controller which load data from a web service, so it will change as the data load, I want to keep both of these variables in sync. As soon as currentContent is updated, I want to update cellHeights. How do I do that?
I tried:
- (void)setCurrentContent:(NSMutableArray *)currentContent{
_currentContent = currentContent;
self.cellHeights = [NSMutableArray arrayWithDefaultHeightsForCellCount:[currentContent count]];
}
But it's not working, cos it will only be set at the first time when I set currentContent, when it's empty. So self.cellHeights currently will stay empty. When there is finally value in self.currentContent, self.cellHeights was not updated.

I've done a similar thing before, with variable cell heights depending on the content from your web-service, and I'd advise that keeping an array of cell heights might not be the best idea.
What I did was to create a 'fake' cell in the viewDidLoad: method, that I use just to calculate cell heights.
Then I use the 'heightForRowAtIndexPath' method to specify how tall cell should be by populating the 'fake' cell with the data for the index path, then finding out how tall that cell is. For example:
#interface MyTableViewController()
#property (nonatomic, strong ) MyCustomTableViewCell *cellForTestingHeight;
#end
#implementation MyTableViewController
- (void)viewDidLoad {
[super viewDidLoad]
self.cellForTestingHeight = [[MyCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *myData = [self.data objectAtIndex:indexPath.row];
self.cellForTestingHeight.viewData = myData;
return self.cellForTestingHeight.height;
}
#end
This code assumes that you've created a class called MyCustomTableViewCell which has a method to set the viewData property on it, and that after setting that property you'll be able to tell how tall that cell will be by accessing a height property.

What you need to do is observe changes to the mutable array itself, not your property which references the array. This should be straightforward but unfortunately there is a twist...
OK, let's assume there is no twist and rather than an NSMutableArray your property is of type MyObservableClass. Then to setup the observing you would do something like this (all code is pseudo - i.e. typed into the answer):
- (void)setCurrentContent:(MyObservableClass *)currentContent
{
if(_currentContent)
[_currentContent removeObserver:self forKeyPath:#"myObservableProperty"];
_currentContent = currentContent;
[_currentContent addObserver:self forKeyPath:#"myObservableProperty" ...];
}
Now whenever the currentContent is changed your code stops observing the properties of its old value and starts observing properties of its new value.
Easy, but its not quite that simple for arrays...
The twist, though KVO will inform an observer of additions, deletions and changes to a collection the NSMutableArray class doesn't issue those notifications. You need to use a proxy for the array, and such a proxy is provided by the frameworks - see for example NSArrayController and mutableArrayValueForKey:. However the second part of the twist is that the changes to the array must be done through this proxy - you can't have your client call setCurrentContent and pass any NSMutableArray if you are going to observe it, your client needs to pass in the proxy... Whether you can use this easily in your design you'll have to figure out. You'll need to read Automatic Change Notification in this document and other Apple docs to sort this out.
This sounds more complicated that it needs to be, maybe somebody else will provide a more succinct way of doing this.

Related

How to access content of NSMutable array from another class?

I have a FirstViewControlloller in which I have a start and stop button. When start is pressed pictures are taken by using AVCaptureSession until stop button is pressed. These pictures are processed and information (The R-value component of RGB) from each picture is stored in an NSMutableArray called Yaxis.
Added to this View I have a ContainerView. In this view I want to continously display a graph of the information in my Yaxis-array. The problem is to reach this information.
Some parts of my code follow here:
FirstViewController.h
#property (nonatomic, strong) NSMutableArray *Yaxis;
FirstViewController.m
-(void)viewDidLoad{
Yaxis=[[NSMutableArray alloc] init];
[super viewDidLoad];
}
At another place in the code objects are continuously added to "Yaxis" as pictures are taken.
[Yaxis addObject: RGB];
This works fine since I have no problem accessing the array filled with information as long as Im in FirstViewController.
To be able to use this information to update my graph I have tried to create an object of FirstViewController in my ContainerViewController and in that way reach my Yaxis array.
ContainerViewController.m
FirstViewController * myView=[[FirstViewController alloc] init];
myView.Yaxis
When I do this Yaxis is null. As I understand it the problem is that I create a new object of FirstViewController and therefore I will not be able to reach the filled array that is actually a part of another object. So how do I get connected to the object that holds my filled Yaxis array from within my ContainerViweController class?
Access NSMutableArray from another class - Objective C
I found this question that seams to adress the same problem but I did not solve my problem using the answers from there.
You initialize the array in -(void)viewDidLoad. But that method is not called anywhere in your second code snippet. Try putting the Yaxis=[[NSMutableArray alloc] init]; line in your init method
When you manually create a new FirstViewController, this is a new object unrelated to the original, and the mutable array property is initially nil.
The new VC will not magically point to the one that's displayed. Also, the VC might get destroyed, and the array released.
You should separate the storage of this data in another object if it's shared between VCs.

NSArrayController ignoring filterPredicate on init

Noob warning!
My app displays data in a standard NSTableView via NSArrayController all via bindings. All cool.
There is a filterPredicate, “completed tasks” which is toggled by a check box which drives a BOOL in the background. This works great when the app is running, filter on, filter off, but if I set the BOOL to NO in either init or awakeFromNib, the NSArrayContoller ignores the filterPredicate, but works later.
I could use the predicate to deliver a filtered array to the AC, but guessed bindings & direct filterPredicate would be better.
The AC is init’d in the NIB file and from looking at the KVO feedback, gets created very early on. The filterPredicate does not exist when the AppController inits, but does by the time we get to awakeFromNIb.
Any ideas what I am missing?
Edit:
This is the method called by the check box action:
- (void)displayClosedJobs {
if (self.showClosedJobs)
{
self.showClosedPredicate=nil;
}
else
{
NSString *predValue=#"Closed";
self.showClosedPredicate=[NSPredicate predicateWithFormat:#"!(DisplayName contains[c] %#)",predValue];
}
[self changeTableSortOrder];}
This is also called on init and awakeFromNib.
Most likely you set the bool variable in your init/awakeFromNib directly like so:
_myBoolVariable = NO;
However, what you have to do to send the correct KVO notifications for bindings to notice the change, is use the setter method explicitly either by
[self setMyBoolVariable:NO];
or by
self.myBoolVariable = NO;
However, it's hard to tell without seeing your initialization code. If this is not the problem you should provide more details about how you initialize the variables and how your bindings are set up.

Passing values from UITableView to UIView in table header

My CoreData model drives series of UITableViewControllers.
One of the detail tables has a header that contains a UIView "GraphView" that uses data from the rows of the table. (It's like iTunes having a custom header to chart song length in a given playlist.) My "playlist" populates from CoreData correctly. But how do I pass data from the TableView to the UIView in its header?
It seems wasteful and redundant to fetch and parse it again, especially when the data is sitting in a convenient mutable array called "steps". Can I just pass the mutable array to the UIView in the header? If so, how?
I've tried creating an NSArray property in the UIView's class called "passedStepsArray", and then setting it from the StepDetailViewController like so:
graphView.passedStepsArray = [[NSArray alloc] initWithArray:steps copyItems:YES];
But back in the GraphView class, it remains NULL. No value gets passed.
Any ideas?
From my visualization of the problem, it seems you are on the right tracks by having an NSArray property in the GraphView. You probably don't need to copy the existing array using the initWithArray:copyItems: method, just pass a non-mutable copy of the populated steps array:
graphView.passedStepsArray = [[steps copy] autorelease];
This is assuming that the graphView passedStepsArray property is set to retain. (Then don't forget to set self.passedStepsArray = nil; in the graphView dealloc methood.)

How to click a checkbox in nstableview and update an object using KVC?

I am trying to learn cocoa and have a few problems with KVC and bindings. I have a nstableview with three columns; "checkbox", "text", "icon". The values of each column is binded to an arraycontroller using KVC. When program is launched the rows and columns are correctly filled into the tableview according to the values in the array. I can click a row and correctly print the content of that row using something like this:
- (IBAction)fileTableViewSelected:(id)sender{
NSInteger r;
NSDate *fModOne;
id object;
r = [[NSNumber numberWithInt:[sender selectedRow]] intValue];
object = [arrayIntersect objectAtIndex:r];
fModOne = [object valueForKey:#"fileModifiedDirOne"];
NSLog(#"Date found in row is %#",fModOne);
}
My problem is when I try to click the checkbox in column one and change the value of the box. Initially, the value of the checkbox is set to 1 using the arraycontroller which works fine, but when I want to change the value of the checkbox of a specific row to 0 by clicking on it the program crashes. When the box is clicked an action is correctly called and this is where I thought I could simply change the value of my objects BOOL by calling:
[object setValue:[NSNumber numberWithBool:NO] forKey:#"doSync"];
My setters and getters for the BOOL doSync is defined as:
#property(nonatomic, readwrite) BOOL doSync;
#dynamic doSync;
- (void)setDoSync:(BOOL) value{
NSLog(#"setting dosync %i", value);
doSync = NO;
}
- (BOOL)doSync{
return doSync;
}
I have searched everywhere for a solution to my problem, but I am unable to find any examples of how to use checkboxes in tableview using KVC and bindings. I appreciate any help I can get on this and I would appreciate any examples I could take a look at.
Cheers and thanks! Trond
You don't need to implement this yourself as an action. Just bind the column through your array controller's arrangedObjects to the doSync property of the model objects.
If you don't want to use Bindings, you still shouldn't implement it as an action. Instead, be the table view's data source and respond to the message the table view will send you to change one of the values.
#dynamic doSync;
There's no reason to have this if you turn around and implement the accessors for that property in the same class.
If this is a managed-object class and the property is an attribute of the entity, then your accessors should send [self willAccessValueforKey:] before and [self didAccessValueForKey:] after accessing the instance variable. If that's all they do, then you should not implement the custom accessors at all; cut them out and have #dynamic alone.
- (void)setDoSync:(BOOL) value{
doSync = NO;
That's not setting the property to the value passed in.

Exposing model object using bindings in custom NSCell of NSTableView

I am struggling trying to perform what I would think would be a relatively common task. I have an NSTableView that is bound to it's array via an NSArrayController. The array controller has it's content set to an NSMutableArray that contains one or more NSObject instances of a model class. What I don't know how to do is expose the model inside the NSCell subclass in a way that is bindings friendly.
For the purpose of illustration, we'll say that the object model is a person consisting of a first name, last name, age and gender. Thus the model would appear something like this:
#interface PersonModel : NSObject {
NSString * firstName;
NSString * lastName;
NSString * gender;
int * age;
}
Obviously the appropriate setters, getters init etc for the class.
In my controller class I define an NSTableView, NSMutableArray and an NSArrayController:
#interface ControllerClass : NSObject {
IBOutlet NSTableView * myTableView;
NSMutableArray * myPersonArray;
IBOutlet NSArrayController * myPersonArrayController;
}
Using Interface Builder I can easily bind the model to the appropriate columns:
myPersonArray --> myPersonArrayController --> table column binding
This works fine. So I remove the extra columns, leaving one column hidden that is bound to the NSArrayController (this creates and keeps the association between each row and the NSArrayController) so that I am down to one visible column in my NSTableView and one hidden column. I create an NSCell subclass and put the appropriate drawing method to create the cell. In my awakeFromNib I establish the custom NSCell subclass:
MyCustomCell * aCustomCell = [[[MyCustomCell alloc] init] autorelease];
[[myTableView tableColumnWithIdentifier:#"customCellColumn"]
setDataCell:aCustomCell];
This, too, works fine from a drawing perspective. I get my custom cell showing up in the column and it repeats for every managed object in my array controller. If I add an object or remove an object from the array controller the table updates accordingly.
However... I was under the impression that my PersonModel object would be available from within my NSCell subclass. But I don't know how to get to it. I don't want to set each NSCell using setters and getters because then I'm breaking the whole model concept by storing data in the NSCell instead of referencing it from the array controller.
And yes I do need to have a custom NSCell, so having multiple columns is not an option. Where to from here?
In addition to the Google and StackOverflow search, I've done the obligatory walk through on Apple's docs and don't seem to have found the answer. I have found a lot of references that beat around the bush but nothing involving an NSArrayController. The controller makes life very easy when binding to other elements of the model entity (such as a master/detail scenario). I have also found a lot of references (although no answers) when using Core Data, but Im not using Core Data.
As per the norm, I'm very grateful for any assistance that can be offered!
Finally figured this one out. Man that took some doing. So here is what I needed to do...
First of all I needed to create an array of my model's key values in my model object and return those key values in an NSDictionary from within the model.
Thus my model got two new methods as follows (based on my simplified example above):
+(NSArray *)personKeys
{
static NSArray * personKeys = nil;
if (personKeys == nil)
personKeys = [[NSArray alloc] initWithObjects:#"firstName", #"lastName", #"gender", #"age", nil];
return personKeys;
}
-(NSDictionary *)personDictionary
{
return [self dictionaryWithValuesForKeys:[[self class] personKeys]];
}
Once implemented, I assign my bound value in my table to
arrayController --> arrangeObjects --> personDictionary.
The last step is to reference the object in the NSCell drawWithFrame and use as needed as follows:
NSDictionary * thisCellObject = [self objectValue];
NSString * objectFirstName = [thisCellObject valueForkey:#"firstName"];
NSString * objectLastName = [thisCellObject valueForKey:#"lastName"];
And as I hoped, any update to the model object reflects in the custom NSCell.
There is another approach that does not require the dictionary. However, it does require the implementation of the - (id)copyWithZone:(NSZone *)zone method within your data class. For example:
- (id)copyWithZone:(NSZone *)zone {
Activity *copy = [[self class] allocWithZone: zone];
copy.activityDate = self.activityDate;
copy.sport = self.sport;
copy.sportIcon = self.sportIcon;
copy.laps = self.laps;
return copy; }
Now in IB, you point the Table Column's value at Array Controller --> arrangedObjects
The drawWithFrame method will now return your actual object from the objectValue.
Activity *rowActivity = [self objectValue];
Changing the class requires updating the copyWithZone method and then accessing the data directly in your drawWithFrame method.
I'm very grateful for this post, Hooligancat, because my project was stalled on this problem, and as hard as I looked at Tim Isted's identical solution at http://www.timisted.net/blog/archive/custom-cells-and-core-data/
I couldn't figure it out.
It was only when I read your excellent simplification of the problem and your version of the solution that the penny dropped - I'm very grateful!