Understanding how different methods in OutlineView relate - objective-c

I'm having trouble fully understanding all the different places you can return cells when using an OutlineView. As far as I can tell there are four places:
NSOutlineViewDataSource has:
outlineView:child
outlineView:objectValueForDataColumn
And NSOutlineViewDelegate has:
outlineView:willDisplayCell
outlineView:dataCellForTableColumn
If I have a outline view with different items, like the SourceList example, where do I do what and why? I have GroupItem headers and a tree of IconAndImage cells that subclass NSTextFieldCell. Where should these be instantiated and where should I set the styling, image and title?

What Cocoa means by the word cell is not the same as what you would call a cell in for example Excel.
In Cocoa, a cell is a NSCell subclass and could be considered as a light-weight reusable NSView. It is used to draw many items in the same way. E.g.
- (void)drawRect:(NSRect)draw_rect {
// ...
for ( id value in myDataArray ) {
[cell setObjectValue:value];
NSRect cellFrame = ...;
[cell drawWithFrame:cellFrame inView:self];
}
So a data source does not return cells, but instead return objects that are parameters to [(NSCell) -(void)setObjectValue:(id)value]. The delegate returns which cell-object to use for each item and should be implemented so that you only create each cell-type once. E.g.
- (NSCell *)outlineView:(NSOutlineView *)outlineView
dataCellForTableColumn:(NSTableColumn *)tableColumn
item:(id)item {
NSCell *cell = nil;
switch(tableColumn.tag) {
case 0:
if ( ! myCell ) {
myCell = [[NSCell alloc] init];
}
cell = myCell;
break;
default:
break;
}
return cell;
}
You should use table column tags or a similar feature to handle column re-ordering by the user.

Related

Setting text and image on NSTableCellView

Hello I'm trying to use an NSTableView in my program and I'm having a problem setting the values for the NSTableCellView and getting them to display in the NSTableView. When I run my program, only blank cells show up. Using NSLog's, I can see that the cell imageView gets set, but doesn't display. When I go to set stringValues for the NSTableCellViews however, I only get null from my NSLog's despite the string containing data. Here's the delegate method I'm having a problem with:
-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSString *cellIdentifier;
NSImageView *pageImageView;
NSString *pageString;
int pageVotes;
if (_connectionArray.count == 0) {
return nil;
}
NSTableCellView *cellView = [[NSTableCellView alloc] init];
if (tableColumn == tableView.tableColumns[0]) {
cellIdentifier = #"firstColumn";
pageImageView = [[_connectionArray objectAtIndex:row] getImage]; //Gets imageView from Page Object
cellView.imageView = pageImageView; //Set image view for cell
NSLog(#"%#", cellView.imageView); //This works
}
if (tableColumn == tableView.tableColumns[1]) {
cellIdentifier = #"secondColumn";
pageString = [[_connectionArray objectAtIndex:row] getTitle];
cellView.textField.stringValue = pageString; //Set text for cell
NSLog(#"%#", cellView.textField.stringValue); //Does not work, returns null
}
if (tableColumn == tableView.tableColumns[2]) {
cellIdentifier = #"thirdColumn";
pageVotes = [[_connectionArray objectAtIndex:row] getVotes];
pageString = [NSString stringWithFormat:#"%i", pageVotes]; //Convert int to string
cellView.textField.stringValue = pageString; //Set text for cell.
NSLog(#"%#", cellView.textField.stringValue); //Does not work, returns null
}
[_tableView makeViewWithIdentifier:cellIdentifier owner:self];
return cellView;
}
I think everything set-up correctly between the Storyboard and the ViewController as well, but I could very well be wrong since this is my first time working with NSTableViews. I've also tried using:
[cellView setImage:pageImageView];
[cellView setTextField:[NSTextField textFieldWithString:pageString]];
but I run into the same issue. If anyone can help I greatly appreciate it! I feel like I'm missing something simple...
Setting the textField and imageView properties of NSTableCellView does not add a text field or an image view to the cell view. Those outlets are just intended to inform the cell view about which of its subviews are the primary text field and/or primary image view. You are still responsible for adding those views to the cell view as subviews or, possibly, as deeper descendant views.
Also, it's a bad idea for your model to vend views. That's not how it should work. Among other things, that will specifically interfere with adding those views to the cell view's subview hierarchy.
It's also strange that you're both creating the cell view and asking the table view to make it (by calling -makeViewWithIdentifier:owner:). Normally, you'd do one or the other, or first try -makeViewWithIdentifier:owner: and only create a view if that fails. And, of course, you wouldn't ignore the return value.
Frankly, the best thing to do is set this all up in Interface Builder. If you do it right, there's no need to implement -tableView:viewForTableColumn:row: at all. Is there a reason you didn't go that route?

Assigning a separate user interface for every table view item

Can someone tell me how to assign a interface to a table view element, using storyboards? I'm making a medical calculator that has different calculators for every equation, and I need help making code that points a element to push to another interface. This is because for every equation, there are different fields to fill out (such as age, oxygen levels, whether someone has diabetes or not, height, etc.) Not every equation needs the same fields.
I have tried doing this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Deselect row
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// Declare the view controller
UIViewController *anotherVC = nil;
// Determine the row/section on the tapped cell
switch (indexPath.section) {
case 0:
switch (indexPath.row) {
case 0: {
// initialize and allocate a specific view controller for section 0 row 0
anotherVC = [[BmiViewController alloc] init];
break;
}
case 1: {
// initialize and allocate a specific view controller for section 0 row 1
/anotherVC = [[AaOxygenGradientViewController alloc] init];
break;
}
}
break;
}
}
But after doing this, it refers back to what was originally in the storyboard document (which is empty because I have created the interface programmicatally), instead of showing my test alert popup.
Also, is it possible to maybe make a bunch of table view cells, then have every one segue to every other view controller in the storyboard?
Thanks a lot in advance!
First, you are running deselectCellAtIndexPath? What is the reason for this? If you are just trying to remove the blue highlight then it's better to change the UITableViewCellSelectionStyle (or something like this) of the cell.
I'm not sure what you're asking for the first part but for the segue part then yes.
In Storyboard set up the segues from the tableViewController to each other VC that you want to segue to and give them all sensible identifiers.
i.e. medicalCalulatorSegue
graphSegue
userDetailsSegue
etc...
Then in your didSelect method you will have something like...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//some stuff...
switch(indexPath.row) {
case 0:
[self performSegueWithIdentifier:#"medicalCalulatorSegue"];
break;
case 1:
[self performSegueWithIdentifier:#"graphSegue"];
break;
case 2:
[self performSegueWithIdentifier:#"userDetailsSegue"];
break;
}
}
This will then segue to each of the different view controllers depending on which cell is selected.
The reason for not deselcting the cell is that in your method prepareForSegue you can then still access the indexPath of the selected cell...
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];

View Based NSTableView with Sections

I am looking for a way to create the iOS like sections in NSTableView (like in iTunes 11 - Attached).
As you can see in the screenshot, "Albums" is one section and "Songs" is second. Any help would be appreciated.
Thanks!
I see this is an old question, but the answer is to use a View based NSTableView then implement tableView:viewForTableColumn:row:.
This is code based on how I do it. It hasn't been compiled in Xcode.
-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSTableCellView *cell = nil;
// get your row from your array of objects.
// determine if it's a section heading or not.
SomeClass *someObject = [self.myObjects objectAtIndex:row];
if (someObject.isSectionHeading) {
cell = [tableView makeViewWithIdentifier:#"HeaderCell" owner:self];
cell.textField.objectValue = someObject.headingName;
} else {
cell = [tableView makeViewWithIdentifier:#"DataCell" owner:self];
cell.textField.objectValue = someObject.rowValue;
}
return cell;
}
And also tableView:isGroupRow will put a grey background on your section headings
-(BOOL)tableView:(NSTableView *)tableView isGroupRow:(NSInteger)row {
BOOL isGroup = NO;
// Choose some way to set isGroup to YES or NO depending on your records.
return isGroup;
}
Make sure in Interface Builder you have set the identifiers for your NSTableCellViews to "HeaderCell" and "DataCell". Or use whatever names you want. As long as it matches your code. You can have as many of these cells as you want.
If you make a subclass of NSTableCellView then you can easily add your own text fields, checkboxes, images, etc. to the view and set their values accordingly.
If you want sections you basically have to roll your own (recognize that row x is supposed to be a section cell and provide a section view. TwUI has TUITableView which enables this (and massively improves scroll performance, in my experience).
There is a very good and simple tutorial showing how to implement a NSTableView with sections with sample code on github. Just watch it here and in the video description there is a link to download the code.
I believe the proper way to deal with this these days is to implement the table view delegate method tableView(rowViewForRow:). If you detect that the specified row is not a header, simply return nil. Otherwise, the process is similar to making a view for a specific table row and column. For example, if you store all your row data in a single array of Any called tableRows, and determine the difference between a header and an ordinary row by using different classes within that array, it might look something like this:
override func viewDidLoad() {
super.viewDidLoad()
let headerNib = NSNib(nibNamed: "HeaderRow", bundle: nil)
tableView.register(headerNib, forIdentifier: NSUserInterfaceItemIdentifier(rawValue: "SectionHeader"))
}
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
guard let headerData = tableRows[row] as? SectionHeader else { return nil }
let header = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "SectionHeader"), owner: nil) as? HeaderRow
header?.titleLabel.stringValue = headerData.title
return header
}
Note that this only works if your table is view-based!

Changing view for selected in view-based NSTableView

How do I change the view for the selected row when using a view-based NSTableView? Specifically, I'd like to have a simple NSView subclass for unselected rows and a more complex NSView subclass for the selected row which allows editing of more information associated with the row item.
An example is the way Things allows you to expand the item being edited as seen here: http://culturedcode.com/things/
My guess is that you want to use a different NSTableCellView subclass when the row is selected. I think you should be able to do something like this:
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSTableView *table = [notification object];
NSIndexSet *allColumns = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [[table tableColumns] count])];
[table reloadDataForRowIndexes:[table selectedRowIndexes] columnIndexes:allColumns];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if ([[tableView selectedRowIndexes] containsIndex:row])
{
// If the row is selected, return an instance of the class for selected views
SelectedTableCellView *selectedView = ...; // Get from makeViewWithIdentifier:
// Set up selectedView
return selectedView;
}
else
{
NonSelectedTableCellView *nonSelectedView = ...; // Get from makeViewWithIdentifier:
// Set up nonSelectedView
return nonSelectedView;
}
}
It might be nice if you elaborated a little bit more on what you mean by "change the view to a more complex view"
Nonetheless, you could for instance, implement - (void)tableViewSelectionDidChange:(NSNotification *)notification in the delegate of the table view, get the selected NSTableRowView if it is visible, and change it in what way you want, which includes making it more complex, expanding it (see below), etc.
To modify the size of a row, you would need to implement - (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row in the same delegate, and call the table view's -noteHeightOfRowsWithIndexesChanged to update the height for particular rows.
I think the app is created by NSOutlineView in outlineview only you can easily expand your selected row...
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
if ([item isKindOfClass:[NSDictionary class]])
{
return YES;
}else
{
return NO;
}
}
I think this way is write..

Coloring NSTableView Text per row

I have a NSTableView that is displaying an array of objects I have. For each of these objects (rows) I would like to change the color of the text displayed depending on the results of a function I run on each object;
So for example all the object in the table that exist in another list (or some other requirement) I want to display them in green text, and objects that don't exist display in red.
How would I go about doing this?
Assuming that you have NSTextFieldCell in your table (for other cells, setting text color may vary), you can achieve this by implementing a NSTableView's delegate method.
First, you have to define a delegate for the NSTableView, either in Interface Builder or in your code. This can be your application controller for example.
Then, just implement the following method:
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
NSTextFieldCell *cell = aCell;
if (...) {
[cell setTextColor:[NSColor greenColor]];
} else if (...) {
[cell setTextColor:[NSColor redColor]];
} else {
[cell setTextColor:[NSColor blackColor]];
}
}
Each time the NSTableView will draw a cell, you have the opportunity to modify it before it get drawn.
Check out the NSTableViewDelegate documentation page for more details.