NSView in NSCell - objective-c

I've read a lot about this but i can't get it to work, i have a custom NSCell with this code
#import "ServiceTableCell.h"
#implementation ServiceTableCell
-(void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSLog(#"I'm being called");
NSView *newview = [[NSView alloc] initWithFrame:cellFrame];
NSImage *image = [NSImage imageNamed:#"bg.png"];
NSRect imagesize;
NSImageView *IMV = [[NSImageView alloc] initWithFrame:imagesize];
[IMV setImage:image];
[newview addSubview:IMV];
[controlView addSubview:newview];
}
And this my NSTableView data source:
- (long)numberOfRowsInTableView:(NSTableView *)tableView {
return 3;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(long)row
{
return [[ServiceTableCell alloc] initTextCell:#"dd"];
}
As i understand, the drawwithframe... gets called when the cell is initialized but it never gets called, so, what am I missing?

The method tableView:objectValueForTableColumn:row: should return an object value, not a cell.
Note that NSTableView is substantially different from UITableView, which you may be familiar with. For example, the data source doesn't return cells that are filled with the data, but returns the data. And the cell type in an NSTableView is set per column, you can't have a different kinds of cells in one column (well, technically, that's not entirely true, you could have different cells through -[NSTableColumn dataCellForRow:]).

So thanks to #puzzle answer and a little more digging the answer was to set my subclass of NSCell as the main cell in the InterfaceBuilder, then the method was being called, and as he said, in tableView:objectValueForTableColumn:row: i needed to return the data to then draw it.

Related

tableView:cellForRowAtIndexPath: never being called

I have a UITableView, named tblParentComments in a UIView, of class CBox.
I have definitely set my view as the datasource and delegate of my table view, and my view does implement those protocols. The method tableView:numberOfRowsInSection: does get called and returns a non-zero value. But the function tableView:cellForRowAtIndexPath: is never called.
I noticed that if I put the method tableView:cellForRowAtIndexPath: in comments, Xcode does NOT stop compiling with an error like "tableView:cellForRowAtIndexPath: is required" -- the app just runs and show a empty table.
I don't understand. Any ideas? This is the code for my view:
Interface CBox.h
#interface CBox : UIView <UITableViewDelegate, UITableViewDataSource>
And in the implementation file:
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
tblParentComments = [[UITableView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, frame.size.height)];
tblParentComments.delegate = self;
tblParentComments.dataSource = self;
//tblParentComments.userInteractionEnabled = NO;
tblParentComments.backgroundColor = [UIColor clearColor];
tblParentComments.separatorStyle = UITableViewCellSeparatorStyleNone;
tblParentComments.bounces = NO;
[self addSubview:tblParentComments];
}
return self;
}
#pragma mark - UITableViewDelegate + UITableViewDatasource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"num of rows = %d", parentComents.count);
return 1; // I set a non-zero value for test
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
.... // I set a breakpoint here, never been called here
}
YES..i have the same problem... and I just found out the solution.
In my class i use different inits with different parameters.
In my -(void)viewDidLoad i use to alloc the table view with CGRectZero, and ONLY in this case IF u DONT set up the FRAME of the UITableView then:
the numberOfRowsInSection will BE CALLED
the cellForRowAtIndexPath will NEVER BE CALL
I just set up my UITableView frame and it's works.
As I read above comments I can figure out couple of things:
You probably have messed up a bit structure of your code. You should always conform to protocols in your view controller - ! not view. Alternatively, what I like to do (as it gives me better control over my code and it keeps things clean), separate protocols out of view controller - means create new object (model object) that will handle everything what table requires and it will conforms to table delegate and datasource.
If you organise your code wisely, you should avoid situation you described.
Also I believe you may have 2 objects conforming to table protocols, and thats where the things get ugly.

Delegate events for NSTextField in a view-based NSOutlineView?

I have a flawless functioning view-based NSOutlineView with a proper set-up datasource in my project. Now I want to allow the user to change certain entries. So I made the NSTextField in the IB editable. For a cell-based NSOutlineView you can use the delegate method outlineView:setObjectValue:forTableColumn:byItem: however it's not available for a view-based NSOutlineView as stated in the header file for the NSOutlineViewData protocol:
/* View Based OutlineView: This method is not applicable.
*/
(void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
So I searched for another delegate method and found outlineView:shouldEditTableColumn:item:. However this delegate method doesn't get fired. Probably because I'm not editing a cell.
So my question is: Is there any other way to notice when a row changed than having a delegate for each NSTextField?
You are correct that your text field needs to be editable in Interface Builder.
Next, make your controller conform to NSTextFieldDelegate. Then, set the delegate for the text field in outlineView:viewForTableColumn:item:, like so:
tableCellView.textField.delegate = self
Here's a simplified example, where you've implemented the method for returning the table cell view for an item for your outline view.
-(NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
NSTableCellView *tableCellView = [outlineView makeViewWithIdentifier:#"myTableCellView" owner:self];
MyItem *myItem = (MyItem *)item; // MyItem is just a pretend custom model object
tableCellView.delegate = self;
tableCellView.textField.stringValue = [myItem title];
tableCellView.textField.delegate = self;
return result;
}
Then, the controller should get a controlTextDidEndEditing notification:
- (void)controlTextDidEndEditing:(NSNotification *)obj
{
NSTextField *textField = [obj object];
NSString *newTitle = [textField stringValue];
NSUInteger row = [self.sidebarOutlineView rowForView:textField];
MyItem *myItem = [self.sidebarOutlineView itemAtRow:row];
myItem.name = newTitle;
}
Well, it seems like Apple wants us to use the delegate methods of each NSTextField as stated here:
This method is intended for use with cell-based table views, it must not be used with view-based table views. Instead target/action is used for each item in the view cell.
So there's currently no other way to do this.

adding subviews to UITableViewCell's content view makes cell unselectable

I want to add some static text to a UITableViewCell in a UITextView.
UITextView *addressField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 75)];
[addressField setBackgroundColor:[UIColor clearColor]];
[addressField setFont:[UIFont fontWithName:#"HelveticaNeue" size:14]];
[addressField setContentInset:UIEdgeInsetsMake(0, 20, 0, 0)];
[addressField setEditable:NO];
[addressField setScrollEnabled:NO];
// change me later
[addressField setText:#"John Doe\n555 Some Street\nSan Francisco, CA, 00000"];
[cell.contentView addSubview:addressField];
[addressField release];
This works great but I this code makes the cell unselectable probably because the UITextView is covering the entire cell.
How can I work around this so that I can have both the UITextView and selectable cells?
btw, I could make the UITextView size a bit smaller but users would still not be able to select the cell if they touch the UITextView.
I think a slightly better way to do it is to create a tap gesture recognizer on the entire table. (For example in your viewDidLoad)
// gesture recognizer to make the entire cell a touch target
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(changeFocus:)];
[tableView addGestureRecognizer:tap];
[tap release];
Then you create a selector (changeFocus: in this case) to do the actual selecting.
- (void)changeFocus:(UITapGestureRecognizer *)tap
{
if (tap.state == UIGestureRecognizerStateEnded)
{
CGPoint tapLocation = [tap locationInView:self.tableView];
NSIndexPath* path = [self.tableView indexPathForRowAtPoint:tapLocation];
[self tableView:self.tableView didSelectRowAtIndexPath:path];
}
}
You can make your changeFocus method more elaborate to prevent selections or give focus to specific subviews of the selected indexPath.
I would adopt the following approach in order to keep interaction enabled with both the UITextView and the UITableViewCell.
Declare your controller class (a UITableViewController I guess ?) as UITexView delegate.
When you declare your UITextView, set the table view controller as it's delegate.
Implement one of the UITextViewDelegate methods (ex : - (void)textViewDidChangeSelection:(UITextView *)textView) in your table view controller .m file.
From within this method you can manipulate the targeted cell either with a custom code or by triggering the tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *) delegate method through selectRowAtIndexPath:animated:scrollPosition:.
Your code might then look like :
In the table view controller .h file :
#interface MyTableViewController : UITableViewController <UITextViewDelegate> { ...
...
}
In the table view controller .m file :
UITextView *addressField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 75)];
[addressField setDelegate:self];
...
Then implement this function for example (or any other suitable UITextViewDelegate function) :
- (void)textViewDidChangeSelection:(UITextView *)textView {
// Determine which text view triggered this method in order to target the right cell
...
// You should have obtained an indexPath here
...
// Call the following function to trigger the row selection table view delegate method
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]
}
Note that there are other alternatives like subclassing UITextView and deal with it's touch methods. I would recommend to use the possibilites offered by its delegate protocol though.
Note also that it might be handy to have your UITextView declared or at least referenced as an instance variable of the table view controller class. This will help you easily keep track of which addressField was hit and get the right indexPath.
[addressField setUserInteractionEnabled:NO];
I hope this helps you a bit:
[self.view insertSubview:TextView aboveSubview:TableView];
Or vice-versa based on your requirements.

