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
Related
I'm working on a custom "Pulse style" UITableView according to this tutorial, and everything is going great. I've made some modifications and extensions but there is one feature I'd like to implement that I need some help with: the color of the horizontal bounce regions.
This is the method that creates my cell with a table view in it:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = [#"TableViewCell" stringByAppendingFormat:#"%i", self.content.indexInArrayOfViews];
UIView *view = [self.content viewAtIndex:indexPath.row];
UITableViewCell *cell = [self.horizontalTableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier] autorelease];
view.center = CGPointMake(view.center.y,view.center.x);
view.contentMode = UIViewContentModeCenter;
view.transform = CGAffineTransformMakeRotation(M_PI_2);
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
[cell addSubview:view];
return cell;
}
I'm aware there is a reuse cell issue, but for right now I want to change this color here [IMG].
I can control the color of the vertical table view bounce areas, but I am having difficulty replicating this success for my horizontal view.
This is how I do it vertically:
CGRect frame = self.tableView.bounds;
frame.origin.y = -frame.size.height;
UIView* topBack = [[UIView alloc] initWithFrame:frame];
topBack.backgroundColor = [self.delegate backgroundColorForTopOfTableView];
[self.tableView addSubview:topBack];
[topBack release];
This was according to this StackOverflow question.
How do I change the color/background of my horizontal table view (which is nested in a table view cell)?
Here is an album with some relevant iPhone screenshots and IB screenshots.
I discovered a solution:
if (indexPath.row == 0){
UIView *bounce = [[UIView alloc] initWithFrame:CGRectMake(-320, 0, 320, 150)];
bounce.backgroundColor = [self.delegate colorForBounceRegionAtRow:self.content.indexInArrayOfViews];
[view addSubview:bounce];
}
if (indexPath.row + 1 == self.content.viewCount){
UIView *bounce = [[UIView alloc] initWithFrame:CGRectMake([self.content widthOfViewAtIndex:self.content.viewCount - 1], 0, 320*2, [self.content greatestHeight])];
bounce.backgroundColor = [self.delegate colorForBounceRegionAtRow:self.content.indexInArrayOfViews];
[view addSubview:bounce];
}
This adds a full screen's worth of colored rectangle to the first and last elements, giving the illusion of a bounce region.
I'm trying to add a dynamic horizontal scroll to a table cell.
I found an example after searching for a while, but since the example project only was the view it was directly connected to the app delegate which contained the majority of the relevant code. Most of it within applicationDidFinishLaunching.
Do any of you know how I am supposed to add this code to my project?
Thanks in advance, Tom
EDIT:
Here's the link to the sample project i downloaded: http://blog.sallarp.com/wp-content/uploads/2008/11/slidemenu.zip
Here's the youtube clip of how it's supposed to work: http://www.youtube.com/watch?v=wFqBNXZ4SHI
EDIT 2 (New code):
"famorables" is declared with 5 one-word-strings
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyFamorablesCell";
UITableViewCell *cell;
//Create Cell
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//Create a UIScroll View
UIScrollView *scrollview = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 80)];
scrollview.contentSize = CGSizeMake(self.view.frame.size.width * 2, 80);
scrollview.showsHorizontalScrollIndicator = NO;
float totalButtonWidth = 0.0f;
famorableArray = self.famorables;
for(int i = 0; i < [self.famorables count]; i++){
UIButton *famorableButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[famorableButton setFrame:CGRectMake(0.0f, 0.0f, 100.0f, 30.0f)];
[famorableButton setTitle:[NSString stringWithFormat:#"%#", [famorableArray objectAtIndex:[indexPath row]]] forState:UIControlStateNormal];
// Move the buttons position in the x-demension (horizontal).
CGRect btnRect = famorableButton.frame;
btnRect.origin.x = totalButtonWidth;
[famorableButton setFrame:btnRect];
// Add the button to the scrollview
[scrollview addSubview:famorableButton];
// Add the width of the button to the total width.
totalButtonWidth += famorableButton.frame.size.width;
}
[cell.contentView addSubview:scrollview];
return cell;
}
Okay Try this
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell;
//Create Cell
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
//Create a UIScroll View
UIScrollView scrollview = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, (it will be the height of the Cell u want))];
scrollview.contentSize = CGSizeMake(self.view.frame.size.width * 2,(it will be the height of the Cell u want));
scrollview.showsHorizontalScrollIndicator = NO;
/*
Now add every this u might want to add in ur Scroll view.
keep that in mind that the height and width of contentSize and ScrollView will be depandent on ur requirement
Make sure u add every thing u want in the Scroll view will be the part of Scroll View
like for label
//Add Something to Scroll View
[scrollView addSubview:label];
*/
//Add Scroll view to Cell
[cell.contentView addSubview:scrollView];
return cell;
}
This will Work
In case of any queries feel free to ask
I'm trying to use my ReusableCell for cells with images in different dimensions. The images are put inside a 220x150 black box with with scaling UIViewContentModeScaleAspectFit.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"NewsTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NewsItem *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.imageUrl]];
[cell.imageView setImage:[[UIImage alloc] initWithData:data]];
[cell.imageView setBackgroundColor:[UIColor blackColor]];
[cell.imageView setContentMode:UIViewContentModeScaleAspectFit];
CGRect imageViewFrame = cell.imageView.frame;
imageViewFrame.size.width = 220;
imageViewFrame.size.height = 150
[cell.imageView setFrame:imageViewFrame];
[cell.textLabel setText:item.title];
return cell;
}
The above code results in a layout like below and the images are sometimes changing when scrolling in the table view.
Instead of this unstructured layout, I would like the images to be aligned like this:
What am I doing wrong with this ReusableCell?
EDIT1:
I'm trying to create an imageView and add this imageView as a superview to cell.contentView.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"NewsTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NewsItem *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
UIImage *placeholderImage = [UIImage imageNamed:#"ImagePlaceholderThumb"]; //220x150
UIImageView *imageView = [[UIImageView alloc] initWithImage:placeholderImage];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.imageUrl]];
[imageView setImage:[[UIImage alloc] initWithData:data]];
[imageView setBackgroundColor:[UIColor blackColor]];
[imageView setContentMode:UIViewContentModeScaleAspectFit];
CGRect imageViewFrame = imageView.frame;
imageViewFrame.size.width = placeholderImage.size.width;
imageViewFrame.size.height = placeholderImage.size.height;
[imageView setFrame:imageViewFrame];
[cell.contentView addSubview:imageView];
[cell.textLabel setText:item.title];
return cell;
}
The above code results in the following:
It is like some of the images are visible in two cells. It seems that they are not keeping the size I've set in the imageViewFrame. Do you know why?
A quick fix would be using content mode UIViewContentModeScaleAspectFill. Images will be stretched in one or both dimensions to fill up the whole image view bounds.
You really need subclassing UITableViewCell to do this right.
Thre is a lazy solution adding a new UIImageView and using a spacer, as Keller told you in his answer (feel free to accept his answer, this is just the missing code).
Extract of tableView:cellForRowAtIndexPath::
...
cell.textLabel.text = [NSString stringWithFormat:#"Cell #%i", indexPath.row];
cell.imageView.image = [UIImage imageNamed:#"spacer.png"]; /* spacer is 64 x 44 */
/* image view width should be ~ 64 px, otherwise it will overlap the text */
UIImageView *iv = [[UIImageView alloc] initWithFrame:(CGRect){.size={64, tableView.rowHeight}}];
switch (indexPath.row) {
case 0:
iv.image = [UIImage imageNamed:#"waterfall.png"];
break;
/* etc... */
}
if (indexPath.row < 3) {
/* add black bg to cell w/ images */
iv.backgroundColor = [UIColor blackColor];
}
iv.contentMode = UIViewContentModeScaleAspectFit;
[cell.contentView addSubview:iv];
...
The table will look like this:
You need to set the placeholder (spacer.png above) in the existing cell image view. It will push the text label to the right.
You can use aspect fill and remove the background color bit:
iv.contentMode = UIViewContentModeScaleAspectFill;
The table will look wrong because the image is drawn outsite the bounds:
Just clip to bounds to get a better result:
iv.clipsToBounds = YES;
Create a UIImageView subview for each cell and it to the contentView. Each UIImageView contains an image with a consistent frame but with option UIViewContentModeScaleAspectFit. Then just Set the background color of the UIImageView to black.
I just confirmed this works, but you need to also create a placeholder spacer image to make sure the textLabel moves out of the way. Just make it the same dimensions of your image (with the letter boxing).
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
//spacer
cell.imageView.image = [UIImage imageNamed:#"placeholder"];
//imageview
UIImageView *thumbnail = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 80, 44)];
thumbnail.tag = kThumbTag;
thumbnail.backgroundColor = [UIColor blackColor];
thumbnail.contentMode = UIViewContentModeScaleAspectFit;
[cell.contentView addSubview:thumbnail];
}
// Configure the cell...
cell.textLabel.text = [NSString stringWithFormat:#"Cell %d", indexPath.row];
cell.imageView.frame = CGRectMake(0, 0, 80, 44);
UIImageView *thumb = (UIImageView*)[cell.contentView viewWithTag:kThumbTag];
if (indexPath.row == 0) {
[thumb setImage:[UIImage imageNamed:#"image1.png"]];
} else {
[thumb setImage:[UIImage imageNamed:#"image2.png"]];
}
return cell;
}
Obviously, this example isn't lazy loading the images (I didn't realize you were loading them from a URL). For that, I would use a subclass with EGOImageView or something of the like.
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;
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.