Set custom HeaderView for each Column in NSTableView - macos

Is it possible to set a custom header view for each Column in NSTable ? When I drag drop a table to a view, I can see that there is only 1 header view for both the columns. I referred this answer and implemented same in my table class.
- (NSTableHeaderView *)headerView{
CustomHeaderView *customHeader = [[CustomHeaderView alloc] initWithFrame: ...];
return customHeader;
}
But this will add one view for all the table columns.
If my understanding is correct, TableHeaderView is only one view, and default header view looks like it contains 2 cells for both columns but its actually one view. ? If so how do I go about having separate views or mimic the same behaviour for custom header view ?

Related

How to expand table view cell and show content beneath it?

I want to have the table view cell expand and show the buttons that I have laid out below the visible view when the cell isn't selected. So far I have managed to expand the cell so that the entire view shows with the buttons, but there is one major problem with this....
The buttons that are supposed to be revealed only when the cell is selected always appear in the table, and the table view looks really weird becuase for each cell there are buttons overlapping the next cell which were supposed to be hidden!
I have tired making a subclass of the cell, but I am stuck because when I override the setSelected method to show the button, all the buttons from all the cells show up, not just the one I clicked, Ill provide my code below.
I there an easier way to show the buttons without using a subclass? And if not how could I use the subclass in a way that wouldn't show all the buttons for all the cells?
Cell Subclass (.m file)
- (void)awakeFromNib {
// Initialization code
editHidden.hidden = YES;
removeHidden.hidden = YES;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
editHidden.hidden = NO;
removeHidden.hidden = NO;
// Configure the view for the selected state
}
Your table view delegate needs to implement tableView:heightForRowAtIndexPath:. Implement this method to return the correct height for your cell given the state that it is in (collapsed or expanded). When it comes time to expand your cell you should update your state and call [tableView beginUpdates]; [tableView endUpdates]; to have it recalculate and relayout the tableview.

more than 2 values passed by table view cell

How can I have more than 2 values held by my table view cell? What I want to happen is when I click a cell in my table view, it will pass 3 values to the next view. But I can only get two values (name, address) because they are contained in the textlabel and detailtextlabel but the (phone) is not being showed in my cells, only name and address.
If u want to display more value in cell the create custom cell.
Option is use NSMutableArray with like below and show all values in custom cell:
NSMutableArray *arrValues = [[NSMutableArray alloc] initWithObjects:[NSArray arrayWithObjects:"Value1","Value2","Value3"],nil];
Add as many array into arrValues.
Now when tableViewDidSelectAtRow 's method: pass this array to next controller
NSArray *arrPassing = [arrValues objectAtIndex:indexPath.row];
create a custom cell for the tableview cell

Multiple UITableView but some rows smaller than others

I have multiple UITableViews for layout purposes on the screen. These are functionally working fine, I'm just boggled as to why they are displaying differently.
My 3 table views are: loginTableView, forgotPass and openAccount The delegate and datasource of all 3 is file's owner. This is what I have on the viewDidLoad:
#define TABLE_CELL_HEIGHT 50;
loginTableView.rowHeight = TABLE_CELL_HEIGHT;
forgotPass.rowHeight = TABLE_CELL_HEIGHT;
openAccount.rowHeight = TABLE_CELL_HEIGHT;
The loginTableView cells show up fine but the other two tables' cells are smaller in height than it. I'm using a custom method to create the cells (same method for all tables) and I can't spot any difference between how the different tables are set up.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return TABLE_CELL_HEIGHT;
}
try implementing this method in your tableView delegate.
Ah - it looks like the correct table view had "Separator = None" set in interface builder where the other two table views did not. This was adding a separator and making them the tiniest bit smaller than the other table view.

NSTableView and displaying sort image in column header question

