Add subview to UITableViewCell on selection - objective-c

I'm trying to add a UIView that is supposed to strikethrough the text (don't worry about the horizontal misplacement).
However, when selecting a row, the line is added several rows below. Why?
Here's my code:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%#", indexPath);
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UILabel *label = cell.textLabel;
CGSize textSize = [[label text] sizeWithAttributes:#{NSFontAttributeName:[label font]}];
CGFloat strikeWidth = textSize.width;
UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(self.view.bounds.size.height / 2, 200, strikeWidth, 2)];
lineView.backgroundColor = [UIColor redColor];
lineView.tag = 100;
[cell.contentView addSubview:lineView];
}

Instead of using a UIView and adding a subview to the cell, you should use an NSAttributedString for the cell text and the NSStrikethroughStyleAttributeName to strike through with an NSStrikethroughColorAttributeName for the strikethrough colour.

Your problem is here:
UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(self.view.bounds.size.height / 2, 200, strikeWidth, 2)];
In this case, "self" is the tableViewController, not the label or the cell. What you're doing is setting the x origin of the view as half the height of the screen, the y origin down 200 points, with a width of strikeWidth and a height of 2.
Because the line view you are adding is going to be a subview of the cell, you always want to make the frame relative to it's superview, which in this case is the cell itself. You likely want to use something like similar to below:
CGRectMake(CGRectGetMinX(cell.textLabel.frame), CGRectGetHeight(cell.contentView.frame) / 2, strikeWidth, 2)
You'll likely want to tweak values to make it line up, but you get the idea...
EDIT: Better frame added and here's more code that does it nicely:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UILabel *label = cell.textLabel;
CGSize textSize = [[label text] sizeWithAttributes:#{NSFontAttributeName:[label font]}];
CGFloat strikeWidth = textSize.width;
UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetMinX(label.frame), CGRectGetHeight(cell.contentView.frame) / 2, strikeWidth, 2)];
lineView.backgroundColor = [UIColor redColor];
[cell.contentView addSubview:lineView];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}

I think your line is appearing outside of your cell because you set an y origin of 200 in your frame, which seems pretty high.
Moreover, if you want to play with strikethrough in a UITableviewCell, you'd better not do it this way, because on multiple selection this 'strikethroughView' will be added multiple times, and never removed. Also on a tableview reloadData, or scrolling the cells are reused, and you don't want to see these strikethroughViews randomly displayed.
Here are two ways to do it properly :
Use the NSAttributedString framework. Basically if allows you to do all sorts of things to a string, like setting color, background color, paragraph style, but also strikeThrough.
Here is what i would write in the didSelect delegate method :
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *string = cell.textLabel.text;
NSDictionary *attributes = #{NSStrikethroughStyleAttributeName: #(NSUnderlineStyleThick)};
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string attributes:attributes];
cell.textLabel.attributedText = attributedString;
}
The other solution would be to add a Category on UICollectionViewCell and implement a "setStrikeTrough" method in it.
Hope it'll help.

Related

UITableView overlaps cells after scrolling

All, I have a UITableView to which I add cells on user input. My cells have a slight transparency, and I recently noticed that after I have more cells than fit the screen, when I scroll down and back up, the cells which were momentarily off-screen now have new cells positioned behind them!
This shows up in my app, and also is evidenced in the view hierarchy debugger.
Interesting to note that it only happens when two or more cells scroll off screen (notice the count skips cell 7, which is at the bottom, just off the screen at the moment.
I figure this has something to do with caching the UIScrollView data for quicker loads? How do I stop it?
Adding a cell:
NSArray *indexPaths = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:numberOfCounters inSection:0], nil];
[counterTable insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationBottom];
numberOfCounters++;
[counters addObject:[[CKCounter alloc] init]];
cellForRowAtIndexPath: method
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *simpleTableIdentifier = #"CKCounter";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
CKCounter *counter = [counters objectAtIndex:indexPath.row];
[counter setCurrentCount:indexPath.row];
[counter setIndex:indexPath.row];
[cell.contentView addSubview:[counter view]];
[cell.contentView setAutoresizesSubviews:TRUE];
[cell setBackgroundColor:[UIColor clearColor]];
/** Sets up the bounds for the subcounters **/
CGRect frame = cell.contentView.bounds;
frame.size.height -= counterMargins; // This will give the counters an inset
frame.size.width -= counterMargins; // The gap will be split to each side
frame.origin = cell.contentView.bounds.origin;
counter.view.frame = frame;
counter.view.opaque = false;
counter.view.center = cell.contentView.center;
//[NSString stringWithFormat:#"%lu", (unsigned long)[indexPath indexAtPosition:0]]
return cell;
}
See also: This Question

UITableViewCell with UILabel subview caching issue

