Trouble with UITableViewCellAccessory (Checkmark/None toggle) - iphone

edit
Well now don't I feel like an idiot. Until now, I haven't been paying any attention to whether or not a cell gets a checkmark. Somehow, I had UITableViewCellAccessoryCheckmark and UITableViewCellAccessoryNone flipped, so it was turning off when I wanted it on and turning on when I wanted it off. Reading code properly really does help with debugging...
/edit
Below is the code I was having so much trouble with.
From tableView:didSelectRowAtIndexPath:
if (i.need == 0) { // item not needed - hide (#) and turn on checkmark
i.need = 1;
cell.textLabel.text = [NSString stringWithFormat:#"%#", i.name];
cell.accessoryType = UITableViewCellAccessoryNone;
} else if (i.need < 0) { // item not needed - hide (#) and turn on checkmark
i.need = -i.need;
cell.textLabel.text = [NSString stringWithFormat:#"%#", i.name];
cell.accessoryType = UITableViewCellAccessoryNone;
} else { // item not needed - show (#) and turn off checkmark
i.need = -i.need;
cell.textLabel.text = [NSString stringWithFormat:#"%# (%d)", i.name, -i.need];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
The following is what you get for making this mistake.
Tap row with checkmark: (#) hidden, checkmark removed
Tap row without checkmark: (#) shown, checkmark not set until next tap

I'm not sure how I made the mistake of not connecting comment and accessory, but there you go. This is what it should look like:
if (i.need == 0) { // item not needed - hide (#) and turn on checkmark
i.need = 1;
cell.textLabel.text = [NSString stringWithFormat:#"%#", i.name];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else if (i.need < 0) { // item not needed - hide (#) and turn on checkmark
i.need = -i.need;
cell.textLabel.text = [NSString stringWithFormat:#"%#", i.name];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else { // item not needed - show (#) and turn off checkmark
i.need = -i.need;
cell.textLabel.text = [NSString stringWithFormat:#"%# (%d)",
i.name, -i.need];
cell.accessoryType = UITableViewCellAccessoryNone;
}

Related

Ugly heightForRowAtIndexPath and cellForRowAtIndexPath

I have a UItableview, which I'm populating with data, using heightForRowAtIndexPath and cellForRowAtIndexPath. Apparently Apple makes me do things in my code twice.
First I have to calculate the size of my views (for that I have to make them) in heightForRowAtIndexPath and then I have to make them again, to add them to the actual view.
I have a pretty complicated view, so it looks double ugly, when you have to write it twice.
Isn't there a better way to do this?
UPDATE
This is how my code looks. It's not totally the same, but pretty close. Why in the world does apple make me write this twice?
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"heightForRowAtIndexPath");
//Initiating strings
NSString *headlineString;
NSString *subHeadlineString;
NSString *bylineString;
if (global.magazine.issues.count==0) {
return 45;
}else if(indexPath.section == global.magazine.issues.count+1) {
//Finding the right issue and article for this row
Issue *issue = [global.magazine.issues objectAtIndex:global.magazine.issues.count-1];
//Creating the headline
headlineString = [NSString stringWithFormat:#"<span class='bold_style'>FOREWORD</span>"];
//Creating the subHeadline
subHeadlineString = [NSString stringWithFormat:#"%#", [issue.magazine_foreword substringToIndex:100]];
//Creating byline
bylineString = [[NSString stringWithFormat:#"<span class='ital_style'>By %#</span>", issue.magazine_byline] capitalizedString];
}else{
//Finding the right issue and article for this row
Issue *issue = [global.magazine.issues objectAtIndex:indexPath.section-1];
Article *article = [issue.articles objectAtIndex:indexPath.row];
//Creating the headline
headlineString = [NSString stringWithFormat:#"<span class='bold_style'>%#</span>", [article.title uppercaseString]];
//Creating the subHeadline
subHeadlineString = [NSString stringWithFormat:#"%#", [article.main_text substringToIndex:100]];
//Creating byline
bylineString = [NSString stringWithFormat:#"<span class='ital_style'>By %#</span>", article.byline];
}
//Creating the labels
NMCustomLabel *headline = [global.label headLineLabelWithString:headlineString fromTop:30 withWidth:global.screenWidth-60];
NMCustomLabel *subHeadline = [global.label subHeadlineLabelWithString:subHeadlineString fromTop:30+headline.height+10 withWidth:global.screenWidth-60];
NMCustomLabel *byline = [global.label articleBylineLabelWithString:bylineString fromTop:30+headline.height+10+subHeadline.height+10 withWidth:global.screenWidth-60];
//Setting the height of the row
return 30+headline.height+10+subHeadline.height+10+byline.height+30;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"cellForRowAtIndexPath");
//Preparing the cell
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
//Removing former text views
for (UIView *subview in [cell subviews]) {
if (subview.tag == 21 || subview.tag == 22 || subview.tag == 23) [subview removeFromSuperview];
}
//Removing and setting tableview border
[[cell viewWithTag:30] removeFromSuperview];
UIView *rightBorder = [[UIView alloc] initWithFrame:CGRectMake(cell.width-1, 0, 1, cell.height)];
rightBorder.backgroundColor = global.lightGrey;
rightBorder.tag = 30;
[cell addSubview:rightBorder];
//Setting the seletion background color on the cells
UIView *bgColorView = [[UIView alloc] init];
bgColorView.backgroundColor = global.extraLightGrey;
cell.selectedBackgroundView = bgColorView;
if (global.magazine.issues.count==0) {
return cell;
}else if (indexPath.section-1 == global.magazine.issues.count) {
//Finding the right issue and article for this row
Issue *issue = [global.magazine.issues objectAtIndex:global.magazine.issues.count-1];
//Creating the headline
NSString *headlineString = [NSString stringWithFormat:#"<span class='bold_style'>FOREWORD</span>"];
NMCustomLabel *headline = [global.label headLineLabelWithString:headlineString fromTop:30 withWidth:global.screenWidth-60];
headline.tag = 21;
[cell addSubview:headline];
//Creating the subHeadline
NSString *subHeadlineString = [[NSString stringWithFormat:#"%#", issue.magazine_foreword] substringToIndex:100];
NMCustomLabel *subHeadline = [global.label subHeadlineLabelWithString:subHeadlineString fromTop:30+headline.height+10 withWidth:global.screenWidth-60];
subHeadline.tag = 22;
[cell addSubview:subHeadline];
//Creating byline
NSString *bylineString = [[NSString stringWithFormat:#"<span class='ital_style'>By %#</span>", issue.magazine_byline] capitalizedString];
NMCustomLabel *byline = [global.label articleBylineLabelWithString:bylineString fromTop:30+headline.height+10+subHeadline.height+10 withWidth:global.screenWidth-60];
byline.tag = 23;
[cell addSubview:byline];
}else{
//Finding the right issue and article for this row
Issue *issue = [global.magazine.issues objectAtIndex:indexPath.section-1];
Article *article = [issue.articles objectAtIndex:indexPath.row];
//Creating the headline
NSString *headlineString = [NSString stringWithFormat:#"<span class='bold_style'>%#</span>", [article.title uppercaseString]];
NMCustomLabel *headline = [global.label headLineLabelWithString:headlineString fromTop:30 withWidth:global.screenWidth-60];
headline.tag = 21;
[cell addSubview:headline];
//Creating the subHeadline
NSString *subHeadlineString = [NSString stringWithFormat:#"%#", [article.main_text substringToIndex:100]];
NMCustomLabel *subHeadline = [global.label subHeadlineLabelWithString:subHeadlineString fromTop:30+headline.height+10 withWidth:global.screenWidth-60];
subHeadline.tag = 22;
[cell addSubview:subHeadline];
//Creating byline
NSString *bylineString = [[NSString stringWithFormat:#"<span class='ital_style'>By %#</span>", article.byline] capitalizedString];
NMCustomLabel *byline = [global.label articleBylineLabelWithString:bylineString fromTop:30+headline.height+10+subHeadline.height+10 withWidth:global.screenWidth-60];
byline.tag = 23;
[cell addSubview:byline];
}
return cell;
}
The easiest solution is to follow DRY principles and either add height as a property of the objects you are using as a datasource or add a method to your view controller such as:
-(CGFloat)calculateHeightForHeadline:(NSString*)headline andSubHeadline:(NSString*)subHeadline andByLine:(NSString*)byLine
Then at least you only have the calculation code in one place.
Alternatively, you could call [tableView heightForRowAtIndexPath:indexPath] from your cellForRowAtIndexPath method
You have to do it twice because the table view needs to know how tall it is in total before it draws anything - so it calls the height method for every row before it calls any cell method. With your current code, depending on the number of rows, you may be experiencing a slight delay before the table appears - instruments will show you that it is the height method you're spending time in.
I don't know what you custom label classes do but you may be able to calculate the height without having to create views (which is expensive) by using the string or attributed string drawing and size calculation UIKit extensions, which were created for this exact purpose.
heightForRowAtIndexPath
Will be called for all your rows. This delegate method returns the height for your rows. As your design required different height for each rows depending upon the condition. Unfortunately you'll have to calculate the row height each time before your row gets created. It's like we are deciding just before drawing the row what will be it's hight.
There's one way you can avoid this. But I won't advice you to go for it. Because it'll change the design of your UITableView. What you can do is you can decide the maximum height of your row out of all the possible conditions you have. For eg. Let's consider it as 100 pixel. Then you can draw your rest of the cells. However, that'll leave empty space if any of your row is less then 100 pixel. And it'll look shaggy.
Basically, to meet your requirement you'll have to do this twice. No other choice :-(
I wonder, why are you creating your custom labels in heightForRowAtIndexpath? Why don't you just calculate the size of the text with sizeWithFont: or such methods? I think that would be a better way to calculate height of the row. Good Luck!

cell labels are not refreshing in table

I am attempting to call a reloadData on my table's rows on a viewDidAppear method access. However, my cells are not refreshing their values and I cannot figure out why, as it seems everything is being accessed in the order it is suppose to. To make matters more odd, 1 row actually does refresh, but none of the others do.
Here is my code...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Set up the cell...
static NSString *CellWithIdentifier = #"Cell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellWithIdentifier];
NSLog(#"generating cell contents");
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellWithIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [_tableGroup.options objectAtIndex:rowcount];
rowcount++;
//label for currently selected/saved setting
_currentSetting = [[UILabel alloc] initWithFrame:CGRectMake(160, 8, 115, 25)];
[_currentSetting setFont:[UIFont systemFontOfSize:14]];
_currentSetting.backgroundColor = [UIColor clearColor];
_currentSetting.textColor = [UIColor blueColor];
_currentSetting.textAlignment = NSTextAlignmentRight;
[cell.contentView addSubview:_currentSetting];
NSLog(#"added new label to cell");
}
//depending on the setting, set the label in the cell to what is currently selected
if (indexPath.section == 1 && indexPath.row == 0) {
_currentSetting.text = [NSString stringWithFormat:#"%# %#",[settings.mapDistance stringValue], NSLocalizedString(#"MILES_IDENTIFIER", nil)];
NSLog(#"setting map distance label: %#", settings.mapDistance);
}
else if(indexPath.section == 1 && indexPath.row == 1)
{
_currentSetting.text = [NSString stringWithFormat:#"%# %#",[settings.maxCustomers stringValue], NSLocalizedString(#"ITEMS_IDENTIFIER", nil)];
NSLog(#"setting max customers: %#", settings.maxCustomers);
}
else if(indexPath.section == 2)
{
_currentSetting.text = [NSString stringWithFormat:#"%# %#",[settings.maxProducts stringValue], NSLocalizedString(#"ITEMS_IDENTIFIER", nil)];
NSLog(#"setting max products: %#", settings.maxProducts);
}
return cell;
}
based on this code, i get this output with my NSLOGS.
this is the first run of the cells when the view is created. It generates 4 cells, puts labels in each cell, and in 3 of those labels, puts in a value.
generating cell contents
added new label to cell
generating cell contents
added new label to cell
setting map distance: 15
generating cell contents
added new label to cell
setting max customers: 250
generating cell contents
added new label to cell
setting max products: 150
at this point i have clicked a row, went to a different screen, and have now returned. as you can see, map distance is different. although no change is displayed, even though the code to change the label's text is accessed during the reload process.
reloading data
generating cell contents
generating cell contents
setting map distance: 25
generating cell contents
setting max customers: 250
generating cell contents
setting max products: 150
again, I'm at a loss because the last row DOES refresh correctly. But none of the others do.
Thanks
When you reload your tableView, the cells already exist and are dequeued from the tableView, so the condition if (cell == nil) returns false, and the cell creation code is not executed.
In that cell creation code, you are assigning a value to _currentSetting and then proceed with the acode assuming that value is correct. However, when the cell creation code is not executed, that value points to the latest created cell, and thus, it won't update.
To fix this: make _currentSetting a local variable and change the code to look like this:
(You don't really need to make it a local variable, but it's more appropriate because you don't really need a reference to the last label you created after you leave this method)
UILabel *_currentSetting = nil;
if (cell == nil) {
_currentSetting = ...
_currentSetting.tag = 123;
}
else
_currentSetting = [cell.contentView viewWithTag:123];
...
The problem here is that the second time (when you are reloading the view ) the _currentSetting is not having a valid memory .So it is better to implement a custom cell and do the job
Better refer this an excellent guide
The second time around, you can see that "added new label to cell" isn't being called, so you're re-using an old tableViewCell.
Note that you're not setting _currentSetting when re-using a cell, only when creating a new cell. So _currentSetting is set to the last new cell that was created, most likely the last cell in the table.
You need to make sure to set _currentSetting to the correct label (maybe by using viewWithTag: or something similar).
(e:f;b)

UITableView didSelectRowAtIndexPath , different value from what is displayed in section rows!

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Directory *aDirectory = [appDelegate.directories objectAtIndex:indexPath.row];
_XMLDetailsView.aDirectory = aDirectory;
if (indexPath.section == 0) // First section
{
_XMLDetailsView.aDirectory = aDirectory;
}
else if(indexPath.section == 1) // Second Section
{
Directory *aDirectory = [appDelegate.directories objectAtIndex:indexPath.row +[listOfItems count]];
_XMLDetailsView.aDirectory = aDirectory;
}
else if(indexPath.section == 2) // Third Section
{
Directory *aDirectory = [appDelegate.directories objectAtIndex:indexPath.row +[listOfItems count] +3];
_XMLDetailsView.aDirectory = aDirectory;
}
else if(indexPath.section == 3) // Fourth Section
{
Directory *aDirectory = [appDelegate.directories objectAtIndex:indexPath.row +[listOfItems count] +9];
_XMLDetailsView.aDirectory = aDirectory;
}
}
is this the correct way to select certain row of value from certain section? because when i didSelectRowAtIndexpath for e.g section "C" , it is suppose to display the value that starts from array (4) . but instead at section "C" , it displays the value from array(0) again. i'm stuck for so long on this , anyone with any suggestion or help?
else if(indexPath.section == 2) // Third Section
{
if(indexPath.row >4)
{
Directory *aDirectory = [appDelegate.directories objectAtIndex:indexPath.row +[listOfItems count] +3];
_XMLDetailsView.aDirectory = aDirectory;
}
}
Try this, I think it helps you.
Directory *aDirectory = [appDelegate.directories objectAtIndex:indexPath.row];
statement at the start of the method chooses the directory using indexPath.row which will not be from the correct section. Try indexPath.section for this.
Directory *aDirectory = [appDelegate.directories objectAtIndex:indexPath.section];

iPhone: cornerRadius quality gets degraded

I have a UILabel that I create a radius on the layer, using cornerRadius. The ultimate goal is to make the label look like Apple does in the mail app.
It looks great at first, but once you drill down into that row and back a few times, the quality of the rounded edge starts to degrade. You can see in the screen shot, the left side is blocky.
Why would this be happening? It seems to happen after about 2 times of loading that view.
(source: puc.edu)
Here is my cell creation method:
- (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] autorelease];
}
Trip *trip = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = trip.title;
cell.detailTextLabel.text = #"Date of trip";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// Create a nice number, like mail uses
UILabel *count = [[UILabel alloc] initWithFrame:CGRectMake(cell.contentView.frame.size.width - 50, 12, 34, 20)];
[count setText:[NSString stringWithFormat:#"%i",[[trip.rides allObjects] count]]];
[count setFont:[UIFont fontWithName:#"Helvetica-Bold" size:16]];
count.textAlignment = UITextAlignmentCenter;
count.backgroundColor = [UIColor grayColor];
count.textColor = [UIColor whiteColor];
count.layer.cornerRadius = 10;
[cell addSubview:count];
[count release];
return cell;
}
In every call to cellForRowAtIndexPath, you are creating a new count UILabel. Eventually there will be several overlapping views in the same place with the same antialiased curve, so it will look blocky.
Try creating the count UILabel only when a new cell is created, in the if (cell == nil) block. Otherwise, get the count label by tag.
if ( cell == nil ) {
cell = ...
count = ...
...
count.tag = 'coun';
[cell.contentView addSubview:count];
[count release];
} else {
count = (UILabel *)[cell viewWithTag:'coun'];
}
[count setText:[NSString stringWithFormat:#"%i",[[trip.rides allObjects] count]]];
Check to see if the tableview/cell is set to clear its context before drawing. I noticed I had similar issues with text on the cell.
I've seen some strange issues wherein it looks like Core Animation based properties are accumulative even though they shouldn't be. Your problem here might be caused by some kind of accumulative creep from changing the value of the corner radius repeatedly every time the cell is returned.
I would suggest testing if the corner radius already equals 10 before setting it again. (Although I would expect that to show up more with scrolling up and down than in reloading the view.)
Another possible problem would be that some subview is migrating causing a visual artifact.
Edit:
Instead of adding the label to the cell itself. Try adding it as a subview of the cell's content view.

iphone code - custom table instead of default one

i'm using a default style table (UITableViewCellStyleSubtitle)
i want to add more then one detailTextLabel in each row,
how can i customize it?
code:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Leave cells empty if there's no data yet
if (nodeCount > 0)
{
// Set up the cell...
ARecord *aRecord = [self.entries objectAtIndex:indexPath.row];
cell.textLabel.text = aRecord.lDate;
cell.detailTextLabel.text = aRecord.WNum;
// Only load cached images; defer new downloads until scrolling ends
//(!aRecord.appIcon) - use icon
if (!aRecord.appIcon)
{
if (self.tableView.dragging == NO && self.tableView.decelerating == NO)
{
[self startIconDownload:aRecord forIndexPath:indexPath];
}
// if a download is deferred or in progress, return a placeholder image
cell.imageView.image = [UIImage imageNamed:#"Placeholder.png"];
}
else
{
cell.imageView.image = aRecord.appIcon;
}
}
return cell;
}
The best way of doing this is to add a UILabel to the cell.contentView. You would do this when you initially create the cell. I've found two things to be especially helpful: to lay out the label on a table cell in a throwaway document in Interface Builder to determine the initial frame. It's also especially helpful to set the autoresizingMask so that the label will be resized appropriately when the cell is resized (due to autorotation, going into edit mode, etc.).
Finally, you'll need to set the table view's rowHeight to a higher value to accommodate the larger cells, otherwise you'll end up with overlapping cells.
Also, set a tag on your label to make it easy to retrieve with viewWithTag: when you go to update the text.
You could add the labels to cell.contentView.