I followed Connect outlet of a Cell Prototype in a storyboard
So my outlets are connected. I have this method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
HomeCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[HomeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"] ;
}
// Configure the cell.
Group * group = [self.groups objectAtIndex: [indexPath section]];
Friend * friend = [[group friends ]objectAtIndex: [indexPath row]];
NSLog(#"%#", friend.name);
[[cell nameLabel] setText:friend.name];
[[cell statusLabel] setText:friend.status];
[[cell picImageView] setImage:[UIImage imageNamed:#"similey.jpg"]];
return cell;
}
It compiles fine but the cells show up blank with nothing in it. Any suggestions?
Did you set the identifier of the cell in the storyboard to "Cell" to match your dequeueReusableCellWithIdentifier call?
Interface Builder creates objects, and then allows you to replicate them. It doesn't change the definition of a class. So if you design an instance of a class in Interface Builder, you're only designing that single instance. Loading Storyboards and NIBs are mechanisms you can use to replicate that instance.
Designing an instance of a class in IB, and then later going and doing a direct alloc+init of that class will result in an instance completely independent of the one in Interface Builder.
This following line of code indicates to me that you're expecting the instance in the storyboard to have an influence on what happens when you alloc+init your class directly:
[[HomeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"]
If you're ever hitting that line, that's probably your problem. That line shouldn't be there if you created the cell in the storyboard. For cells created in the storyboard, -dequeueReusableCellWithIdentifier: will always return an instance of the cell provided that you matched up the identifiers.
Since HomeCell is a custom UITableViewCell it won't be in a reuse queue, so your table view will need to know about it through the registerClass method, like this:
- (void)viewWillAppear:(BOOL)animated
{
...
[self.tableView registerClass:[HomeCell class] forCellReuseIdentifier:#"Cell"];
}
Related
I have a UITableView embedded inside a parent UIView. I have a CustomUITableViewController class set as delegate and datasource for the tableview.
After a certain background operation, I get an updated array of objects to be displayed in the tableview.
When I update the datasource array and call tableview.reloadData method, the tableview doesn't refresh. It only refreshes if I scroll the tableview.
However, if I call the API as follows:
tableview.beginUpdates -> tableview.reloadSections -> tableview.endUpdates,
it works perfectly and immediately reloads the table.
The problem is that depending on the new data, I have to add a new section, or remove an old section from the tableview.
Hence I am not able to use the reloadSections API.
Any thoughts on how to fix this?
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = #"tempCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
[cell initializeWithModel:modelsToShow[indexPath.row]];
return cell;
}
-(void) showModelsInList:(NSMutableArray*) models {
[modelsToShow removeAllObjects];
[modelsToShow addObjectsFromArray:models];
[self setupDataForList];
[self reloadTable];
}
-(void) reloadTable {
[self.tableView beginUpdates];
NSMutableIndexSet* index = [[NSMutableIndexSet alloc]init];
[index addIndex:0];
[self.tableView reloadSections:index withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
//[self.tableView reloadData]
}
The showModelsInList method is invoked from the other class, in the main thread itself.
The modern way to initialize table view cells is to register the cell class (or nib, if the cell is defined in its own nib). viewDidLoad is a good time to do this...
// if the cell is a prototype defined in the nib containing the table view, or if
// the cell is built in code in its init method
[self.tableView registerClass:[CustomCell self] forCellReuseIdentifier:#"tempCell"];
// or, if the cell is defined in its own nib
UINib *nib = [UINib nibWithNibName:#"your cell's nib name goes here" bundle:nil];
[_tableView registerNib:nib forCellReuseIdentifier:#"tempCell"];
In either case above, the cell must have it's "tempCell" identifier initialized in IB or in code. Then, in cellForRowAtIndexPath, dequeue the cell using the method...
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"tempCell" forIndexPath:indexPath];
No further check is required to see if (cell == nil). This version of dequeue will just work (or crash, if something's not setup correctly).
I think, technically, it's a bug, but the truth is that, though it's not documented, you shouldn't be recreating subviews in cellForRowAtIndexPath when reusing cells.
Create the cells with all needed subviews at design time in Interface Builder. Changing their positions, sizes, and other properties in cellForRowAtIndexPath is okay.
If your cells have different subviews, each cell "type" should be its own class. Create a different prototype cell class with a different identifier for each, and simply use that identifier when you dequeue the cell. That way, you have the proper cell class in cellForRowAtIndexPath.
To reference additional properties (subviews) from your view controller, simply create class files for each cell type (derived from UITableViewCell). Assign it to the prototype UITableViewCell in IB, drag the views to the .h file to create outlets like you do for a view controller, then import that class in your view controller.
So, you might end up with code like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (whatever) {
MyBasicCell *cell = [tableView dequeueReusableCellWithIdentifier:#"basicCell"];
cell.specialLabel.Text = ...
return cell;
} else {
MyOtherCell *cell = [tableView dequeueReusableCellWithIdentifier:#"otherCell"];
cell.otherLabel.Text = ...
return cell;
}
}
I'm trying to display a TableView of a list of songs in a user's library. I used the code from this tutorial (which uses a storyboard, but I would like to try it a different way and with just a subclass of UITableView).
I get the error:
*** Assertion failure in -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:], /SourceCache/UIKit/UIKit-2903.23/UITableView.m:5261
2014-05-07 20:28:55.722 Music App[9629:60b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
and an error Thread 1: SIGABRT on the line:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
This is my code:
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
NSArray *songs = [songsQuery items];
return [songs count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
NSArray *songs = [songsQuery items];
MPMediaItem *rowItem = [songs objectAtIndex:indexPath.row];
cell.textLabel.text = [rowItem valueForProperty:MPMediaItemPropertyTitle];
cell.detailTextLabel.text = [rowItem valueForProperty:MPMediaItemPropertyArtist];
cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Table-view-background.png"]];
cell.textLabel.textColor = [UIColor colorWithRed:0.278 green:0.278 blue:0.278 alpha:1.0];
cell.selectedBackgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Table-view-selected-background.png"]];
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
return cell;
}
The app loads and works fine, showing a blank table when I run it in the iPhone simulator on my Mac. it comes up with this error when I run it on my iPhone.
Any help would be much appreciated, thanks!
If you create the table view programmatically, and you're just using the default UITableViewCell, then you should register the class (in viewDidLoad is a good place). You can also do this for a custom class, but only if you create the cell (and its subviews) in code (use registerNib:forCellWithReuseIdentifier: if the cell is made in a xib file).
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
However, this will only give you a "Basic" table view cell with no detailTextLabel. To get that type of cell, you should use the shorter dequeue method, dequeueReusableCellWithIdentifier:, which doesn't throw an exception if it doesn't find a cell with that identifier, and then have an if (cell == nil) clause to create the cell,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//Configure cell
return cell;
}
Please, if you're using a custom class for your cell check that you've already registered the nib for the cell to use in your table view, as follow:
[self.yourTableViewName registerNib:[UINib nibWithNibName:#"YourNibName" bundle:nil]
forCellWithReuseIdentifier:#"YourIdentifierForCell"];
Apart from that please check that your custom UITableViewCell subclass has the appropiate reuse identifier.
If you're not using a custom class, please follow this instructions from Apple Docs:
The data source, in its implementation of the tableView:cellForRowAtIndexPath: method, returns a configured cell object that the table view can use to draw a row. For performance reasons, the data source tries to reuse cells as much as possible. It first asks the table view for a specific reusable cell object by sending it a dequeueReusableCellWithIdentifier:
For complete information, please go to this link: Creating a Table View Programmatically.
I hope this helps!
The reason for the crash is that you are using
[tableView dequeueReusableCellWithIdentifier: forIndexPath:]
which will cause a crash unless you specify a cell or nib file to be used for the cell.
To avoid the crash, use the following line of code instead
[tableView dequeueReusableCellWithIdentifier:];
This line of code will return nil if there is not a cell that can be used.
You can see the difference within the docs which can be found here.
There is also a good answer to a Q&A that can be found here
I know this is a bit late to answer this question but here is what I was missing.
I was doing:
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
But I should be doing this:
let cell = self.tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
Notice: the self. added next to tableView.
I hope this saves you 24 hours.
if you use default UITableViewCell (not customize one) [tableView dequeueReusableCellWithIdentifier: forIndexPath:], you must registerClass or registerNib; (dequeReusableCellIdentifier:forIndexPath won’t return nil cell, if the cell is not there, then it will automately create a new tableViewCell with the default UITableViewCellStyelDefault!)
otherwise, you can use [tableView dequeueReusableCellWithIdentifier] without registerClass or registerNib
If you are using StoryBoard then select that tableviewcontroller->table->Cell, give that Cell a 'Identifier' in attribute inspector.
Make sure you use the same 'Identifier' in 'cellforrowatindexpath' method.
I am NOT using Storyboards. I have a UITableViewController and I would like to display a list of songs from user's library.
This is my code so far:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
NSArray *songs = [songsQuery items];
return [songs count];
}
But for this block, I do not know what to do. I found a tutorial for storyboards, but it is not valid here:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
NSArray *songs = [songsQuery items];
MPMediaItem *rowItem = [songs objectAtIndex:indexPath.row];
cell.textLabel.text = [rowItem valueForProperty:MPMediaItemPropertyTitle];
cell.detailTextLabel.text = [rowItem valueForProperty:MPMediaItemPropertyArtist];
return cell;
}
This only works in Storyboard because I can click the cell in the storyboard and rename its identifier to 'Cell'. In the .xib/nib file in my project, all I see is a view filled with country's names. I cannot click a single cell, I can only edit the whole table.
My question is, what code must I put in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath in order to display this list?
Thanks!
The basic problem you're encountering is that, when used in conjunction with a properly configured storyboard, dequeueReusableCell... will create a cell of the appropriate type if none is available to dequeue.
If you are using a standard UITableViewCell, you can use the following block to dequeue and/or create an appropriate cell:
static NSString* reuseIdentifier = #"Cell";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if(!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
}
If, on the other hand, you're loading your cells from a nib file, you can just add:
[self.tableView registerNib:[UINib nibWithNibName:nibName bundle:[NSBundle mainBundle]] reuseIdentifier:reuseIdentifier];
to your viewDidLoad.
Alternatively you can use registerClass:forCellReuseIdentifier: if you have a custom cell class that sets up it's own subviews.
First off, check if the cell being dequeued is nil or not.
The next thing I can see a problem with is the fact that you aren't really loading anything in cellForRowAtIndexPath:. Basically what this method does is dequeue some cell which has been marked for reuse in an attempt to save memory, and if you haven't set the proper Restoration Identifier in the Interface Builder, then there is no way to know which Nib you want initialized to use here.
What used to happen (or at least my understanding of it) is that in cellForRowAtIndexPath: you would have to check if the cell returned from dequeueReusableCellWithIdentifier: was nil, and if so, you'd have to create the cell from scratch using something like this:
SomeCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell)
{
cell = [[[NSBundle mainBundle] loadNibNamed:#"SomeCellNib" owner:self options:nil] objectAtIndex:0];
}
and this would ensure that if no cell was dequeued that you would load up a fresh one from scratch, although I believe that Apple actually changed how dequeueReusableCellWithIdentifier: works, and as long as you've registered the Identifier, it will create a new cell for you.
For this reason I'm not sure what the problem is if you've set the Identifier properly, and all I can suggest is to try and manually load the cell.
EDIT: I forgot to mention where Restoration Identifiers can be set. The Restoration Identifer field is in the Identity tab, or the third tab in the Interface Builder:
To create a prototype cell inside a .nib, drag a UITableCellView out from the right sidebar. From there you can create your cell prototype, as well as set the cell reuse identifier.
I'm doing a simples app using Storyboard that a have a View with a UITableView with a UITableViewCell that do the navigation to another UIView.
So a have to code to populate the cell on the table view.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"SampleCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
NSLog(#"cai no init da cell");
}
GPItem *item = [self.items objectAtIndex:indexPath.row];
cell.textLabel.text = #"Post";
cell.detailTextLabel.text = item.imageURL;
return cell;
}
I realised that the code if (cell == nil) { ... never executes so I really need to do that on uses the cell from Storyboard?
Thanks.
You are correct; that code is guaranteed to return a non-nil cell if you are using a storyboard. Also, in iOS 6, the new call dequeueReusableCellWithIdentifier:forIndexPath: never returns nil. See the discussion in my book:
http://www.apeth.com/iOSBook/ch21.html#_registering_a_cell_class
If you've declared your UITableViewCell in table view's prototype cells it's already allocated and just needs to be dequeued. If you're using a custom UITableViewCell subclass, then you must check if it's nil and allocate new entities when necessary.
Nope you don't need that code when using a cell made in your storyboard.
It is probably best to remove this code so that you crash nice and early if the identifier you gave to the cell in interface builder and the identifier you use in code ever drift. This snippet will mask this error and just provide a cell that you most likely was not intending to have.
I have a custom class that extends UITableViewCell. It has two labels and a UISegmentedControl.
Here is the cellForRowAtIndexPath() that I configured. When I inspect "cell" in the debugger it has all the data I'm providing. But somehow that data never gets applied.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCell";
CustomGameCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[CustomGameCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
MyData *my_data = [rows objectAtIndex:indexPath.row];
UILabel *my_date = [[UILabel alloc] init];
my_date.text = my_data.myDate;
[cell setMyDateLabel:my_date];
UILabel *my_question = [[UILabel alloc] init];
my_question.text = my.question;
[cell setMyQuestionLabel:my_question];
UISegmentedControl *my_choices = [[UISegmentedControl alloc]
initWithItems:[NSArray arrayWithObjects:my.firstChoice, my.secondChoice, nil]];
[my_choices setSelectedSegmentIndex:my.choice];
[cell setMyChoiceSegments:my_choices];
return cell
}
The data that I want displayed is currently in an array I create in viewDidLoad() which is accessible to cellForRowAtIndexPath() through the "rows" var.
When I run the code in the simulator I get three rows in the table representing the three elements in the array I created in viewDidLoad(). However, the content of those rows look exactly like what I defined in the storyboard.
What am I missing?
Where are you defining the layout of your cell? In a NIB? In your storyboard? Programmatically in your initWithStyle of CustomGameCell? The implementation details vary a little based upon which approach you use, but you definitely need to either define NIB or prototype cell in your storyboard, or programmatically create the controls, set their frames, perform addSubview so they're included in the cell, etc.
Your code is adding new UILabel objects, not adding them as a subview to anything, doing it regardless if you're using a dequeued cell or not, etc. So there are numerous problems here. To see examples of how you might properly use a custom cell, see Customizing Cells in the Table View Programming Guide. But, like I said, the details vary a bit based upon how you're designing your subclassed UITableViewCell layout, so I hesitate to propose any code until you specify how you're designing your user interface.
You must have added the labels and segmentcontrol in the cells content view of the cell, if not then please do so.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCell";
CustomGameCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[CustomGameCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
MyData *my_data = [rows objectAtIndex:indexPath.row];
cell.myDateLabel.text = my_data.myDate;
cell.myQuestionLabel.text = my.question;
[cell.myChoiceSegments setSelectedSegmentIndex:my.choice];
[cell autorelease];
return cell
}
Also use autorelease for the memory management.