View Based NSTableView with Sections - objective-c

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!

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?

Updating subviews in cells on a UITableView

I'm developing an application in iPad 6.0 using Storyboards.
Let me first explain my goal. I'm trying to achieve a Master-Detail (SplitViewController-like) View Controller using 2 UITableViewControllers.
The first UITableView("Master"), let's call this HeaderTableView, as the name implies, lists down the Headers for the...
...Second UITableView("Detail"), let's call this the EncodingTableView, which contains a programmatically changing CustomTableViewCell (subviews contained within each cell may be a UITextField, UIButton or UISwitch).
See EncodingTableView.m
- (void)updateEncodingFields:(NSArray *)uiViewList
{
// Add logic for determining the kind of UIView to display in self.tableView
// Finally, notify that a change in data has been made (not working)
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *encodingFieldsTableId = #"encodingFieldsTableId";
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:encodingFieldsTableId];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:encodingFieldsTableId];
}
// Change text in textView property of CustomTableViewCell
cell.encodingFieldTitle.text = uiViewList.title;
// added methods for determining what are to be added to [cell.contentView addSubView:]
// data used here is from the array in updateEncodingFields:
}
My HeaderTableView.m, contains the didSelectRowAtIndexPath to update the EncodingTableView
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (![selectedIndexPath isEqual:indexPath]) {
selectedIndexPath = indexPath;
[self updateDataFieldTableViewForIndexPath:indexPath];
}
}
- (void)updateDataFieldTableViewForIndexPath:(NSIndexPath *)indexPath {
[self.encodingTableView updateEncodingFields:self.uiViewList];
}
Question
- Data is all ok but why doesn't EncodingTableView "redraw"ing the fields? My
suspicion is that reusing cells has something to do with this but I just can't figure out why.
Screenshots on the result:
Initial Selection in HeaderTableView
Second Selection in HeaderTableView
What I've tried :
I kept seeing suggestions such as [UITableView setNeedsDisplay],
[UITableView reloadData] and [UITableView setNeedsLayout] but none of
them worked.
Removing the reuse of tableViewCells works fine but this causes parts of my
CustomTableView.encodingFieldTitle to disappear. Not to mention that this might cause performance issues if I were to drop reusing cells.
Restrictions:
I know that a good idea is to use a SplitViewController but this is just a subpart of my app (hence not the RootViewController).
Finally, thanks for reading such a long post. ;)
It looks like you are most likely adding subviews inside tableView:cellForRowAtIndexPath:.
The issue is that if you use cell reuse then are not always starting from a blank slate inside tableView:cellForRowAtIndexPath: instead you can possibly be given a cell back that has already been configured once. This is what you are seeing, a cell that has previously had labels added to it is handed back to you and then you add some more labels over the top.
There are a few way to deal with this:
(My preferred option) Create a subview of UITableViewCell with these extra sub views available as properties.
Ensure the cell setup is only done once
A great place to do this is when you actually create a cell when one does not already exist e.g. inside the if (cell) check
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:encodingFieldsTableId];
// add subview's here and give them some way to be referenced later
// one way of doing it is with the tag property (YUK)
UILabel *subView = [[UILabel alloc] initWithframe:someFrame];
subView.tag = 1;
[cell.contentView addSubview:subView];
}
UILabel *label = (id)[cell.contentView viewWithTag:1];
label.text = #"some value";
One problem i can see in your code is that the cell identifiers used are different in tableView cellForRowAtIndxPath function.
While dequeueing you are using this identifier - > "encodingFieldsTableId"
&
while creating a cell you are using this identifier - > "dataFieldUiGroupTableId".
Ideally these two identifiers should be same !!!
Try adding,
cell.encodingFieldTitle.text = nil;
Before if(cell == nil)
So that whenever your cellForRowAtIndexPath method is called, the string already present in the cell you are going to reuse will get deleted and the new text in uiViewList.title will be displayed.

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];

table view data loading issue

I have a doubt in table view. I have placed a label & switch in table view cell. I set the switch value as NO by default. Now when table view is loaded in simulator the table view displays switch with NO value. Now I selected switch value as YES. But table view uses dequeue reusable cell method Property when table view is scrolled objects will reload every time now what will be the switch value will it be NO or YES?
It will be YES.
One more thing on scrolling tableview it not call reload method. Its just reusing already created tableview cells if you are using deque reusable cell method Property.
TableViewCells get destroyed and created where necessary when the table view gets scrolled.
When a cell gets destroyed as it goes out of the visible screen area, your switch control being a subview of the cell also gets destroyed.
So when you scroll back to one of your previously set switches, what you're actually seeing is another instance of a UITableViewCell with a switch view added to it, and so it looks like the switch value changed back to NO.
What you need is a third thing that remembers what the value of each switch should be in each row. If you're display a table of Core Data entity information, then perhaps you can define a property for your entity like "active". In that case, every time you change a switch value, you set your core data entity "active" property to the switch value e.g.:
-(UITableViewCell *)tableView:(UITableView)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
if(cell == nil)
{
// set switch control tag number (don't use 0)
}
// get switch control by tag number
// pseudocode
Engine *myEngine = [arrEngine objectAtIndex:indexPath.row];
mySwitchControl.active = myEngine.active;
...
}
// when you change switch value, remember to update your core data entity value
-(void)updateSwitchValue:(BOOL) newValue
{
// update value
}
Otherwise, you can simply use a NSMutableArray of bool values to identify which row each switch should be YES or NO:
// my header file (.h file)
#interface
{
NSMutableArray *arrSwitchValues;
}
// my implementation file (.m file)
-(UITableViewCell *)tableView:(UITableView)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
if(cell == nil)
{
// set switch control tag number (don't use 0)
}
// get switch control by tag number
// pseudocode
BOOL switchVal = [arrSwitchValues objectAtIndex:indexPath.row];
mySwitchControl.active = switchval;
...
}
// when you change switch value, remember to update your array
-(void)updateSwitchValue:(BOOL) newValue
{
// update value
}
Hope that helps.
to guarantee not have duplicated cells:
use this code in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
as follows :
for(UIView *v in [cell subviews])
{
if([v isKindOfClass:[UILabel class]] ||[v isKindOfClass:[UISwitch class]])
[v removeFromSuperview];
}

Understanding how different methods in OutlineView relate

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.