Getting a cell number from cellForRowAtIndexPath - iphone

I'm making an iPhone app with a Table View, and I'm trying to place a different icon / image next to each cell on a table.
I know that you set the image in (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath with code that looks like this:
UIImage *image = [UIImage imageNamed:#"image.png"];
cell.imageView.image = image;
What I'm trying to figure out is, how do I target a specific cell, so that it has a unique image? Like:
if (cell.number == 0) {
//Use a specific image
}
else if (cell.number == 1) {
//Use a different image
Thanks!

The indexPath variable contains information about the cell's position. Modifying your example:
if (indexPath.row == 0) {
// Use a specific image.
}
See the NSIndexPath Class Reference and NSIndexPath UIKit Additions Reference for more information. It's also important to note that cell numbers reset in each section.

Use the row (and possibly also section) properties in the NSIndexPath passed to your tableView:cellForRowAtIndexPath: method to identify which cell is being queried.

this function is passed an index path, which has a section and a row. indexPath.row will pass back an integer you can check.

When cellForRowAtIndexPath is executed you have access to the indexPath variable, so if you want to customize the cell style depending on the cell index you can do something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 0) {
// code for cell 0
}
else {
if (indexPath.row == 1) {
// code for cell 1
}
}
}
This is just an example, I don't think that customizing your cells by using if conditions is the best idea, but it shows you how to do what you need.
Remember that indexPath contains the section of the table too. If you are using a Grouped table view, you need to manager the section too. For example:
if (indexPath.section == 0) {
// section 0
if (indexPath.row == 0) {
// code for section 0 - cell 0
}
else {
if (indexPath.row == 1) {
// code for section 0 - cell 1
}
}
}
else {
if (indexPath.section == 1) {
// section 1
if (indexPath.row == 0) {
// code for section 1 - cell 0
}
else {
if (indexPath.row == 1) {
// code for section 1 - cell 1
}
}
}
}

For a slightly nicer looking approach I would put all the images you want to use into an array:
_iconArray = #[#"picture1.png", #"picture2.png", #"picture3.png"];
This means that when you come to the cellForRowAtIndex function you can say only:
cell.imageView.image = [UIImage imageNamed:_iconArray[indexPath.row]];
This is also easier if you have more than one section, this time you can make an array of arrays, each containing the required pictures for the different sections.
_sectionsArray = #[_iconArray1, _iconArray2, _iconArray3];
cell.imageView.image = [UIImage imageNamed:_sectionsArray[indexPath.section][indexPath.row];
This immediately makes it very easy to modify the pictures (as you are only dealing with the arrays. And much easier if you have more rows and sections (imagine doing it manually for 100 rows)

Related

When an UITableView is empty, show an UIImage

This is related to another question of mine which wasn't answered in a helpful way (message when a UITableView is empty).
I'm trying to show an UIImage graphic that says You haven't saved any bookmarks over an UITableView when it's empty. I have NSNotification set-up so that when bookmarks are added or deleted, a message is sent so that the UITableView can be updated.
I've been trying to do it with this code. Why won't this work?
- (void)bookmarksChanged:(NSNotification*)notification
{
[self.tableView reloadData];
UIImageView* emptyBookmarks = [[UIImageView alloc] initWithFrame:CGRectMake(75, 100, 160, 57)];
emptyBookmarks.alpha = 1;
emptyBookmarks.image = [UIImage imageNamed:#"emptyBookmark.png"];
[self.view addSubview:emptyBookmarks];
[emptyBookmarks release];
if ([self.dataModel bookmarksCount] == 0)
{
emptyBookmarks.alpha = 1;
}
else
{
emptyBookmarks.alpha = 0;
}
}
I'm probably approaching this the wrong way... But if salvageable, what am I doing wrong?
When I initially have an empty bookmarks tableview, there's no image displayed. After I add a bookmark and then delete it, the image shows. Grrh.
Another way (and IMO the correct way) to do this is to manipulate the backgroundView property on the UITableView.
While making a single cell with a custom image cell would certainly works, I think it overly complicates the logic of your UITableViewController's data source. It feels like a kludge.
According to UITableView documentation:
A table view’s background view is automatically resized to match the
size of the table view. This view is placed as a subview of the table
view behind all cells , header views, and footer views.
Assigning an opaque view to this property obscures the background color
set on the table view itself.
While you probably don't want to just set it to your UIImageView, it is very easy to make a UIView that contains the UIImageView that you want.
Well first off if you were going to do it that way, you would need to reload the tableView after updating the image or model etc. and not before.
But you are probably making things more complicated than they need to be!
Why not just check to see if the data for section 0 and indexPath.row 0 are empty and if so in cellForRowAtIndexPath display a text message accordingly.
// First make sure there is always one row returned even if the dataModel is empty.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger numRows = 0;
if ([self.dataModel lastObject]) {
// Return the number of rows in the section.
numRows = [self.dataModel count]; // etc.
}
if (numRows < 1) numRows = 1;
return numRows;
}
// Then display the data if there is some, otherwise a message if empty.
-(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];
}
if ([self.dataModel lastObject]) {
// setup the cell the normal way here.
} else { // the datasource is empty - print a message
cell.textLabel.text = nil;
cell.detailTextLabel.text = NSLocalizedString(#"You haven't saved any bookmarks", #"");
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.7];
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Are you sure [self.dataModel bookmarksCount] is equal to 0 ?
While I agree that you are probably going about this the wrong way,
your image is allocated and added in your bookmark changed, your notification does not trigger when there are no bookmarks initially. Hence you don't see the image. Call the bookmar changed when your table view inits or appears.
Probably the best way to achieve this is to perform a check in your numberOfRowsInSection method to return 1 if your data source is empty. Then in cellForRowAtIndexPath check if your data source is empty and if it is, create a custom cell that contains whatever you want. In heightForRowAtIndexPath you need to return your custom cell height if your datasource is empty, but only if you want the cell larger than the default. At least that is how I would approach it.
when bookmarks count is nil add one to your row method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
int c;
c = bookmarks.count;
if(c == 0){
c = 1;
}
return c;
}
and then the same check again in your cellforrowatindexpath.
Another thing to be aware of in this situation is that if you're using core data and you're datasource is feeding off an entity, you will want to make sure your model matches. You can get some weird side-effect behavior in certain situations. This is especially true if you allow editing and core data has an empty model but you're tableview is still showing a cell.

Load cell selectively in TableView

I need to return cell in tableView:cellForRowAtIndexPath: only when some condition is true,for example:
if (condition == true)
return nil;
else
return cell;
Returning nil gives me an error.
You'll need to do a little more to conditionally have a cell in a UITableView.
Let's assume you have 3 cells, and the first one is conditional. The first thing you need to do make your table have either 2 or 3 cells based on this condition:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(condition) {
return 3;
} else {
return 2;
}
}
Next you can return the actual cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if(condition) {
if(indexPath.row == 0) {
//create and return conditional cell
} else {
//create and return normal cell
}
} else {
//create and return normal cell
}
}
And voila!
PS: (This is assuming everything is done in a single section. If you need to break that out into multiple sections we can get into that as well).
This is because you cannot have blank spaces in your UITableView, that is not allowed, you must at least return an empty cell. What are you trying to do?
The error presents when the TableView tries to retrieve the next cell and gets nil, it has to get the next cell no matter what.
Depending on exactly what you are intending to do here could you not do perform the conditional test before making a call to tableview:cellForRowAtIndexPath:
EG
if( someCondition )
{
[self.tableview cellForRowAtIndexPath:indexPath]
}
Or if your out come is to only display the table cells that meet a certain condition the I suggest you create a function that would copy those elements from your tableview data into an NSArray that you would use to display the desired/conditional table data.
IE
-(void)composeVisibleTableData
{
[m_visibleTableData removeAllObjects];
for( NSObject* dataObject in m_tableDataArray )
{
if( someCondition )//dataObject meetsCondition
{
m_visibleTableData addObject:dataObject];
}
}
}
Then in your UITableDelegate functions for numberOfRowsInSection: and tableview:cellForRowAtIndexPath: reference m_visibleTableData as the UITableView Data Source.
You should check condition when Cell's datasource is set :)
and Filter your data with condition.