First of all, please don't tell me this is a duplicate. I know this question has been asked and answered many times but I still can't seem to get my code to work even after reading everyone else's solutions.
I'm having an issue with my UITableViewCell that contains a UILabel subview. The UILabel sometimes doesn't appear in certain cells until I scroll away from those cells and return to them. Here is the code I am using to customize the cells:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UILabel *label;
if (cell == nil) {
// cell is nil, create it
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 33)];
label.tag = 888;
} else {
label = (UILabel*)[cell.contentView viewWithTag:888];
[label removeFromSuperview];
}
label.text = #"Label Text";
label.backgroundColor = [UIColor clearColor];
[label sizeToFit];
label.center = CGPointMake(cell.contentView.frame.size.width-label.frame.size.width/2-20, cell.contentView.frame.size.height/2);
[cell.contentView addSubview:label];
// customize cell text label
cell.textLabel.text = #"Cell Text";
cell.textLabel.textColor = [UIColor darkGrayColor];
return cell;
}
It appears as though the label shows up correctly when dequeueReusableCellWithIdentifier:CellIdentifier returns a non nil value but does not show up if the return value is nil and a new cell must be instantiated.
If anyone has an idea of why this might be happening, help would be greatly appreciated.
I see a couple of things you want to do.
1) read up on "sizeToFit" - the description says if the view has no superview you may get weird results.
2) When you create the view, add it to the cell immediately.
3) After you resize the cell, get its size, then compute the proper frame - I'd suggest not using "center" but I do not know that your code won't work for a fact.
4) Before even changing the center to changing the frame, hard code something like this:
CGRect r = label.frame;
r.origin = (CGPoint){ 20, 20 };
label.frame = r;
This will at least convince you that new cells and old cells are working properly. Then you can compute the frame you really want, or further play with center.
Not sure of the cause of your problem, but there are some improvements you could make. Maybe one of them fixes the issue:
In the dequeu scenario, you remove the label from the view, only to add it back. Instead, you should leave it in the view hierarchy.
To avoid having to resize and move the label all the time. Why not make it sufficiently wide, set the text right aligned. That way, you don't have to resize of move the label in the dequeue scenario.
It seems that the issue may be with modifying cell.textLabel. Other posts on this site have suggested that any time this label is modified, a new label is actually created and added to the cell's contentview (as opposed to just modifying the existing label). Setting cell.textLabel.backgroundColor = [UIColor clearColor]; seems to have fixed the problem
I'm still a bit confused about this though because even adding my custom label subview last (after setting properties for cell.textLabel) didn't fix the problem - the background color of cell.textLabel had to be set to transparent/clear.
There are three things out of place:
if (cell == nil) { // cell is nil, create it
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 33)];
label.tag = 888;
[cell.contentView addSubview:label]; // (1) DO THIS HERE
} else {
label = (UILabel*)[cell.contentView viewWithTag:888];
// (2) DON'T DO THIS: [label removeFromSuperview];
}
label.text = #"Label Text";
label.backgroundColor = [UIColor clearColor];
[label sizeToFit];
label.center = CGPointMake(cell.contentView.frame.size.width-label.frame.size.width/2-20, cell.contentView.frame.size.height/2);
// (3) DON'T DO THIS HERE: [cell.contentView addSubview:label];
....
I assume ARC is on, otherwise you need a [label release] after adding it to the contentView.

IOS UITableViewCell Spacer (or margin)

I'm trying to get a space between my table custom view cells on my table view controller. For example, for each cell created it is stacked up one by one and there is no margin. For example on the facebook iPhone app has a spacer after each cell which i would like to create, any ideas guys?
Edit: From comment below
The code looks like this
MSRSalesCompanyCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
cell.backgroundView = [[CustomCellBackground alloc] init];
NSDictionary *company = [companies objectAtIndex:[indexPath row]];
cell.companyName.text = [company objectForKey:#"Name"];
cell.backgroundColor = [UIColor clearColor];
cell.backgroundView.bounds = CGRectMake(0, 0, 320, 55);
[self loadImageAsync:cell withImageUrl:imageURL];
Check my answer for the similar question, it suggests you to create the invisible cells between the cells you want to divide with some space.
Option 2 is to create only the visible cells, make their height above the visible area and prepare special content and selected view's, note the selected view creation is not so easy and you'll need to do it as you probably don't want the spacing area to get selected, so i'm using the first option when there's a need to get some cell's spacing.
The main (and probably the only) disadvantage of the first option is that you have to treat the cell's indexes in a special way to distinguish the spacing-cells and the visible-cells.
If your tableView's rowHeight is 50 points, make your background image view height say, 40 points, and centre it in the cell. Then make your cell background colour [UIcolor clearColor]
Okayy i did some thing like this.....
First the BGColor Of view which will hold the Tableview is set to White (it was a personal choice w.r.t to my design) and then the cellForRowAtIndexPath method looks some thing like this.
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell;
cell = nil;
//Create Cell
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
//Retrive Values From DB
DBTable_Description *dbObject = [[DBTable_Description alloc]init];
dbObject = [contentList objectAtIndex:[indexPath row]];
//Cell View
UIView *cellView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 65)];
//ImageView
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(4.0, 8.0, 300.0, 58.0)];
imageView.userInteractionEnabled = NO;
imageView.image = imgForCellBkg;
//Label
UILabel *lblFor = [[UILabel alloc]initWithFrame:CGRectMake(70, 25, 200, 21)];
lblFor.text =dbObject.field1;
lblFor.backgroundColor = [UIColor clearColor];
lblFor.font = [UIFont fontWithName:#"Helvetica Neue" size:16];
lblFor.textColor = [UIColor grayColor];
lblFor.shadowColor = [UIColor grayColor];
//Adding Views to Cell View
[cellView addSubview:imageView];
[cellView addSubview:lblFor];
[cell.contentView addSubview:cellView];
cell.selectionStyle = NO;
return cell;
}
Okay First and formost neglect the Database code.
Now what i did is i created a View on each Cell name cellView (set the height as per ur requirement) next i had an Image View and set the appropriate image (again it may not be the thing u want to do) but pay attention to the CGRectMake i se the values according to the amount of gap i want b.w my cells.. the rest is usual.
here is the image of the view i had in my App
Let me Know if it worked
Cheers
W

