What is an easy way to set up my NSTableView with multiple columns to only display certain data in one column. I have the IBOutlets set up, but I don't know where to go from there.
Assuming you're not using Cocoa Bindings/Core Data, you can display data in an NSTableView by implementing two methods from the NSTableViewDataSource protocol. Typically your controller will implement the protocol, so open the controller .m file and add these methods to the controller's #implementation:
- (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
return 25; // fill this out
}
– (id) tableView:(NSTableView*)tableView
objectValueForTableColumn:(NSTableColumn*)column
row:(int)row {
return row % 3 ? #"Tick..." : #"BOOM!"; // fill this out
}
You need to set the table's dataSource property to the controller. In Interface Builder control-drag from the table view to the controller and set dataSource. Now build and run and you should see your data in the table.
If you only want to fill out one column, add an IBOutlet NSTableColumn* to your controller; let's call it explosiveColumn. In Interface Builder, control-drag from the controller to the column you want to fill in and set explosiveColumn. Then, in tableView:objectValueForTableColumn:row: you can test if the column parameter is the same object as the one that the outlet is set to:
– (id) tableView:(NSTableView*)tableView
objectValueForTableColumn:(NSTableColumn*)column
row:(int)row {
if (column == explosiveColumn) {
return row % 3 ? #"Tick..." : #"BOOM!";
} else {
// other columns blank for now
return nil;
}
}
This tutorial might be useful: http://www.cocoadev.com/index.pl?NSTableViewTutorial
Here's an example using multiple table views with data source methods and a document based application:
#pragma mark - Data Source Methods
- (NSInteger) numberOfRowsInTableView:(NSTableView *)tv
{
if (tv == racerTableView)
return [racerList count];
else if (tv == vehicleTableView)
return [vehicleList count];
else
return 0; // something wrong here...
}
- (id)tableView:(NSTableView *)tv objectValueForTableColumn:(NSTableColumn *)col
row:(NSInteger)rowi
{
NSString *colid = [col identifier];
if (tv == racerTableView){
NHRacers *racer = [racerList objectAtIndex:rowi];
return [racer valueForKey:colid];
}
else if (tv == vehicleTableView){
NHVehicles *vehicle = [vehicleList objectAtIndex:rowi];
return [vehicle valueForKey:colid];
}
else
return 0; // something wrong here...
}
- (void)tableView:(NSTableView *)tv setObjectValue:(id)obj forTableColumn:(NSTableColumn *)col row:(NSInteger)rowi
{
NSString *colid = [col identifier];
if (tv == racerTableView) {
NHRacers *racer = [racerList objectAtIndex:rowi];
[racer setValue:obj forKey:colid];
}
else if (tv == vehicleTableView){
NHVehicles *vehicle = [vehicleList objectAtIndex:rowi];
[vehicle setValue:obj forKey:colid];
}
else
nil; // something wrong here...
[self updateChangeCount:NSChangeDone];
}
The tableview datasource outlets are set to the File's Owner and the File's Owner has set vehicleTableView and racerTableView to their respective "Table View" in the IB. The colid key checks the identifier (set in IB by selecting the table view column under the "Identity" drop down, while the "Identity Inspector" is shown). These values were chosen to be the KVC (key coding compliant) properties of the classes being displayed in the table views: use lower case first letter (see apple docs for rest).
For example:
(in NHVehicles.h)
#interface NHVehicles : NSObject
{
NSUInteger entry;
NSString *name;
NSString *vehicleClass;
}
#property NSUInteger entry;
#property NSString *name, *vehicleClass;
#end
(in NHVehicles.m)
#implementation NHVehicles
#synthesize entry, name, vehicleClass;
#end
for this tableView, "entry", "name" and "vehicleClass" would be typed (w/o ") into the identifier fields for their respective columns.
If you don't want to show some data in the class, simply do not enter the key for the column identifier. A word of caution: I am using Xcode 4.5.1 and I noticed that once I had entered a few keys for a particular column identifiers and then changed my mind about and attempted to clear the text, it complained when I deleted the text from the identifier field (I could no longer leave the field blank for the columns that I had edited). This was not difficult to work around, but it was a surprise.
Related
I have a NSTableView that uses mostly standard NSTextTableCellViews but I want some other cells that contain another UI component in my table in one or two rows. The question is: Where do I define those custom cells so that Cocoa finds them?
I just dropped a custom NSTableCellView into my XIB (same XIB in that the table is) and gave it an identifier. But then using this code it will obviously not find the cell ...
func tableView(tableView:NSTableView, viewForTableColumn tableColumn:NSTableColumn?, row:Int) -> NSView?
{
var view:NSTableCellView?;
if (tableColumn?.identifier == "labelColumn")
{
view = tableView.makeViewWithIdentifier("labelCell", owner: nil) as? NSTableCellView;
view!.textField?.stringValue = _formLabels[row];
}
else if (tableColumn?.identifier == "valueColumn")
{
if (row == 1)
{
view = tableView.makeViewWithIdentifier("customCell", owner: nil) as? NSTableCellView;
println("\(view)"); // nil - Not found!
}
else
{
view = tableView.makeViewWithIdentifier("valueCell", owner: nil) as? NSTableCellView;
view!.textField?.stringValue = _formValues[row];
}
}
return view;
}
All works but the cell with id customCell will not be found. How do I tell Cocoa to find it?
You need to drop the new cell view in the table column that would contain that kind of cell view (the one whose identifier is "valueColumn", in this case).
i'm creating an Xcode app and i need to create an NSMutableArray that gets initialized when my switch are on and when a switch is off the value assigned to the switch that was also assigned to the NSMutableArray gets destroyed from the Array, how the code should be? I already initialized my switch but i don't know how to add/destroy the values from the NSMUtableArray
- (void)changeState:(id)sender
{
UICustomSwitch* currentSwitch = (UICustomSwitch*)sender;
if ([currentSwitch isOn]) {
[myMutableArray addObject:currentSwitch.myCustomValue];
} else {
[myMutableArray removeObject:currentSwitch.myCustomValue];
}
}
if you have to add new object in NSMutableArray
ringsArray.addObject(_ring)
if insert object within NSMutableArray
ringsArray.insertObject("Value", atIndex: position)
Same as work on remove value
ringsArray.removeObject("")
ringsArray.removeObjectAtIndex(position)
Newbie here,
I have 4 textfields on my single-view app example (address, city, state and zip). I'm trying to use delegation from each to dismiss the keyboard when the user taps out of each. I can't have two identically named methods.
Here's the method to dismiss the address textfield:
-(BOOL) textFieldShouldReturn:(UITextField *)address
{
if (address == self.address)
{
[address resignFirstResponder];
}
return YES;
}
So, my return key can dismiss the keyboard only if the user is in the address textfield, but I can't figure out how to use delegation for the other textfields. The delegate protocol documentation didn't have any specifics on this.
thanks,
J.
And that's the use of the parameter passed in the textFieldShouldReturn delegate method.
If you have multiple text fields, the same delegate method will be called and the text field sender object is passed as the parameter.
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
if (textField == self.addressField)
{
//Do what you need to do if address field should return
}
else if (textField == self.cityField)
{
//Do what you need to do if city field should return
}
return YES;
}
But if what you want is just to resign the text field and since the text field is passed as the sender, you can just resign the passed text field:
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
I'm able to get my NSBrowser instance to display the correct data in the first column. When I select one of the options, however, the next column simply displays the same set of options. I have read the docs, looked at all of Apple's relevant sample code, and just about everything I could find on the internet but I simply can't figure out the correct way to implement the required methods. The data I'm supplying to the browser is an array of dictionaries. Each dictionary in turn contains a "children" key that is another array of dictionaries. And those dictionaries have their own "children" key that are also arrays of dictionaries, etc. Using JSON for descriptive purposes (objects are dictionaries, arrays are arrays), it looks like this:
data = [
{
name: 'David',
children:[
{
name: 'Sarah',
children: {...}
},
{
name: 'Kevin',
children: {...}
}
]
},
{
name: 'Mary',
children:[
{
name: 'Greg',
children: {...}
},
{
name: 'Jane',
children: {...}
}
]
}
]
So the first column should show "David" and "Mary". If "David" is selected, the next column should show "Sarah" and "Kevin", and so on.
My current implementation relies on a custom method I created that is supposed to translate the browser's index path into the corresponding NSArray level from the provided data. This method looks like:
- (NSArray *)getSelectionInBrowser:(NSBrowser *)browser
{
NSArray *selection = browserData;
NSIndexPath *path = [browser selectionIndexPath];
for (NSUInteger i = 0; i < path.length; i++) {
selection = [[selection objectAtIndex:i] objectForKey:#"children"];
}
return selection;
}
My implementation of the required NSBrowserDelegate protocol methods looks like:
- (NSInteger)browser:(NSBrowser *)sender numberOfRowsInColumn:(NSInteger)column
{
return [[self getSelectionInBrowser:sender] count];
}
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
return [[self getSelectionInBrowser:browser] count];
}
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
return [self getSelectionInBrowser:browser];
}
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
return ![item isKindOfClass:[NSMutableArray class]];
}
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
return nil;
}
- (void)browser:(NSBrowser *)browser willDisplayCell:(NSBrowserCell *)cell atRow:(NSInteger)row column:(NSInteger)column {
NSArray *selection = [self getSelectionInBrowser:browser];
cell.title = [[selection objectAtIndex:row] objectForKey:#"name"];
}
The first column of the NSBrowser is populated with the correct names. However, as soon as I make a selection the program crashes with the error -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 0]. After doing some debugging, the line of code it crashes on is the objectAtIndex: call in my custom getSelectionInBrowser:.
That doesn't fully surprise me because even before the crash I figured I was doing something wrong by relying on that custom method to retrieve the current selection. I imagine this work should be done within the delegate methods themselves and, when implemented correctly, the current selection should be accessible in the item variable that is provided in many of those methods. However, I couldn't get that to work. The item variable always seemed to be simply the root data object rather than reflecting the most "drilled-down" selection.
So how do I correct my implementation?
Solved it! Here is my final working code. No need for that custom getSelection... method, and a couple of the delegate methods I had were unnecessary (only used of you are NOT going with the "item-based API").
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
if (item) {
return [[item objectForKey:#"children"] count];
}
return [browserData count];
}
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
if (item) {
return [[item objectForKey:#"children"] objectAtIndex:index];
}
return [browserData objectAtIndex:index];
}
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
return [item objectForKey:#"children"] == nil;
}
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
return [item objectForKey:#"name"];
}
The first method is how you tell the NSBrowser the number of rows there should be. The second method is where you determine what data should be represented in a given row (index). In both cases, you must first check to see if item actually exists. If it doesn't, that's because you are at the root of the data (first column in the NSBrowser). Only when a row (or item!) in the NSBrowser gets selected will the item variable hold anything. The final method should return the string you wish to show in the given row.
Hopefully this helps people in the future.
I have an entity Tag (in core data) that have some attributes and a to-many relationship named "aliases".
My ArrayController is "bind" in Xcode to:
Parameters->Managed Object Context = File's Owner.managedObjectContext
It work OK for all Attributes, the others columns present the correct values.
In one column I try to "display" this to-many relationship. If I do it naively and bind the Value of my NSTableColumn to my ArrayController.arrangedObjects.aliases (like all other attributes) I get this on the screen:
Relationship fault for (),
name aliases, isOptional 1, isTransient 0, entity Tag,
renamingIdentifier aliases, validation predicates ( ), warnings ( ),
versionHashModifier (null), destination entity TagAlias,
inverseRelationship tag, minCount 0, maxCount 0 on 0x10053db10
It seems to be some kind of CoreData proxy for the relationship...
I then build a subclass of NSValueTransformer:
#interface tagAliasesToStringTransformer : NSValueTransformer
+ (Class)transformedValueClass;
+ (BOOL)allowsReverseTransformation;
- (id)transformedValue:(id)value;
#end
and tried to use it as a Value Transformer in the binding. But I'm lost in my implementation:
#implementation tagAliasesToStringTransformer
+ (Class)transformedValueClass {
return [NSString class];
}
+ (BOOL)allowsReverseTransformation {
return NO;
}
- (id)transformedValue:(id)value {
if (value == nil) return nil;
...
}
#end
In the method transformedValue:, value is of class '_NSFaultingMutableSet' and I don't know how to get a Set/Array of the aliases or anything useful.
The goal is to build a NSString of the concatenation of each alias. Something like:
aTag : alias1 alias2 alias3 ...
I found the solution:
_NSFaultingMutableSet is actually a kind of NSSet, so by doing something like this:
- (id)transformedValue:(id)value {
if (value == nil) return nil;
NSArray *tags = [value allObjects];
for (Tag *tag in tags) {
...
}
}
you get access to all the entity.
Don't know why it's obvious now and not one week ago... getting out of newbie zone ?