Cocoa : Custom TableView cell? - macos

I am not really familiar with tables, as I usually make games, but now I want to create a level builder where I need a table view with custom cells. I have created a nib file and I have subclassed NSTableCellView, but I don't know what to do next. All I have is:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
NSScrollView * tableContainer = [[NSScrollView alloc] initWithFrame:NSMakeRect(self.window.frame.size.width-TABLEWIDTH, 0, TABLEWIDTH, self.window.frame.size.height)];
SpriteTable *sT = [[SpriteTable alloc]initWithFrame:NSMakeRect(self.window.frame.size.width-TABLEWIDTH, 0, TABLEWIDTH, self.window.frame.size.height)];
NSTableView *tableView = [[NSTableView alloc] initWithFrame: sT.bounds];
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];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView{
return 4;
}
- (NSView *)tableView:(NSTableView *)tableView
viewForTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row {
// get an existing cell with the MyView identifier if it exists
CustomCell *result = [tableView makeViewWithIdentifier:#"MyView" owner:self];
// There is no existing cell to reuse so we will create a new one
if (result == nil) {
NSLog(#"result = nil");
// create the new NSTextField with a frame of the {0,0} with the width of the table
// note that the height of the frame is not really relevant, the row-height will modify the height
// the new text field is then returned as an autoreleased object
//result = [[[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 250, 70)] autorelease];
// the identifier of the NSTextField instance is set to MyView. This
// allows it to be re-used
result.identifier = #"MyView";
}
// result is now guaranteed to be valid, either as a re-used cell
// or as a new cell, so set the stringValue of the cell to the
// nameArray value at row
result.imageView.image = [NSImage imageNamed:NSImageNameHomeTemplate];
// return the result.
return result;
}
If any, which delegate methods do I have to implement ? And how do I customize my cell WITH a nib file ?

Do this in ur subview->
#implementation suhasView
#synthesize name,containerView;// container view contains ur subview
- (NSView*) myView
{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSNib *theNib = [[NSNib alloc] initWithNibNamed:#"suhas"bundle:bundle];
[theNib instantiateNibWithOwner:self topLevelObjects:nil];
return containerView;
}
In Controller->
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
suhas *a=[[suhas alloc]initWithFrame:NSMakeRect(0,0, 40, 40)];
NSView * v = [a myView];
[a.name setStringValue:#"suhas"];
return v;
}...//sorry for my naming of the class:)

Related

OSX App, NSTextField only responds to Force Click

I am writing my first OS X app and I am running into a problem. I am placing a NSTextField inside of a NSTableViewCell. A single click in the text field does nothing. A force click will, however, activate the textfield to enter text.
Is this because it's embedded in a cell?
So far my code is very simple:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSScrollView * tableContainer = [[NSScrollView alloc] initWithFrame:self.bounds];
mainTableView = [[NSTableView alloc] initWithFrame:NSMakeRect(0, 0, 1200, self.frame.size.height)];
mainTableView.autoresizingMask = NSViewWidthSizable|NSViewHeightSizable;
/// {creating columns here}
[tableContainer addSubview:mainTableView];
mainTableView.backgroundColor = [NSColor whiteColor];
mainTableView.rowHeight = 25;
[mainTableView setDelegate:self];
[mainTableView setDataSource:self];
[tableContainer setDocumentView:mainTableView];
[tableContainer setHasVerticalScroller:YES];
[self addSubview:tableContainer];
[mainTableView reloadData];
}
- (NSView *)tableView:(NSTableView *)tableView
viewForTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row {
NSView *view = [[NSView alloc] initWithFrame:CGRectMake(0, 0, tableColumn.width, tableView.rowHeight)];
NSTextField *tf = [[NSTextField alloc] initWithFrame:view.bounds];
tf.stringValue = #"!";
tf.editable = YES;
tf.delegate = self;
[view addSubview:tf];
return view;
}

Getting a CollectionView delegate to load properly

(Let me first put the error up here so that I don't forget it: could not dequeue a view of kind: UICollectionElementKindCell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard)
I've got a regular UICollectionViewController that loads just fine. But I also have another controller (UserViewController) that loads a CollectionView delegate (UserPlaceViewController).
I get really confused with what I have to load in the delegate and what I have to load in the current controller. But anyway here's the delegate as-is:
#import "UserPlaceViewController.h"
#interface UserPlaceViewController ()
#end
#implementation UserPlaceViewController
#synthesize placeArray, placeTable;
- (void)viewDidLoad
{
[super viewDidLoad];
// placeTable.delegate = self;
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(145, 150);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
placeTable = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:flowLayout];
//self.collectionView.frame = CGRectMake(10, 120, self.view.bounds.size.width-20, self.view.bounds.size.height-50);
placeTable.autoresizesSubviews = YES;
placeTable.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
//placeTable.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
placeTable.scrollEnabled = YES;
self.view = placeTable;
NSLog(#"%lu", (unsigned long)[placeArray count]);
}
That's basically the whole thing. Couple other things at the end... but that's it for the most part.
And here are the relevent parts of the ViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES animated:YES];
UserPlaceViewController *collectionView = [[UserPlaceViewController alloc] initWithNibName:#"UserPlace" bundle:nil];
UINib * placeCell = [UINib nibWithNibName:#"Shops" bundle:nil];
[collectionView.placeTable registerNib:placeCell forCellWithReuseIdentifier:cellIdentifier];
- (void) fetchedData: (NSData *) responseData
{
UserPlaceViewController * uptvLike = [[UserPlaceViewController alloc] init];
if (IS_IPHONE5) {
uptvLike.view.frame = CGRectMake(0, 0, 320, 300);
}
else
{
uptvLike.view.frame = CGRectMake(0, 0, 320, 200);
}
uptvLike.placeTable.delegate = self;
uptvLike.placeTable.dataSource = self;
uptvLike.placeTable.layer.borderWidth = 1.0f;
uptvLike.placeTable.layer.borderColor = [UIColor colorWithRed:5/255.0f green:96/255.0f blue:255/255.0f alpha:1.0f].CGColor;
[uptvLike.placeTable reloadData];
[self.scrollView addSubview:uptvLike.view];
}
And finally:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
PlaceCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
Place * p = [placeArray objectAtIndex:indexPath.item];
cell.placeName.text = p.PName;
cell.placeImg.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:p.PImage]]];
return cell;
}
Like I said: I know how a UICollectionView is supposed to work. I've got one running. I know you have to register a nib or class in viewDidLoad (I've registered a Nib.) But I'm confused over how exactly to load this delegate and where to load what. For example I see that I'm initialising the collectionview in both the delegate and the view controller. I'm also confused about WHERE exactly I should load the nib... (delegates give me a headache...)
So what the hell... It's raining. I figured I'd ask.
Ok. You want a UICollectionView but you also want to have a lot of regular "View" items on the same screen? Two things are important:
1. You don't need two ViewControllers. You just need to load what would normally be your UICollectionViewController as a regular ViewController:
#interface UserViewController : UIViewController <UICollectionViewDataSource, UIScrollViewDelegate>
2. Once that's done you can load your collectionview on TOP of that view:
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(145, 150);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:flowLayout];
[self.scrollView addSubview:self.collectionView];
[self.view addSubview:HUD];
UINib * placeCell = [UINib nibWithNibName:#"UserCell" bundle:nil];
[self.collectionView registerNib:placeCell forCellWithReuseIdentifier:#"Cell"];
The rest is just your standard CollectionView stuff.

NSOutlineView Source List without a NIB

I am trying to figure out how to do a source list 100% in code. I've been hacking away at SideBar demo and various SO questions/answers and have gotten close, but not quite there. I've copied my code below, this seems to work in that it does build a view, with 3 empty cells I can select. What I can't quite figure out is how to get my text to show up in those cells. I will ultimately replace that with my own view, but I'd settle for the stock rendering to work for now..
In my controller:
#implementation SourceListController {
NSScrollView *_scrollView;
NSOutlineView *_sourceList;
SourceListDataSource *_sourceListDataSource;
}
- (void)loadView {
_scrollView = [[NSScrollView alloc] init];
_sourceList = [[NSOutlineView alloc] init];
_sourceListDataSource = [[SourceListDataSource alloc] init];
[_sourceList setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
NSTableColumn *c = [[NSTableColumn alloc] initWithIdentifier: #"Column"];
[c setEditable: NO];
[c setMinWidth: 150.0];
[_sourceList addTableColumn: c];
[_sourceList setOutlineTableColumn:c];
[_sourceList setDelegate:_sourceListDataSource];
[_sourceList setDataSource:_sourceListDataSource];
[_scrollView setDocumentView:_sourceList];
[_scrollView setHasVerticalScroller:YES];
[_sourceList reloadData];
NSLog(_sourceList.dataSource == _sourceListDataSource ? #"YES" : #"NO");
self.view = _scrollView;
}
And in my data source/delegate:
#implementation SourceListDataSource {
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
return item == nil ? 3 : 0;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
return [NSObject new];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
return item == nil;
}
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
NSLog(#"Here %f", tableColumn.width);
NSTableCellView *result = [[NSTableCellView alloc] init];
NSTextField *textField = [[NSTextField alloc] init];
[textField setStringValue:#"Test"];
[result addSubview:textField];
return result;
}
UPDATE: Further digging tells me viewForTableColumn doesn't even get called.. I am stumped as to why..
UPDATE 2: Figured out that it was the missing column and outlineTableColumn leading to not calling the delegate method. I fixed that and updated the code, but still no text shows up.
Your delegate method needs changes, it won't work that way, and also doesn't make use of the new caching methods for views. Mostly, use the NSTableCellView.textFieldproperty instead of adding your own NSTextFieldwithout frame and frame constraints. So, the code that should work looks like this:
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
NSTableCellView *result = [outline makeViewWithIdentifier:#"MyView" owner:self];
if (result == nil) {
// Create the new NSTextField with a frame of the {0,0} with the width of the table.
// Note that the height of the frame is not really relevant, because the row height will modify the height.
CGRect myRect = CGRectMake(0,0,outlineView.framesize.width,0);
result = [[NSTableCellView alloc] initWithFrame:myRect];
// The identifier of the view instance is set to MyView.
// This allows the cell to be reused.
result.identifier = #"MyView";
}
result.textField.stringValue = #"Test";
return result;
}
All the above typed in SO.

How does the NSTableView set content Mode(view-based or cell-based) by code?

As title.
How does the NSTableView set content Mode(view-based or cell-based) by code?
Thanks for helping
NSTableView defaults to being cell-based, which makes sense for backwards compatibility. Table views are view-based when the table view delegate implements -tableView:viewForTableColumn:row:. You can easily test by programmatically creating a table view as follows:
#implementation BAVAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSView *contentView = self.window.contentView;
NSTableView *tableView = [[NSTableView alloc] initWithFrame:(NSRect){{50, NSMaxY(contentView.frame) - 200}, {400, 200}}];
tableView.dataSource = self;
tableView.delegate = self;
[contentView addSubview:tableView];
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:#"column"];
column.width = 400;
[tableView addTableColumn:column];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return 3;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
return [NSString stringWithFormat:#"%ld", row];
}
//- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// NSTextField *textField = [[NSTextField alloc] initWithFrame:(NSRect){.size = {100, 15}}];
// textField.stringValue = [NSString stringWithFormat:#"%ld", row];
// return textField;
//}
#end
If you run this code with that delegate method commented out, you get a cell-based table view:
And if you uncomment that delegate method, you get a view-based table view:
The documentation for -tableView:viewForTableColumn:row: states that
This method is required if you wish to use NSView objects instead of NSCell objects for the cells within a table view. Cells and views can not be mixed within the same table view.
which hints at it being the condition that determines whether a table view is cell- or view-based.

Simple UICollectionView to show images behaves odd: Some Images are displayed correct, some at wrong position, some missing at all

I want to show images in a grid on iPhone using a UICollectionView, showing 3 images a row. For a "dead simple test" (as I thought), I've added 15 JPG images to my project, so that they'll be in my bundle and I can load them simply via [UIImage imageNamed:...].
I think I've done everything correct (setting up & registering UICollectionViewCell subclass, use of UICollectionViewDataSource Protocol methods), however, the UICollectionView behaves very weird:
It shows only a few of the images in the following pattern:
First line shows image 1 & 3, second line is blank, next line like the first again (image 1 & 3 showing properly), fourth line blank, and so on...
If I push a button in my NavBar that triggers [self.collectionView reloadData], random cells appear or disappear. What drives me nuts is that it's not only an issue of images appear or not. Sometime, images also swap between the cells, i.e. they appear for a indexPath they are definitely not wired up!
Here is my code for the cell:
#interface AlbumCoverCell : UICollectionViewCell
#property (nonatomic, retain) IBOutlet UIImageView *imageView;
#end
#implementation AlbumCoverCell
#synthesize imageView = _imageView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_imageView = [[UIImageView alloc] initWithFrame:frame];
[self.contentView addSubview:_imageView];
}
return self;
}
- (void)dealloc
{
[_imageView release];
[super dealloc];
}
- (void)prepareForReuse
{
[super prepareForReuse];
self.imageView.image = nil;
}
#end
Part of the code for my UICollectionViewController subclass, where 'imageNames' is an NSArray holding all jpg filenames:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.collectionView registerClass:[AlbumCoverCell class] forCellWithReuseIdentifier:kAlbumCellID];
}
#pragma mark - UICollectionViewDataSource Protocol methods
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.imageNames count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
AlbumCoverCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kAlbumCellID forIndexPath:indexPath];
NSString *imageName = [self.imageNames objectAtIndex:indexPath.row];
NSLog(#"CV setting image for row %d from file in bundle with name '%#'", indexPath.row, imageName);
cell.imageView.image = [UIImage imageNamed:imageName];
return cell;
}
#pragma mark - UICollectionViewDelegateFlowLayout Protocol methods
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
{
return CGSizeMake(100, 100);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
{
return UIEdgeInsetsMake(0, 0, 0, 0);
}
From the NSLog statement in cellForItemAtIndexPath: I can see that the method is called for all of the cells (not only the one's displayed) and that the mapping between indexPath.row and filename is correct.
Has anybody an idea what could cause this weird behavior?
In the meantime, I've found the solution. It was actually a very subtle error in the implementation of my UICollectionViewCell subclass AlbumCoverCell.
The problem is that I've set the frame of the cell instance as the frame of the UIImageView subview instead of passing the bounds property of the cell's contentView!
Here is the fix:
#implementation AlbumCoverCell
#synthesize imageView = _imageView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// WRONG:
// _imageView = [[UIImageView alloc] initWithFrame:frame];
// RIGHT:
_imageView = [[UIImageView alloc] initWithFrame:self.contentView.bounds];
[self.contentView addSubview:_imageView];
}
return self;
}
- (void)prepareForReuse
{
[super prepareForReuse];
// reset image property of imageView for reuse
self.imageView.image = nil;
// update frame position of subviews
self.imageView.frame = self.contentView.bounds;
}
...
#end

Resources