When I attempt to construct a view per row for my view-based NSTableView the method makeViewWithIdentifier:owner: always returns nil. The table is build in IB and all seems to be linked properly, the delegate methods are all called as I'd expect. When looking through the Apple documentation about the makeViewWithIdentifier -method I can't find a reason why the method will return nil. A snippet from the code:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
if ([tableColumn.identifier isEqualToString:#"foo"]) {
NSTableCellView *view = [tableView makeViewWithIdentifier:#"id" owner:self]; // Is always nil..
view.textField.stringValue = [myArray objectAtIndex: row];
return view;
}
return nil;
}
Why could view be nil?
You must use the same identifier in xib and code:
static NSString * const ItemMainTableCellViewIdentifier = #"ItemMainTableCellViewIdentifier";
nib = [[NSNib alloc] initWithNibNamed:#"ItemMainTableCellView" bundle:nil];
[self.table registerNib:nib forIdentifier:ItemMainTableCellViewIdentifier];
ItemMainTableCellView *cellView = [tableView makeViewWithIdentifier:ItemMainTableCellViewIdentifier owner:self];
Related
The structure of my app currently looks like this:
Collection View Controller -> Generic Cell with table view inside of it -> individual cells.
I would like to call a method in the collection view controller from one of the individual cells. So far I have implemented a delegate in the individual cell but if I can't seem to set my delegate in the collection view controller because I don't have an instance of it.
Furthermore, I have several cells inside the table view that are required to access the methods in the collection view controller.
The responder chain can help.
The view can query the responder chain for the first target that can accept a message. Suppose the message is -fooBar, then the view can query the target using the method -[UIResponder targetForAction:sender:]
// find the first responder in the hierarchy that will respond to -fooBar
id target = [self targetForAction:#selector(fooBar) sender:self];
// message that target
[target fooBar];
Note that this communication is controlled by this method:
(BOOL)canPerformAction:(SEL)action
withSender:(id)sender;
This default implementation of this method returns YES if the responder class implements the requested action and calls the next responder if it does not.
By default, the first object that responds to that message will become the target so you may want to override the canPerformAction:withSender: if needed for some views or view controllers.
For that you can do like that :
In Collection View Controller -> .h file
#interface CollectionViewController : UICollectionViewController<ColectionCellDelegate>
#end
In Collection View Controller -> .m file
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [self.collectionData count];
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CollectionCell *cell = (CollectionCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"CollectionCell" forIndexPath:indexPath];
cell.cellData = [self.collectionData objectAtIndex:indexPath.row];
cell.delegate = self;
return cell;
}
-(void)tableCellDidSelect:(UITableViewCell *)cell{
NSLog(#"Tap %#",cell.textLabel.text);
DetailViewController *detailVC = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
detailVC.label.text = cell.textLabel.text;
[self.navigationController pushViewController:detailVC animated:YES];
}
In CollectionCell.h
#class CollectionCell;
#protocol ColectionCellDelegate
-(void)tableCellDidSelect:(UITableViewCell *)cell;
#end
#interface CollectionCell : UICollectionViewCell<UITableViewDataSource,UITableViewDelegate>
#property(strong,nonatomic) NSMutableArray *cellData;
#property(weak,nonatomic) id<ColectionCellDelegate> delegate;
#end
In CollectionCell.m
#implementation CollectionCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.cellData = [[NSMutableArray alloc] init];
}
return self;
}
-(void) awakeFromNib{
[super awakeFromNib];
self.cellData = [[NSMutableArray alloc] init];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.cellData count];
}
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"TableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
cell.textLabel.text = [self.cellData objectAtIndex:indexPath.row];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[[self delegate] tableCellDidSelect:cell];
}
I'm running into a simple problem but have yet to find an optimal solution. I have a view based NSTableView that is loading it's cell views from different xibs. My table view is dynamic and based on user input I will dynamically add and remove rows ultimately adjusting the table data source. Each one of my NSTableCellViews have a button in it and I link the IBAction click handler to the NSView that holds the table view. What I need to do is get the row number for the button that was clicked in the table view so I can process the logic. I am able to do this successfully in : tableViewSelectionDidChange:(NSNotification *)notification
Here is how I do it:
- (void)tableViewSelectionDidChange:(NSNotification *)notification {
NSTableView *tableView = [notification object];
NSInteger selectedRow = [tableView selectedRow];
}
This works perfectly for a user actually clicking the row. Now when I move the NSButton IBAction and link it in the NSView as follows:
- (IBAction)buttonClickHandler:(NSButton *)sender {
NSInteger selectedRow = [self.tblView rowForView:sender];
NSLog(#"%ld", (long)selectedRow);
}
I based this approach from this selected answer.
I also tried this:
- (IBAction)buttonClickHandler:(NSButton *)sender {
id representedObject = [(NSTableCellView *)[sender superview] objectValue];
NSLog(#"%#", representedObject);
}
//My configuration
- (void)configureView {
[self.view setFrame:[self bounds]];
[self addSubview:self.view];
[self.view setWantsLayer:YES];
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
self.tblView.delegate = self;
self.tblView.dataSource = self;
[self.tblView setIntercellSpacing:NSMakeSize(0, 0)];
[self.tblView registerNib: [[NSNib alloc] initWithNibNamed:#"ParentCellXib" bundle:nil] forIdentifier:#"ParentCell"];
[self.tblView registerNib: [[NSNib alloc] initWithNibNamed:#"ChildCellXib" bundle:nil] forIdentifier:#"ChildCell"];
[self.tblView registerNib: [[NSNib alloc] initWithNibNamed:#"HeaderCellXib" bundle:nil] forIdentifier:#"HeaderCell"];
}
But the represented object returns null. If it's worth mentioning, I've set my File's Owner as the View that holds the tableView so I can link the IBAction and I've subclassed the TableCellView to a different class. However, I don't think this is part of the problem as far as I can see. Is there a simple solution to reliably give me the selectedRow number based on a button click in that cell? Both approaches I tried above return -1 and null respectively.
I would set the row in NSButton's tag property:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
SomeTableCellView *cell = [tableView makeViewWithIdentifier:#"cell" owner:self];
if (cell == nil) {
cell = // init some table cell view
cell.identifier = #"cell";
}
cell.button.tag = row;
[cell.button setTarget:self];
[cell.button setAction:#selector(buttonAction:)];
}
- (IBAction)buttonAction:(id)sender {
NSLog(#"row: %d", sender.tag);
}
Try This
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
yourCustomeCell *aCell;
NSString *aStrIdentifier = #"yourIdentiFier";
aCell = (yourCustomeCell *)[tableView dequeueReusableCellWithIdentifier:aStrIdentifier];
//you have to set your indexpath
objc_setAssociatedObject(aCell.btnUpload_or_Add, #"objBtn", indexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[aCell.YourButton addTarget:self action:#selector(yourButtonActiontapped:) forControlEvents:UIControlEventTouchUpInside];
return aCell;
}
-(IBAction)yourButtonActiontapped:(UIButton *)sender{
NSIndexPath *aIndPath = objc_getAssociatedObject(sender, #"objBtn");
NSLog(#"row:%#",aIndPath.row);
}
also you have to import #import <objc/runtime.h>
another way to get row in IBAction is TAG but objc is better option insted of TAG.
Create a subclass of UIButton and add a property for NSIndexPath for the button. Use this button in cellForRowAtIndexPath method. assign the index path of the cell to that of index path of the button.
On Tap, get the index path from its sender. In your case index path of that button.
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 need to pass an array to my parent view controller, and I'm not sure how to do that. Is my only choice to use a delegate? My application crashes at the line:
[self.parentViewController setrecipientItems:remoteRecipientItems];
with the message:
[UINavigationController setrecipientItems:]: unrecognized selector sent to instance 0x8a10ab0
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int newRow = [indexPath row];
int oldRow = (lastIndexPath !=nil)?[lastIndexPath row]:-1;
if (newRow != oldRow) {
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:lastIndexPath];
oldCell.accessoryType = UITableViewCellAccessoryNone;
// lastIndexPath = indexPath;
lastIndexPath = [indexPath retain];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// UIViewController may not respond to setrecipientItems: warning
[self.parentViewController setrecipientItems:remoteRecipientItems];
[[self.parentViewController.] ]
[self.navigationController popViewControllerAnimated:YES];
}
Also my parent UIViewController is set up like this:
#import <UIKit/UIKit.h>
#interface AddRecipientsTableViewController : UITableViewController {
NSMutableArray *recipientItems;
}
#property(nonatomic,retain)NSMutableArray *recipientItems;
#end
Your answer is in your question :).
[UINavigationController setrecipientItems:]: unrecognized selector sent to instance 0x8a10ab0
The parent view controller, when using a UINavigationController hierarchy, is UINavigationController, not your previous view controller.
If you want to get at that view controller, ask the UINavigationController for its list of view controllers by calling [self.parentViewController viewControllers], and then you can cycle through that NSArray using isKindOfClass: to determine which one is yours.
An NSNotification could also work in this case, or as you suggest, a delegate.
I need an example or explanations of how to populate 2 table views which are on the same view. I need to understand the "cellForRowAtIndexPath" method, could someone provide me an example on how should the code be?
I mean how to identify which goes which table view?
Thanks
Below is my cellForRowAtIndexPath method:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
// Set up the cell
MyAppAppDelegate *appDelegate = (MyAppAppDelegate *)[[UIApplication sharedApplication] delegate];
if (tableView == radios_tv) { //radio_tv is an IBOutleet UITableView
sqlClass *aRadio = (sqlClass *)[appDelegate.array_radios objectAtIndex:indexPath.row];
[cell setText:aRadio.r_name];
return cell;
}
if (tableView == presets_tv) { //preset_tv is an IBOutlet UITableView
}
}
and hey vikingsegundo, now I need to delete a cell which is on my TableViewController class, how do I do this? I explain, here is my code:
- (void)tableView:(UITableView *)tv commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
if(editingStyle == UITableViewCellEditingStyleDelete) {
//Get the object to delete from the array.
Coffee *coffeeObj = [appDelegate.coffeeArray objectAtIndex:indexPath.row];
[appDelegate removeCoffee:coffeeObj];
//Delete the object from the table.
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
Since we put different controllers, how should we proceed for this line? Should I put the tableViewController instead of the "self"?
//Delete the object from the table.
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
IMO the cleanest solution would be to have one controller for each tableview.
radios_tv would call it own delegate's method, while presets_tv calls it own.
edit
if you use one controller for n tableview, you will have to use if-statemenst in many places,
in
– numberOfSectionsInTableView:
– tableView:numberOfRowsInSection:
– tableView:titleForHeaderInSection:
…
basically in all UITableViewDatasource-Protocol methods that you will need to implement.
So if you need to change something, you have to change it in many places.
If you use one controller class for one tableview, you won't have to check at all.
write a controller class for every tableview, make it conforming to the UITableViewDatasource protocol
implement the protocol methods you will need. at least
– numberOfSectionsInTableView:,
– tableView:numberOfRowsInSection:,
– tableView:cellForRowAtIndexPath:
call -setDataSource:for every tableview to an object of the right controller class
I think, it was shown in one of the WWDC 2010 videos. I am not sure, but I guess it was Session 116 - Model-View-Controller for iPhone OS.
edit
I wrote an example code: http://github.com/vikingosegundo/my-programming-examples
On one view controller if you have to use two tables then you can set IBOutlet to both tables or assigns different tag to them so when you use following cellForRowAtIndexPath you can differentiate in both tables as below
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCellStyle style =UITableViewCellStyleSubtitle;
static NSString *MyIdentifier = #"MyIdentifier";
DataListCell *cell = (DataListCell*)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
cell = [[DataListCell alloc] initWithStyle:style reuseIdentifier:MyIdentifier];
cell.selectionStyle=UITableViewCellSelectionStyleNone;
if(tableView==tblLanguage)//tblLanguage IBOutlet for first table
{
if ((selectedIndexPath != nil) && (selectedIndexPath.row == indexPath.row))
{
UIImageView *imgView=[[UIImageView alloc]initWithFrame:CGRectMake(320-30, 9, 22, 15)];
imgView.image=[UIImage imageNamed:#"btn_Expand.png"];
[cell addSubview:imgView];
tblSongs.hidden=NO;
tblSongs.frame=CGRectMake(0,42, 320, ([arrSongListForSpecificLanguage count]*40));
[cell addSubview:tblSongs];
}
else
{
UIImageView *imgView=[[UIImageView alloc]initWithFrame:CGRectMake(320-30, 9, 16, 22)];
imgView.image=[UIImage imageNamed:#"btn_Collaps.png"];
[cell addSubview:imgView];
}
cell.lblCustomerName.textColor=[UIColor blackColor];
cell.lblCustomerName.font=[UIFont boldSystemFontOfSize:16];
//set the first label which is always a NamesArray object
[cell setcustomerName:[objAppDelegate.viewController.arrLanguage objectAtIndex:indexPath.row]];
}
else //for other table
{
ParseData *objParse;
objParse=[arrSongListForSpecificLanguage objectAtIndex:indexPath.row];
cell.lblCustomerName.textColor=[UIColor blackColor];
cell.lblCustomerName.frame=CGRectMake(cell.lblCustomerName.frame.origin.x, cell.lblCustomerName.frame.origin.y, 310, cell.lblCustomerName.frame.size.height);
//set the first label which is always a NamesArray object
[cell setcustomerName:objParse.track];
}
return cell;
}
}
You can also use tag for the same in which your if statement as below
if(tableView.tag==1)//tblLanguage tag=1
Similar if statement use for other delegate & datasource methods of table