I have a NSTableView with 3 columns. I can sort using any of the colunms by clicking on the column header. Here is my issue though: I am presorting the array and then loading it in NSTableView so the initial TableView is already sorted by the values in one of the 3 columns. Now when I click on the column headers, i can resort and the small ascending/descending image ( triangle) appears indicating how the sort order is. What I want is, to be able to display this ascendingimage triangle in the column header right at the start when the NSTableView is loaded the first time, for the column based on which I have already sorted the Array. Thanks in advance :)
Thanks Peter and cb160. So I added this in my refreshList method:
The *lastColumn parameter has the right identifier if i display it using NSLog, but still that triangle image does not get loaded when the table loads data the first time. Is there something I am missing here ?
My table View is setup like this:
-(IBAction)refreshList:(id)sender
{
//setup array here and sort the array based on one column. This column has
identifier 'col1' and it works as expected
// trying to set the sortindicator image for the column here
NSTableColumn *lastColumn;
lastColumn = [aTableView tableColumnWithIdentifier:#"col1"];
[aTableView setIndicatorImage:[NSImage imageNamed:#"NSDescendingSortIndicator"]
inTableColumn:lastColumn];
[aTable reloadData];
}
- (int) numberOfRowsInTableView:(NSTableView *)aTable
{ // return count of array
}
- (id)tableView:(NSTableView *)aTable objectValueForTableColumn: (NSTableColumn *)
tableColumn row:(int)row
{
//set up arrays here to load data in each column
}
- (void)tableView:(NSTableView *)aTableView sortDescriptorsDidChange:(NSArray
*)oldDescriptors
{
//sort here when column headers are clicked
}
You can use the -setIndicatorImage:inTableColumn: method on NSTableView to do this.
You can use the strings NSAscendingSortIndicator and NSDescendingSortIndicator to use the built in images (with UIImage's +imageNamed: method.)
You can get the NSTableColumn * parameter for this method using the -tableColumnWithIdentifier: method on NSTableView. Set the idenitfier in the column using the identifier attribute in Interface Builder (see below)

Dynamically hiding columns in a NSTableView

I want to dynamically hide/show some of the columns in a NSTableView, based on the data that is going to be displayed - basically, if a column is empty I'd like the column to be hidden. I'm currently populating the table with a controller class as the delegate for the table.
Any ideas? I see that I can set the column hidden in Interface Builder, however there doesn't seem to be a good time to go through the columns and check if they are empty or not, since there doesn't seem to be a method that is called before/after all of the data in the table is populated.
In Mac OS X v10.5 and later, there is the setHidden: selector for NSTableColumn.
This allows columns to be dynamically hidden / shown with the use of identifiers:
NSInteger colIdx;
NSTableColumn* col;
colIdx = [myTable columnWithIdentifier:#"columnIdent"];
col = [myTable.tableColumns objectAtIndex:colIdx];
[col setHidden:YES];
I've done this with bindings, but setting them up programmatically instead of through Interface Builder.
This psuedo-snippet should give you the gist of it:
NSTableColumn *aColumn = [[NSTableColumn alloc] initWithIdentifier:attr];
[aColumn setWidth:DEFAULTCOLWIDTH];
[aColumn setMinWidth:MINCOLWIDTH];
[[aColumn headerCell] setStringValue:columnLabel];
[aColumn bind:#"value"
toObject:arrayController
withKeyPath:keyPath
options:nil];
[tableView addTableColumn:aColumn];
[aColumn release];
Of course you can add formatters and all that stuff also.
It does not work in the Interface Builder. However it works programatically. Here is how I bind a NSTableViewColumn with the identifier "Status" to a key in my NSUserDefaults:
Swift:
tableView.tableColumnWithIdentifier("Status")?.bind("hidden", toObject: NSUserDefaults.standardUserDefaults(), withKeyPath: "TableColumnStatus", options: nil)
Objective-C:
[[self.tableView tableColumnWithIdentifier:#"Status"] bind:#"hidden" toObject:[NSUserDefaults standardUserDefaults] withKeyPath:#"TableColumnStatus" options:nil];
I don't have a complete answer at this time, but look into Bindings. It's generally possible to do all sorts of things with Cocoa Bindings.
There's no Visibility binding for NSTableColumn, but you may be able to set the width to 0.
Then you can bind it to the Null Placeholder, and set this value to 0 - but don't forget to set the other Placeholders to reasonable values.
(As I said, this is just a start, it might need some tweaking).
A NSTable is just the class that paints the table. As you said yourself, you have some class you give the table as delegate and this class feeds the table with the data to display. If you store the table data as NSArray's within your delegate class, it should be easy to find out if one column is empty, isn't it? And NSArray asks your class via delegate method how many columns there are, so when you are asked, why not looking for how many columns you have data and report that number instead of the real number of columns you store internally and then when being asked for providing the data for (column,row), just skip the empty column.
There is no one time all the data is populated. NSTableView does not store data, it dynamically asks for it from its data source (or bound-to objects if you're using bindings). It just draws using data it gets from the data source and ditches it. You shouldn't see the table ask for data for anything that isn't visible, for example.
It sounds like you're using a datasource? When the data changes, it's your responsibility to call -reloadData on the table, which is a bit of a misnomer. It's more like 'invalidate everything'.
That is, you should already know when the data changes. That's the point at which you can compute what columns should be hidden.
#amrox - If I am understanding your suggestion correctly, you're saying that I should bind a value to the hidden property of the NSTableColumns in my table? That seems like it would work, however I don't think that NSTableColumn has a hidden property, since the isHidden and setHidden messages control the visibility of the column - which tells me that this isn't a property, unless I'm missing something (which is quite possible).
I would like to post my solution updated for Swift 4 using Cocoa bindings and the actual isHidden flag without touching the column widths (as you might need to restore the original value afterwards...). Suppose we have a Checkbox to toggle some column visibility (or you can always toggle the hideColumnsFlag variable in the example below in any other way you like):
class ViewController: NSViewController {
// define the boolean binding variable to hide the columns and use its name as keypath
#objc dynamic var hideColumnsFlag = true
// Referring the column(s)
// Method 1: creating IBOutlet(s) for the column(s): just ctrl-drag each column here to add it
#IBOutlet weak var hideableTableColumn: NSTableColumn!
// add as many column outlets as you need...
// or, if you prefer working with columns' string keypaths
// Method 2: use just the table view IBOutlet and its column identifiers (you **must** anyway set the latter identifiers manually via IB for each column)
#IBOutlet weak var theTableView: NSTableView! // this line could be actually removed if using the first method on this example, but in a real case, you will probably need it anyway.
// MARK: View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Method 1
// referring the columns by using the outlets as such:
hideableTableColumn.bind(.hidden, to: self, withKeyPath: "hideColumnsFlag", options: nil)
// repeat for each column outlet.
// Method 2
// or if you need/prefer to use the column identifiers strings then:
// theTableView.tableColumn(withIdentifier: .init("columnName"))?.bind(.hidden, to: self, withKeyPath: "hideColumnsFlag", options: nil)
// repeat for each column identifier you have set.
// obviously use just one method by commenting/uncommenting one or the other.
}
// MARK: Actions
// this is the checkBox action method, just toggling the boolean variable bound to the columns in the viewDidLoad method.
#IBAction func hideColumnsCheckboxAction(_ sender: NSButton) {
hideColumnsFlag = sender.state == .on
}
}
As you may have noticed, there is no way yet to bind the Hidden flag in Interface Builder as on XCode10: you can see the Enabled or Editable bindings, but only programmatically you will have access to the isHidden flag for the column, as it is called in Swift.
As noted in comments, the second method relies on the column identifiers you must manually set either via Interface Builder on the Identity field after selecting the relevant columns or, if you have an array of column names, you can enumerate the table columns and assign the identifiers as well as the bindings instead of repeating similar code lines.
I found a straightforward solution for it.
If you want to hide any column with the Cocoa binding technology:
In your instance of the NSArrayController, create an attribute/parameter/slot/keyed value which will have NSNumber 0 if you want a particular column to be hidden and any value if not.
Bind the table column object's maxWidth parameter to the data slot, described in (1). We will use the maxWidth bound parameter as a message receiver.
Subclass the NSTableColumn:
import Cocoa
class Column: NSTableColumn {
/// Observe the binding messages
override func setValue(_ value: Any?, forKey key: String) {
if key == "maxWidth" && value != nil { // Filters the signal
let w = value as! NSNumber // Explores change
if w == NSNumber(integerLiteral: 0) {
self.isHidden = true
} else {
self.isHidden = false
}
return // No propagation for the value change
}
super.setValue(value, forKey: key) // Propagate the signal
}
}
Change the class of the column to Column.

Resources