NSButton inside NSTableView Programmatically?

How can I set a column of checkboxes inside a NSTableView? So far, this is what I have done, but it just puts the int 1 in the column, not the checkboxes:
-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row {
if ([[tableColumn identifier] isEqualToString:#"Enable"]) {
NSButtonCell *cell=[[[NSButtonCell alloc] init] autorelease];
[cell setAllowsMixedState:YES];
[cell setButtonType:NSSwitchButton];
NSCell *aCell = [tableColumn dataCellForRow:row];
[aCell setObjectValue:cell];
return [NSNumber numberWithInt:1];
}
return [[data objectAtIndex:row+1] objectAtIndex:[[data objectAtIndex:0] indexOfObject:[tableColumn identifier]]];
}
you need to use the dataCellForTableColumn delegate method.
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
if([[tableColumn identifier] isEqualTo:#"delete"]){
NSButtonCell *cell = [[[NSButtonCell alloc] init] autorelease];
[cell setAllowsMixedState:YES];
[(NSButtonCell *)cell setButtonType:NSSwitchButton];
[cell setTitle:#""];
return cell;
}else {
NSCell *cell = [tableColumn dataCell];
return cell;
}
}
In Interface Builder, drag a Check Box Cell to the table column. That will set the data cell for the column. Set up the cell however you want in its Inspector. Then delete all the code you have here that acts on the cell, and just return the NSNumber.
In response to your desire to do this programmatically: Interface Builder is really the way to go here. It's designed for laying out GUI objects. I'll tell you how to do it, though.
First, calling setObjectValue: on an NSCell with another NSCell as the argument doesn't transform the first into the second, or change the pointer values or something like that. The setObjectValue: method changes the objectValue of the cell -- essentially, the object that it displays as text.
If you want to supply a cell for the table column programmatically, you'll need to call -[NSTableColumn setDataCell:] with your desired NSCell somewhere (awakeFromNib would probably be a good choice), but be warned, the table column reuses the same cell instance for every row, and just modifies it before it's drawn so it displays correctly. There's no way to set a different cell for each row in a column, UPDATE: without subclassing NSTableColumn.

Drag-drop from NSTableView to NSImageView

I need to drag a row from NSTableView containing an image path and drop it over NSImageView, and the image of the dragged row should appear in the imageview. Help appreciated
Thanks a lot. It really worked. I registered NSImageView and NSTableView for NSStringPboardType and NSFilenamesPboardType. Then in TableView delegate I used
the following code.
- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
{
NSString *string = [filePath objectAtIndex:[rowIndexes firstIndex]];
[pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
[pboard setString:string forType:NSStringPboardType];
return YES;
}
And in NSImageView NSDragging Destination informal protocol, used following code.
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
NSString *str = [[sender draggingPasteboard] stringForType:NSStringPboardType];
myImage = [[NSImage alloc] initWithContentsOfFile:str];
[self setImage:myImage];
[self setNeedsDisplay: YES];
return NSDragOperationCopy;
}
cheers :)
First, in your table data source, implement the necessary methods for table row dragging. You'll put data representing the row onto the drag pasteboard in one or more data types. One type you'll use for this is NSFilenamesPboardType, which takes an array of pathnames.
Then, make a subclass of NSImageView that can handle NSFilenamesPboardType in drops. (You'll need to implement methods from the NSDraggingDestination informal protocol.) Then make your image view an instance of this subclass, instead of NSImageView, and register that view for NSFilenamesPboardType.