My first Cocoa master-detail application: Binding difficulties - objective-c

I'm writing my first master detail view in Cocoa. My data model is really simple: just an NSMutableArray that contains instances of a single class having a few NSStrings properties.
Here's what I've created so far:
A class representing the instances.
An NSMutableArray specified as a property of my app delegate to hold the class instances.
A master detail view, with an NSTable and some text fields to hold the properties of a selection instance.
An NSArrayController with a binding specifying the app delegate, and the name of the NSMutableArray property as the model key path.
Bindings between the columns of the NSTable and the NSArrayController (controller key = "arrangedObjects", and model key path of each column = a property of the class).
Bindings between the text fields of the view and the selection (controller key = "selection", and model key path of each text field = a property of the class).
A "+" button and a "-" button in the view to enable adding and removing objects.
However, I'm having two problems with this design:
(1) I can't find a good way to implement the "+" and "-" buttons. First, I bound them to the add: and remove: properties of the array controller. While this seems to work, it has a problem: my class declares an init member that initializes the NSStrings to stub values, but the array controller doesn't seem to [init] the new instances, because the new list entry has empty strings for each column.
Next, I attached them to IBActions in my app delegate that added or removed an object from the NSMutableArray. However, this feels wrong - it feels like I'm violating the model-view-controller architecture by not talking to the array controller. For example, the "-" function has to talk to the array controller to get the selected item. Also, I notice that I have to send a [didChangeValueForKey] message after altering the array - which feels like a signal that I'm doing this wrong.
(2) One of the detail subviews in my view is an NSTextView that's bound to an NSString in the selected object. It really isn't working as expected: when new text is entered, the text remains the same even if other members of the class (which should have different values) are selected. Also, the text data isn't being saved to any instance of the class.
Any thoughts? Thanks in advance.

Here is an example that should be close to what you want:
#import "AppDelegate.h"
#import "Members.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.theData = [NSMutableArray arrayWithObject:[[Members alloc] init]];
}
-(IBAction)addMember:(id)sender {
[self.controller addObject:[[Members alloc] init]];
}
-(IBAction)removeMember:(id)sender {
[self.controller removeObjectAtArrangedObjectIndex:self.controller.selectionIndex];
}
The array controller (whose IBOutlet is controller) has its content array bound to theData. Members is the name of my class that has 3 string properties, name, author and blurb. In the detail view, the 2 text field's are bound to ArrayController.selection.name or author, just like you said you did. The other property, blurb, is bound the same way (ArrayController.selection.blurb), but to the Attributed String value of a text view. This worked fine, I'm not sure what your trouble with the text view was. If I add text to the text view, it does show up there if I click another row in the mater table and then click back (also if I log the array, it shows up there too).

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.

CS193P UITabBarController MVC Help on Assignment 4

I'm going through the Stanford CS193P course on iTunesU and am a little puzzled on how to do the recently viewed photos portion on assignment 4.
In the assignment we are to have a tab bar controller with two tabs.
1st tab is a navigation controller that will show a table of places, which will push a table of photo names, which will push a scroll view with a photo
2nd tab is a navigation controller that will show a table of recently viewed photos, which will push a scroll view with a photo.
I have the first tab working, and now when I push the scroll view with the image, I also want to add that photo to an array of recent photos, which MVC should own this recent photos array?
The Tab View Controller (if so the docs say that this class is not intended for sub classing)
The root Table View Controller of the 2nd Tab (how do I pass the current photo to the instance is in another tab) (and quite frankly should the first tab know about the second tab)
The root Table View Controller of the 1st Tab (then how does the second tab pull this data from the first tab?)
Something else
I guess I'm still hazy about MVC's, protocols, delegates and data sources. If you have your solution to this task that I could look through I would greatly appreciate it.
I ended up pushing and pulling the data from user defaults.
Although I'm curious why the tab bar controller is not intended for sub classing. That seems like the most logical place for data to be owned when it is needed by multiple tabs.
I've done something similar and if I don't missundestood your question completely, you could create a Singelton whichcould act like some kind of shared database. It will never be initialized in a normal fashion, just created when you use it the first time. This singelton could contain your array and you could then call it from everywhere by writing just:
[SingeltonType main].sharedPhotos
The following example is from my own code where I have a "User" which is the owner of the app. There I store a database with info that will be available from anywhere during runtime.
header:
#interface User : NSObject {
Database *_storage;
}
#property (nonatomic, retain) Database *storage;
+(User*)owner;
main file:
#import "User.h"
#implementation User
#synthesize password = storage = _storage;
static User* _owner = nil;
+(User*)owner {
#synchronized([User class]) {
if(!_owner) [[self alloc] init];
return _owner;
}
return nil;
}
+(id)alloc {
#synchronized([User class]) {
NSAssert(_owner == nil, #"Attempted to allocate a second instance of a singleton.");
_owner = [super alloc];
return _owner;
}
return nil;
}
-(id)init {
self = [super init];
if(self != nil) {
self.storage = [[[Database alloc] init] autorelease];
}
return self;
}
Then I just call it like this:
[User owner].storage // which gives me access to it
Hope that helps! Really useful if you need to access data from different places :)
Note: You will only have ONE instance of this object and cannot create more.
After a bunch of additional searching, I didn't find any one consistent way to pass data from tab to tab.
Since we are only storing a relatively small amount of data, I decided to make a class, with class methods (for convenience) to push and pull the data into user defaults.
I have messed around with that question a bit by using protocol. I created the protocol in the class displaying the image (and UIScrollView). I then adopted the protocol in the "viewed photos" tableController class and implemented that protocol method that passes the viewed image. The problem I have is how do you define the "viewed Photos" tableController class as the delegate, given that 1) it has not been loaded yet and might not be loaded until after viewing pictures 2) how do you work your way though the nav controllers and tab controller to point to the class declaring the protocol. Would love to hear from experts here on whether protocol or class method is the right way here from a programming methodology?
Thanks
KB

