Loaded NSNib orders top level objects in no particular order - cocoa

Here's a piece of code that I'm using to populate view-based NSTableView with data:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
MyCustomCellView *view = (MyCustomCellView *)[tableView makeViewWithIdentifier:#"MyCustomCellView" owner:self];
if (!view) {
NSNib *cellNib = [[NSNib alloc] initWithNibNamed:#"MyCustomCellView" bundle:[NSBundle mainBundle]];
NSArray *array = nil;
if ([cellNib instantiateNibWithOwner:self topLevelObjects:&array]) {
DLog(#"%#", array);
view = [array objectAtIndex:0];
[view setIdentifier:#"MyCustomCellView"];
}
[cellNib release];
}
MyObject *object = [_objects objectAtIndex:row];
[[view titleTextField] setStringValue:object.title];
return view;
}
The DLog statement prints arrays as following for two consecutive delegate calls:
(
"<MyCustomCellView: 0x7fb2abe81f70>",
"<NSApplication: 0x7fb2ab80cbf0>"
)
(
"<NSApplication: 0x7fb2ab80cbf0>",
"<MyCustomCellView: 0x7fb2abb2c760>"
)
This is output only for two rows out of few hundred so I randomly either get my view displayed correctly or I get unrecognized selector error while calling setIdentifier: for view object when view being objectAtIndex:0 is actually an instance of NSApplication top level object from loaded nib.
Is this a bug in nib loading mechanism or am I doing something wrong with this code?

This thread is a little old, but for what it's worth:
It's not clear whether this is a bug, as the documentation is not specific as to the ordering of the array that's passed back in the topLevelObjects: parameter. However, this snippet has worked for me.
NSArray *arrayOfViews;
BOOL wasLoaded = [[NSBundle mainBundle] loadNibNamed:xibName owner:self topLevelObjects:&arrayOfViews];
NSUInteger viewIndex = [arrayOfViews indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [obj isKindOfClass:[MyCustomView class]];
}];
self = [arrayOfViews objectAtIndex:viewIndex];

Related

xcode Converting UITableView to UICollectionView (no valid cell)

EDIT: I should specify that this only happens when I attempt to use the UICollectionViewFlowLayout, not when I try to use a custom view. But either way nothing ever shows up on the CollectionView though it was working just fine before I converted from a TableView.)
So I've been trying to convert a UITableView that I had into a UICollectionView. So far so good. But when I try to run the app it gives me the error:
'the collection view's data source did not return a valid cell from -collectionView:cellForItemAtIndexPath: for index path {length = 2, path = 0 - 0}'
I checked all the similar questions and answers here... so in my viewDidLoad I have (tableView is actually a UICollectionView):
UINib * placeCell = [UINib nibWithNibName:#"Shops" bundle:nil];
[self.tableView registerNib:placeCell
forCellWithReuseIdentifier:CellIdentifier];
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UICollectionView *)tableView numberOfItemsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [_entries count];
//return 5;
}
- (void)tableView:(UICollectionView *)tableView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.item == [_entries count]-1 && page > 1) {
NSLog(#"load more");
//add footer view loading
if (c_page == page) {
// _tableView.tableFooterView = nil;
}
else
{
c_page++;
[self loadPlace:c_page];
}
}
}
- (UICollectionViewCell *)tableView:(UICollectionView *)tableView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
PlaceCell *cell = (PlaceCell *)[tableView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
UINib * placeCell = [UINib nibWithNibName:#"Shops" bundle:nil];
//cell = [cellLoader instantiateWithOwner:self options:nil];
NSArray *topLevelItems = [placeCell instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
Place *p = [_entries objectAtIndex:indexPath.row];
cell.placeName.text = p.PName;
NSLog (#"p:%#", p.PName")
cell.placeImg.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:p.PImage]]];
return cell;
}
I went into the xib of the UICollectionViewCell (PlaceCell) and made sure that "Cell" was the reuseidentifier. And I made sure that the datasource and delegate were connected to file's owner in the collectionView.
I also noticed that when I use a custom layout instead of the flow layout (like this one: https://github.com/ShadoFlameX/PhotoCollectionView/blob/master/CollectionViewTutorial/BHPhotoAlbumLayout.m ) it doesn't give me that error... but my collectionview still isn't populated.
So I'm wondering if there's some sort of log I can run or something I can do to figure out what's going wrong. Because I've tried all the solutions I've seen and it hasn't gotten me anywhere.
When you make a cell in a xib file you should register the xib, not the class. Also, when you register either the class or xib (or make the cell in the storyboard), you don't need an if (cell==nil) clause because your cell will never be nil when you dequeue it with dequeueReusableCellWithReuseIdentifier:forIndexPath:. You should delete that clause.
So the problem is: "Switched from UITableView to UICollectionView and no valid cell is being returned." It is really a two part answer. The crux of which is every instance of UITableView...
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 50, self.view.bounds.size.width, self.view.bounds.size.height-50)];
...you want to turn into "CollectionView"
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 50, self.view.bounds.size.width, self.view.bounds.size.height-50)];
Everything that's a "row":
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
...you'll want to turn into an "item."
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
Ultimately I had to delete the following section entirely:
UINib * placeCell = [UINib nibWithNibName:#"Shops" bundle:nil];
//cell = [cellLoader instantiateWithOwner:self options:nil];
NSArray *topLevelItems = [placeCell instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
My best guess is that the Nib was being loaded twice and that Xcode was complaining that the data wasn't being loaded by the original. So getting rid of that second entry got my cells loaded and populated with data. Hope this helps someone.

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.

Using an array of arrays to populate NSTableView

I currently have a number of arrays, each containing show title, description and duration. I have them in a further 'shows' array and I'm using this array to populate my NSTableView. What I would like to do is extract the show title from each of my arrays for the first column of my table, the description for the second and so on.
The code I have at the moment though takes the first array in my array of arrays and populates column one, the second array for the second column etc. How would I amend what I have so far to get the table to populate correctly? I've tried to use indexOfObject in place of objectAtIndex however doing so throws and exception. Here's my (simplified) code:
AppDelegate.m
- (void)applicationDidFinishLoading:(NSNotification *)aNotification
{
NSArray *show1 = [[NSArray alloc] initWithObjects:#"Title", #"A description", nil];
NSArray *show2...
NSArray *show3...
NSArray *show4...
self.array = [[NSMutableArray alloc] initWithObjects: show1, show2, show3, show4, nil];
[self.tableView reloadData];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [self.array count];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSString *identifier = [tableColumn identifier];
if([identifier isEqualToString:#"title"]) {
NSTableCellView *title = [tableView makeViewWithIdentifier:#"title" owner:self];
title.textField.stringValue = [self.array objectAtIndex:0];
return title;
} else if {...}
return nil;
}
Michele Percich's comment is the correct answer: [self.array objectAtIndex:0] will return the first shows array. What you want is "NSArray * show = [self.array objectAtIndex:row]'" to get the show and then "[show objectAtIndex:0]" to get that shows title. Just a suggestion but I'd use an NSArray of NSDictionary's where the keys are the column identifiers. Then you could just use "[self.array objectAtIndex:row] valueForKey:identifier];"
Note also that the method you're overriding expects an instance of NSView (or subclass) to be returned (read the notes in the NSTableView.h header). You may want to use the tableView:objectValueForTableColumn:row: method instead and just return the appropriate NSString (based on the row & column identifier).

Cocoa : Custom TableView cell?

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:)

Custom cells - how can I separate out tweets into componentsseparatedbyString

I have a custom cell in a tableview. All connections are made. I am conducting a Twitter search. The results of this propagate a custom cell in a tableView. I would like to separate the individual tweets out into an array using componentsseparatedbystring so that I can assign these to 4 labels in my custom cell. Any help would be greatly appreciated. Here is my code.
- (void)fetchTweets
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* data = [NSData dataWithContentsOfURL:
[NSURL URLWithString: #"https://api.twitter.com/1/statuses/public_timeline.json"]];
NSError* error;
tweets = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return tweets.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"TweetCell";
customCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
// added this bit in
cell = [[customCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// THIS IS THE BIT I'M STRUGGLING WITH
// I'M GUESSING THAT THIS LINE SEPARATES THE TWEETS INTO SINGLE TWEETS?
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
// I THEN CREATE AN ARRAY TO HOLD MY COMPONENTS OF THE TWEET SO THAT I CAN SPLIT FOR MY LABELS
NSArray *arrayForCustomCell = [[NSArray alloc] init];
arrayForCustomCell = [tweet componentsSeparatedByString:#":"];
return cell;
}
You have already parsed the results in your async call. Now, you have an array of 'tweet' dictionaries and you can grab any value like this:
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
NSString *tweetText = [tweet valueForKey:#"text"];
NSString *tweetCountry = [tweet valueForKeyPath:#"place.country"] //Nested property
and then you just set your UILabels. You get the idea...

Resources