iOS - heightForRowAtIndexPath does not fire during scrolling

I am writing a simple iOS app using Xcode4, which uses a table view to display a list of stories (fetched from a URL). I'm displaying the story titles as UILabels, and as a subview of the table cell.
I am over-riding heightForRowAtIndexPath to calculate the correct height for the cells according to the length of each story title. I'm adding the label to the cell in cellForRowAtIndexPath. When I run the app in the simulator, everything is rendered well. However: when I scroll down and scroll up, the labels get messed up. They get truncated and over-run. I debugged a little, and found that the heightForRowAtIndexPath method is not fired during scrolling, so the cell heights are not re-calculated, and therefore the label text overflows, and gets rendered ugly. Here is the relevant code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:#"Cell"];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
[cell autorelease];
}
/* NOTE: code to load trimmedTitle dynamically is snipped */
NSString* trimmedTitle;
UIFont *cellFont = [UIFont fontWithName:#"Georgia" size:14.0];
CGSize constraintSize = CGSizeMake(280.0f, MAXFLOAT);
CGSize labelSize = [trimmedTitle sizeWithFont:cellFont constrainedToSize:constraintSize
lineBreakMode:UILineBreakModeWordWrap];
UILabel* tempLabel = [[UILabel alloc] initWithFrame:CGRectMake(60, 0, 230, labelSize.height)];
tempLabel.lineBreakMode = UILineBreakModeWordWrap;
tempLabel.text = trimmedTitle;
tempLabel.numberOfLines = 0;
[cell.contentView addSubview:tempLabel];
[tempLabel release];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString* cellText; // code to load cellText dynamically is snipped off
UIFont *cellFont = [UIFont fontWithName:#"Georgia" size:14.0];
CGSize constraintSize = CGSizeMake(230.0f, MAXFLOAT);
CGSize labelSize = [cellText sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
return labelSize.height + 20;
}
In situations like these I typically pre-calculate the heights of all of my rows, store them in an array, and then just returns those heights in heightForRowAtIndexPath. This way the tableview knows the height of each cell and cells be conform to that height even after reuse. I don't know of a way to force a calculation of the cell height beyond looking for when a cell will be viewable and reloading it, which seems too costly.
Update: some example code:
I have a method called - (void)calculateHeights which does the same calculation you had in heightForRowAtIndexPath, but stores the result in my mutable array heights_ ivar:
- (void)calculateHeights {
[heights_ removeAllObjects]
for (Widget *myWidget in modelWidgetArray) {
NSString* cellText; // code to load cellText dynamically is snipped off
UIFont *cellFont = [UIFont fontWithName:#"Georgia" size:14.0];
CGSize constraintSize = CGSizeMake(230.0f, MAXFLOAT);
CGSize labelSize = [cellText sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
[heights_ addObject:[NSNumber numberWithFloat:labelSize.height + 20.0f]];
}
}
And then in heightForRowAtIndexPath, given a 1-section table view:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [heights_ objectAtIndex:[indexPath row]];
}
If your table view has more than one section you'll need to do some math to convert to the one-dimensional heights_ array and back again. Also, any time you -reloadData you'll need to -calculateHeights as well.
The -tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath method is invoked before the scroll view is composed.
The purpose of calling this method before calling -tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath and not fired during the scrolling is that the table view (Which is inherited from UIScrollView) need to know the whole height of the contentView. Once the table view knows all the height, it cached the height before you call -reloadData
Your problem means you need to clear the content in the cell's -prepareForReuse and call -setNeedLayout to layout all the new contents.
you can use tag for your label to avoid messed up to each other
UILabel *label= (UILabel*)[cell viewWithTag:2];
label.lineBreakMode = UILineBreakModeWordWrap;
label.numberOfLines = 0;

Problems with UITableViewCell heights with a UILabel and random text lengths

I'm currently trying to get a UITableView to use custom cell heights based on text in an array. The problem I'm seeing is that the text seems to squish together at point rather than filling the full length of a custom cell. This results in the text overlapping over cells, sometimes disappearing under them.
Here's the code I have for determining the cell height, I'm not sure of the standard UILabel text height but this seemed to work well at the height I for the font.
if (indexPath.section == 0) {
NSString *text = [[self.recipeDict objectForKey:#"Ingredients"] objectAtIndex:[indexPath row]];
CGSize constraint = CGSizeMake(320 - (10 * 2), 20000.0f);
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:12] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
CGFloat height = size.height;
return height + 20;
}
Also just in case the creation of the cells is either a hinderance to my problem or just helps you know what's going on, here's that too.
UITableViewCell *cell;
UITableViewCell *ingredientCell;
UITableViewCell *methodCell;
if (indexPath.section == 0) {
UILabel *ingredientText;
static NSString *ingredientCellIdentifier = #"Ingredient Cell";
ingredientCell = [tableView dequeueReusableCellWithIdentifier: ingredientCellIdentifier];
if (ingredientCell == nil)
{
ingredientCell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: ingredientCellIdentifier] autorelease];
ingredientText = [[UILabel alloc] initWithFrame:CGRectMake(15, 7, 290, 44)];
[ingredientText setLineBreakMode:UILineBreakModeWordWrap];
[ingredientText setNumberOfLines:0];
[ingredientCell.contentView addSubview:ingredientText];
ingredientText.tag = 1;
[ingredientText release];
ingredientCell.contentMode = UIViewContentModeRedraw;
}
ingredientText = (UILabel*)[ingredientCell viewWithTag:1];
ingredientText.text = [[self.recipeDict objectForKey:#"Ingredients"] objectAtIndex: indexPath.row];
[ingredientText sizeToFit];
return ingredientCell;
}
This is my second attempt at solving this issue but it seems to be beyond my current ability so I welcome wisdom gained through experience.
UPDATE -
After further investigation it seems that the UILabel ingredientText being resized is causing the issue.
It starts out as tall as it needs to be for the text shown, however when that label is redrawn with a larger height for another piece of text which requires more lines it's never shrunk down again. It seems that the sizeToFit method is prioritising using the available height rather than taking up the width and shrinking the height.
Right now I've set the width again after the sizeToFit which works around the issue but it leads to odd spacing depending on the height of the label.
A couple of things:
You have not set the font for the UILabel, which means you're sizing to an unknown height
You need to set your UILabel autoresizing mask, so that it sizes when the cell height changes
Here is working code (tested):
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
NSString *text = [_items objectAtIndex:[indexPath row]];
CGSize constraint = CGSizeMake(320 - (10 * 2), 20000.0f);
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:12] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
CGFloat height = size.height;
return height + 20;
}
return tableView.rowHeight;
}
#pragma mark - UITableViewDatasource
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell;
UITableViewCell *ingredientCell = nil;
UITableViewCell *methodCell;
if (indexPath.section == 0) {
UILabel *ingredientText;
static NSString *ingredientCellIdentifier = #"Ingredient Cell";
ingredientCell = [tableView dequeueReusableCellWithIdentifier: ingredientCellIdentifier];
if (ingredientCell == nil)
{
ingredientCell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: ingredientCellIdentifier] autorelease];
ingredientText = [[UILabel alloc] initWithFrame:ingredientCell.bounds];
ingredientText.font = [UIFont systemFontOfSize:12.0f];
ingredientText.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[ingredientText setLineBreakMode:UILineBreakModeWordWrap];
[ingredientText setNumberOfLines:0];
[ingredientCell.contentView addSubview:ingredientText];
ingredientText.tag = 1;
[ingredientText release];
ingredientCell.contentMode = UIViewContentModeRedraw;
}
ingredientText = (UILabel*)[ingredientCell viewWithTag:1];
ingredientText.text = [_items objectAtIndex: indexPath.row];
return ingredientCell;
}
}
Be sure you are always be returning a values for tableView:cellForRowAtIndexPath: and tableView:heightForRowAtIndexPath: methods
The solution to this issue was that the width specified in the constant in the cell height method didn't match the frame for the cell text specified later in the cellForRowAtIndexPath method.
Once these are the same the problem resolves itself.
The different constant size led to inconsistent and inaccurate UILabel heights being reported. This led to incorrect table row heights being set.