Possible to tag a section in UITableView?

I have a uitableview with cells whose behavior depends on what:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.section == 2) {
do x;
}else{
do y;
}
}
It kind of works when you are dealing with a single table, but when you try to add sections, or subclass things, this type of "magic numbers" break very easily.
My question is, is it possible to "tag" the section? so instead of doing section==1, we will do:
indexPath.section.tag=="user_stats" {load x}
indexPath.section.tag=="answers" {show answers}
indexPath.section.tag=="page" {show pagination}
Yeah, just set up an enum somewhere.
enum { kSectionUserStats = 0,
kSectionAnswers,
kSectionPage };
Then:
if(indexPath.section == kSectionPage)
{
// do x
} else if(indexPath.section == kSectionAnswers)
{
// do y
}
// etc.
This also lets you reorder your sections really easily just by changing their ordering in the enum.
If you are displaying section headers with titles, then the titles themselves can become your tags.
if([self titleForHeaderInSection:indexPath.section] == #"MySection1")
{
//do something
}
else
{
//do something else
}

Collapse UITableViewCell on second click

I want to expand a UITableViewCell with this function, calling from didSelectRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if(selectedCellIndexPath != nil &&
[selectedCellIndexPath compare:indexPath] == NSOrderedSame)
return 128;
return 43;
}
Clicking on a cell expands it, clicking on another cell collapses to old and expands the new cell. So far so good. But I also want to collapse the cell, when clicking on an expanded cell, and don't know how?
As a bonus: the content of a not-expanded-cell is shown, overlapping the cell below. How can I prevent showing content that should be cropped by the size of a collapsed-cell?
Sounds like you just need to check in didSelectRowAtIndexPath:…, if indexPath is equal to selectedCellIndexPath, then set selectedCellIndexPath to nil.
You didn't provide that code, so I'm just guessing.
You can also use a BOOL declared in your header and then int your
-didSelectRowAtIndexPath:indexPath {
if(your condition) {
BOOL Selected = YES
[tableView reloadRowAtIndexPath:indexPath]
}
}
and in
-heightForRowAtIndexPath:indexPath {
if(indexPath.Section == TheOneYouWant && indexPath.row == TheOneYouWant && Selected)
return YourHeight;
else if (indexPath.Section == TheOneYouWant && indexPath.row == TheOneYouWant && !Selected)
return yourOtherHeight;
}

