I have a NSTableView with 10 cells. They are all the same cells and I want to reuse them, however I can't seem to get the table view to do so.
Currently, each time a new cell is needed, the delegate cannot seem to find the previous cell with the same identifier and has to recreate it.
Below is my code, any suggestions on what I might be doing wrong?
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return 10;
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
static NSString *cellIdentifier = #"Name";
NSTableCellView *cellView = [tableView makeViewWithIdentifier:cellIdentifier owner:self];
if (cellView == nil)
{
NSLog(#"A cell");
NSRect cellFrame = [_scrollView.contentView bounds];
cellFrame.size.height = 65;
cellView = [[NSTableCellView alloc] initWithFrame:cellFrame];
[cellView setIdentifier:cellIdentifier];
}
return cellView;
}
I did some testing and noticed that the table will continually create new cells until the table cannot display all cells at the same time.
At this point, the table will reuse the cells that are not visible.
Related
I have a problem which should be a trivial one but I still can't find a good solution to it.
I have a tableView in which I store cells representing different threads/chatrooms. I have an NSArray with data for cells/chats.
I want to display a badge (with a custom badgeCell) indicating a new message on a corresponding cell.
When I receive a push notification my method searches through the cells and finds indexPath for a cell connected with that new message and adds it to NSArray. Then I reload the tableView.
In tableView:willDisplayCell:forRowAtIndexPath: I check if the cells' indexPath is inside that NSArray of indices and if so I display a badge.
Everything works just fine until I scroll tableView so the cell is not visible. When it comes back again it doesn't have any badge.
Also if my cell is not visible when the Push Notification comes it also doesn't display the badge.
I know it has something to do with ReuseIdentifiers but I can't think of any good solution. Please help me !
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier1 = #"MyCell";
ProblemsTableViewCell *cell;
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
if(!cell) {
cell = [[ProblemsTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier1];
}
if ([cell respondsToSelector:#selector(setSeparatorInset:)]) {
cell.separatorInset = UIEdgeInsetsZero;
}
[cell setSelectionStyle:UITableViewCellSelectionStyleGray];
cell.delegate = self;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell.contentView.superview setClipsToBounds:NO];
[cell.contentView setClipsToBounds:NO];
cell.clipsToBounds = NO;
return cell;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(nonnull UITableViewCell *)cell forRowAtIndexPath:(nonnull NSIndexPath *)indexPath{
ProblemsTableViewCell * Cell = (ProblemsTableViewCell*)cell;
/* … some customization - > backgroundColor/images etc. … */
for(NSIndexPath* index in self.newMessages){
if(indexPath.row == index.row){
Cell.badgeString = #"new message"; // the _badge property is a built in view of a custom Cell class with a badge.
Cell.badgeColor = [UIColor whiteColor];
Cell.badgeColorHighlighted = [UIColor whiteColor];
Cell.badge.radius = 0;
Cell.badge.fontSize = 15;
Cell.badge.layer.opacity = 0.55f;
NSLog(#"update 0");
break;
}
}
}
Instead of trying to maintain an array of indices, use the array with cell data. Add an indicator for each element in there to say whether a badge is needed or not and use it to turn the badge on or off during cellForRowAtIndexPath.
Here are my 2 NSTableCellViews:
The 1st is bigger than the 2nd (this one is used when the user is searching the tableview and there are no results) but when i run my app the 2nd gets the size of the 1st:
Why is this happening? Here's my part of the code
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
if (noResults){
return 1;
}else{
return [array count];
}
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
if (noResults){
NSTableCellView *result = [tableView makeViewWithIdentifier:#"NoResults" owner:self];
result.textField.stringValue = [NSString stringWithFormat:#"No results for \"%#\"", _searchTXT.stringValue];
return result;
}else{
SearchTableCell *result = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
result.textField.stringValue = #"Name";
result.textField2.stringValue = #"Last Name";
return result;
}
}
You need to implement the tableView:heightForRowAtIndexPath: method. Otherwise the cell will all be the height of the table views rowHeight property.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (noResults){
// return NSTableCellView height here;
}else{
// return SearchTableCell height here;
}
}
Hope this helps you.
For a view based NSOutlineView, implement the delegate method outlineView:heightOfRowByItem: to return the desired row height.
To avoid constants within your code, consider determining the height from the associated view:
- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
{
NSView* view = [self outlineView:outlineView viewForTableColumn:nil item:item];
return (view ? NSHeight(view.frame) : outlineView.rowHeight);
}
How can I set first row of tableview checkmarked when the app started? I mean when I start app now, there are few rows in tableview and when I click on some of them its accessory change to checkmark. When click another one, the another one comes checkmarked and previous checkmark dissapears. So now I want first row of tableview to be checkmarked when app is started and then when I click another row it 'uncheckmark' and 'checkmark' new row etc...
Try the options suggested in these posts how to apply check mark on table view in iphone using objective c? or iPhone :UITableView CellAccessory Checkmark
Basically you need to keep track of the checkmarks using say a dictionary or so. And In viewDidLoad or init method make the first cell as checked in the dictionary. While drawing cells, always check if the corresponding entry in the dictionary is checked or not and display check mark accordingly. When user taps on a cell, modify the value in dictionary to checked/unchecked.
Update:
Here is a sample code.
In .h file declare a property as
#property(nonatomic) NSInteger selectedRow;
Then use the below code,
- (void)viewDidLoad {
//some code...
self.selectedRow = 0; //if nothing should be selected for the first time, then make it as -1
//create table and other things
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// create cell and other things here
if (indexPath.row == self.selectedRow)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// some other code..
if (indexPath.row != self.selectedRow) {
self.selectedRow = indexPath.row;
}
[tableView reloadData];
}
You could set an integer to a variable similar to "checkedCell", have that value default to cell 0, and in the cellForRowAtIndexPath check to see if the cell is already checked, if not change the accessory to make it checked and update your variable.
-(void)viewWillAppear{
currentCheckedCell = 0;
}
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//
// Create cells -- if indexpath.row is equal to current checked cell reload the table with the correct acessories.
//
static NSString *cellIdentifier = #"RootMenuItemCell";
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyCell" owner:self options:nil];
for (UIView *view in nibContents) {
if ([view isMemberOfClass:[MyCell class]]) {
cell = (MyCell *)view;
break;
}
}
//OTHER CELL CREATION STUFF HERE
// cell accessory
UIImage *accessory = [UIImage imageNamed:#"menu-cell-accessory-default.png"];
UIImage *highlighted = [UIImage imageNamed:#"menu-cell-accessory-selected.png"];
if(indexPath.row == currentCheckedCell){
//DEFAULT ACCESSORY CHECK
// cell.accessoryType = UITableViewCellAccessoryCheckmark;
UIImageView *accessoryView = [[UIImageView alloc] initWithImage:accessory highlightedImage:highlighted];
}else{
//DEFAULT ACCESSORY NONE
// cell.accessoryType = UITableViewCellAccessoryNone;
UIImageView *accessoryView = [[UIImageView alloc] initWithImage:accessory highlightedImage:accessory];
}
[cell setAccessoryView:accessoryView];
}
return cell;
}
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//UPDATE SELECTED CELL & RELOAD TABLE
currentCheckedCell = indexPath.row;
[self.tableview reloadData];
}
Also worth noting that my examples uses custom images for accessories.
If you check out the link #ACB provided you'll find a very similar concept.
You can use,
if (firstCellSelected)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
Set the firstCellSelected flag in viewDidLoad method.
I would like to create an NSTableview with custom NSTableCellViews.
Here is what I have right now:
A nib file for the cell (view nib) called CustomCell.xib
A custom class for my cell called CustomCell
And the code in my AppDelegate.m:
Here I create my table view programmatically:
NSScrollView *tableContainer = [[NSScrollView alloc]initWithFrame:NSMakeRect(self.window.frame.size.width-TABLEWIDTH, 0, TABLEWIDTH, self.window.frame.size.height)];
NSTableView *tableView = [[NSTableView alloc] initWithFrame:NSMakeRect(self.window.frame.size.width-TABLEWIDTH, 0, TABLEWIDTH, self.window.frame.size.height)];
NSTableColumn *firstColumn = [[[NSTableColumn alloc] initWithIdentifier:#"firstColumn"] autorelease];
[[firstColumn headerCell] setStringValue:#"First Column"];
[tableView addTableColumn:firstColumn];
tableView.dataSource = self;
tableView.delegate = self;
[tableContainer setDocumentView:tableView];
tableContainer.autoresizingMask = NSViewHeightSizable | NSViewMinXMargin;
[self.window.contentView addSubview: tableContainer];
And here is the delegate method where I would like to put my custom cell code:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// In IB the tableColumn has the identifier set to the same string as the keys in our dictionary
NSString *identifier = [tableColumn identifier];
if ([identifier isEqualToString:#"myCell"]) {
// We pass us as the owner so we can setup target/actions into this main controller object
CustomCell *cellView = [tableView makeViewWithIdentifier:identifier owner:self];
// Then setup properties on the cellView based on the column
cellView.textField.stringValue = #"Name";
return cellView;
}
return nil;
}
In the nib file for my custom cell I have hooked up the cell view with my custom class called CustomCell which subclasses NSTableCellView. I have not done any other steps as for now. So my CustomCell.m is just default initialization code. I haven't touched it. And I did not do anything else in my nib file, so I did not change file's owner or anything like that because I don't really know what to do.
Can anyone help out ? I looked at sample files from the Apple documentation, but after days of researching I have not found any solutions. I would really appreciate if you could help me.
This is what I ended up doing :
Of course you have to subclass NSTableCellView and return it like I did below. If you are familiar with table views in iOS you should be familiar with methods like:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView{
//this will be called first. It will tell the table how many cells your table view will have to display
return [arrayToDisplay count];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
//this is called after the count of rows is set. It will populate your table according to the data your array to display contains
[tableView setTarget:self];
[tableView setAction:#selector(click)];
NSString *identifier = [tableColumn identifier];
if ([identifier isEqualToString:#"TheCell"]) {
CustomCell *cellView = [tableView makeViewWithIdentifier:identifier owner:self];
cellView.cellText.stringValue = [arrayToDisplay objectAtIndex:row];
return cellView;
}
return nil;
}
And the click method that is triggered when a row is selected would look like this:
-(void)click{
int index = [table selectedRow];
// Do something with your data
//e.g
[[arrayToDisplay objectAtIndex:index] findHiggsBoson];
}
And something that has to be added to the NSTableView:
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:#"column"];
column.width = self.frame.size.width;
[tableView addTableColumn:column];
You do not need to subclass NSTableView to have custom NSTableViewCell subclasses.
You might consider using a view-based Table View also...
I am still in the process of getting myself acquainted with the iPhone SDK.
This is what I am trying to do :
I have a UIScrollView and each scroll view has an UITableView and I have implemented a custom UITableViewCell.
The desired functionality is initially there is no selection, then the user selects the row and scrolls and makes another selection in the next scroll view and continues. I want the selection to stay, so the user can change the selection at a later point.
However what is happening in my case is - the first and the second UITableViews are fine, the selected row stays selected, but in the third UITableView I see a row "already" selected which is same as the first UITableView. I do not want to see any selected.
I know I am doing something stupid but cannot figure out what. Any help would be really appreciated.
Thanks,
Amy
Here are the relevant data source and delegate methods.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%s", __FUNCTION__);
static NSString * ChoiceTableCellIdentifier = #"ChoiceTableCellIdentifier";
choiceTableCell = (ChoiceTableCell *)[tableView dequeueReusableCellWithIdentifier:ChoiceTableCellIdentifier];
if( choiceTableCell == nil )
{
choiceTableCell = [[[ChoiceTableCell alloc]
initWithFrame:CGRectZero
reuseIdentifier:ChoiceTableCellIdentifier] autorelease];
}
return choiceTableCell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%s", __FUNCTION__);
int newRow = [indexPath row];
int oldRow = [lastIndexPath row];
if ( (newRow != oldRow) || (newRow == 0) )
{
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
UIImageView *indicatorN = (UIImageView *)[newCell.contentView viewWithTag:SELECTION_INDICATOR_TAG_1];
indicatorN.image = [UIImage imageNamed:#"selected.png"];
newCell.backgroundView.backgroundColor = [UIColor clearColor];
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:lastIndexPath];
UIImageView *indicatorO = (UIImageView *)[oldCell.contentView viewWithTag:SELECTION_INDICATOR_TAG_1];
indicatorO.image = [UIImage imageNamed:#"notSelected.png"];
oldCell.backgroundView.backgroundColor = [UIColor clearColor];
lastIndexPath = indexPath;
}
}
You are reusing a cell that has the image "selected.png".
In the method cellForRowAtIndexPath you have to "select" or "deselect" your cell depending your last selection. That is: if indexPath is equals to lastIndexPath you have to put the background "selected.png", else "noSelected.png".
When you reuse a cell, it keep its previous state, so you have to initialize it all.