Binding label's value to the count property of an NSArray

I want to show in a label the current number of elements in an NSArray called pages. Following other guides I did the following:
Created an NSArrayController in IB (called pagesController) and bounded it to the NSArray pages
Bounded the value property of the label to the NSArrayController with Controller Key = arrangedObjects and Model Key Path = #count
The problem is that when the program is running the label always shows "0". To check if the things are working correctly I tried to log a message when the user clicks on another button (the button basically inserts a new element in the pages array): NSLog(#"count = %d", [self.pagesController valueForKeyPath:#"arrangedObjects.#count"]); in this case the output is correct, i.e. the current number of elements in the array printed is correct.
Where am I mistaking?
You many not be using KVC correctly. Your array controller will only be notified with the pages property is redefined, not when objects are added to the array. Try wrapping your code with the appropriate change notifications:
[self willChangeValueForKey:#"pages"];
[pages addObject:someObject];
[self didChangeValueFOrKey:#"pages"];

How to edit data of NSArrayController (or to use something else)

i created a nsarraycontroller to display its data on my nstableview. this is working. but how do i modify this data now?
[arrayController addObject:[...]]
adds an object, how do i get it back?
i tried:
NSMutableArray *data = [arrayController mutableArrayValueForKey:#"column1"];]
but then i get this error
2011-05-29 19:25:50.125 TestApp[1665:903] [<NSArrayController 0x113808500> valueForUndefinedKey:]: this class is not key value coding-compliant for the key column1.
(the objects in my arraycontroller representing the rows in my view are of a class (named FileEntry) consisting of 4 properties and i added these as keys to my nsarraycontroller)
i thought kvc-compilant means every attribute has a getter and setter? (and isnt that what properties do?).
i tested my class if i could use this function:
FileEntry *entry = [[FileEntry alloc] initWithUrl:#"test"]; //(this adds the string "test" to the property "fileurl")
NSLog(#"%#\n", [entry valueForKey:#"fileurl"]);
and it returns:
> 2011-05-29 19:31:54.760 TestApp[1718:903] test
and it works. so how can my class not be kvc-compilant?
anyway, i also tried to use the tableviews datasource instead but cant get those 2 functions to work. would that be of more use than the nsarraycontroller? is it even possible to modify data of the nsarraycontroller?
Usually you need a contend array for the NSArrayController to handle, so you could simply take this array and use the NSMutableArray methods. For example you have in your MyController.h a declaration of an array with its setter and getter methods, and you only have to bind the contend array of the array controller to it. Then you can take two buttons for the add and remove methods of the array controller to add something. Now you must bind the value of the column to e.g. FileEntry.fileurl of your array controller managed objects, Now you should be able to add stuff to the table view. And you can also Edit the names in the table view. If you want to get some stuff out of the array in your program somewhere, you can use an outlet of your table view like this:
NSInteger row = [myTableViewOutlet selectedRow];
FileEntry* myEntry = [myArray objectAtIndex:row];

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!