UITableViewCell caching its backgroundView property

I have a UITableView which makes use of cells with custom backgroundViews. I'm assigning the backgroundViews in tableView:cellForRowAtIndexPath:indexPath: as suggested here. In a nutshell, dependent upon the position of a UITableViewCell within its UITableView, I want to assign certain backgroundView images. Here's the code in question:
UIImage *rowBackground = nil;
if (row == 0 && row == sectionRows - 1) {
rowBackground = [UIImage imageNamed:#"row_bg_start_and_end.png"];
} else if (row == 0) {
rowBackground = [UIImage imageNamed:#"row_bg_start.png"];
} else if (row == sectionRows - 1) {
rowBackground = [UIImage imageNamed:#"row_bg_end.png"];
} else {
rowBackground = [UIImage imageNamed:#"row_bg.png"];
}
((UIImageView *)cell.backgroundView).image = rowBackground;
This works fine, and I see the results I'm expecting. I have issues, however, when it comes to the removal or addition of rows, which invariably means certain rows retain their previous backgroundViews, rather than recalculating their placement and updating the view in question.
I understand the purpose of dequeueReusableCellWithIdentifier and why this is happening. But I'm not sure how to go about fixing it correctly. I can tell that the rows are recalculated correctly when tableView:cellForRowAtIndexPath:indexPath: is called by scrolling them off screen, which results in their backgroundView being reset correctly.
Should I be setting the backgroundView property in willDisplayCell:forRowAtIndexPath: also? How should I handle this situation?
The easiest approach to fix the problem you're having is to reprocess the background images of all visible rows when you add new rows to a section.
i.e.
for (UITableViewCell *cell in aTableView.visibleCells)
{
NSIndexPath *cellIndexPath = [aTableView indexPathForCell:cell];
/* look at the cell.backgroundImage and if it's not appropriate
for the indexPath, update it */
}
You could carefully override all the UITableView methods that insert new rows and only check perform this work for the specific rows that need to be updated but I don't think this would be worth the effort.