Trying to render a large columnar grid within a UITableView - ios

I have created a sort of GRID using uitableView. For that I have taken various labels for showing grid type line I am using line image. So by considering Grid my tableview is having around 88 columns.
My issue is when I scroll it down, I am getting jerky effect. Its performance s very poor. I am creating around 108 label and each row is having 88 labels and 86 image views.
What step do I need to follow to improve scrolling performance???
I was using clearColor for label background. But later on I have removed those background colors.

Having read your problem again, I am thinking you are scrolling horizontally and vertically with an incredibly wide tableview. If this is the case then you need to switch to UIScrollView and attach each item to this view. UIScrollView will only load the views that are visible and provide the kind of scroll performance you desire.
It will be important to avoid this:
// Using ARC, therefore no release on the UILabel
- (void) viewDidLoad {
self.scrollView.backgroundColor = [UIColor whiteColor];
UIFont *font = [UIFont systemFontOfSize:24];
CGSize size = [#"10000:10000" sizeWithFont:font];
_scrollView.contentSize = CGSizeMake(10000 * size.width, 10000 * size.height);
for (int y = 0; y < 10000; y++) {
for (int x = 0; x < 10000; x++) {
NSLog(#"Loading: %d, %d", x, y);
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x * size.width, y * size.height, size.width, size.height)];
label.font = font;
label.textAlignment = UITextAlignmentRight;
label.text = [NSString stringWithFormat:#"%d:%d", x, y];
[_scrollView addSubview:label];
}
}
}
While this will eventually load it will take ages while it loads up all of these labels and it will consume a ton of memory. You want to lazy load this view just like a TableView. I will write up an example this evening.

You need to give the table cell a reuse identifier. Otherwise, each time you create a completely new cell and consume more and more memory.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *const kReuseIdentifer = #"ReuseIdentifer";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kReuseIdentifier] autorelease];
}
return cell;
}

Related

Updating height of UITableViewCell

I'm building a table that shows an entry for a user. One of the cells shows a set of "tags" downloaded from the server. I am currently building a set of UILabels and manually adding them to a view contained in the cell. While this works, the cell does not dynamically resize after adding the tags. The tags overlap the cell beneath it and I can't figure out how to manually update the height of the cell.
In cellForRowAtIndexPath:
JournalTagsCell *cell = [tableView dequeueReusableCellWithIdentifier:#"JournalTagsCell" forIndexPath:indexPath];
//Check if we have any tags to show
if(self.journalObject.journalEntryTags != nil){
cell.placeholderLabel.hidden = YES;
cell.tagsView = [self updateTagsView:cell.tagsView];
}
return cell;
The following is my method for actually creating each tag, laying them out and adding them to the view:
- (void)updateTagsView:(UIView*)viewToUpdate{
NSArray *items = self.journalObject.journalEntryTags;
//Clean up the view first
NSArray *viewsToRemove = [viewToUpdate subviews];
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
}
float x = 10;
float y = 10;
for (int i = 0; i < items.count; i++) {
CGRect textRect = [items[i] boundingRectWithSize:CGSizeMake(self.view.frame.size.width - 20, 1000)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont tagCopy]}
context:nil];
CGSize size = textRect.size;
if (x+size.width > (self.view.frame.size.width-20)) {
y += size.height + 10;
x = 10;
}
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(x, y, size.width, size.height)];
lbl.userInteractionEnabled = YES;
[lbl setText:[NSString stringWithFormat:#" %# ", items[i]]];
[lbl setFont:[UIFont tagCopy]];
[lbl sizeToFit];
[lbl setTextColor:[UIColor colorWithRed:0.145 green:0.392 blue:0.576 alpha:1.000]];
[lbl setBackgroundColor:[UIColor colorWithRed:0.804 green:0.871 blue:0.914 alpha:1.000]];
lbl.layer.borderWidth = 1;
lbl.layer.borderColor = [UIColor colorWithRed:0.145 green:0.392 blue:0.576 alpha:1.000].CGColor;
lbl.layer.cornerRadius = 5;
[lbl.layer setMasksToBounds:YES];
[viewToUpdate addSubview:lbl];
UITapGestureRecognizer *tapGesture =
[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(userClickedOnTag:)];
[lbl addGestureRecognizer:tapGesture];
x += size.width + 10;
if (x > (self.view.frame.size.width-20)) {
y += size.height + 10;
x = 10;
}
if (i == items.count-1) {
y+= size.height + 20;
}
}
[viewToUpdate setFrame:CGRectMake(0, 0, self.view.frame.size.width, y)];
}
However, I don't know how to manually update the height for this cell based on the size of this view. I don't want/need to manually calculate the height of every single cell, just this one which is why I'm not currently using heightForRowAtIndexPath but I don't know how to update the height of this one cell. Obviously I can calculate the height I need for this cell if necessary as I'm already setting up the view frame that holds the tags, but short of having to go through every single cell and manually calculate each ones height, I'm stumped.
I think you will have to use heightForRowAtIndexPath. I'm not sure you completely understand what's happening. Or maybe I have it wrong.. Either way, how I understand it: The total height of the "cell" will always be presented. You never set the "height" of the cell, it will automatically show the entire content. heightForRowAtIndexPath is not a way to tell the cell how tall it should be, but rather how much space the tableView should reserve for that particular cell. If you pass a height too short, it will still present the entire cell, but the next cell will start too soon. It also works the other way around, if you pass a bigger number than necessary, it will look like the cells are bigger, even though the cells aren't. It's just the tableView's representation.
If you are using iOS 8 you can use UITableViewAutomaticDimension.You can check out this example
self.tableView.rowHeight = UITableViewAutomaticDimension;
You can take a look also on this video : What's New in Table and Collection Views in the 2014 WWDC.
You can solve the issue with a variable for that row's height. In viewDidLoad() store default cell height to the variable. then while you calculate the height for the view store the view's height to the variable and to reload that cell use below method for the tableview by passing single cell's indexpath in array.
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
It will update cell at particular index by calling all lifecycle method of a cell.
Make sure you return default value for all other cell in heightForRowAtIndexPath() except the cell with tagsView with calculated height cell.
-(CGFloat)tableView:(UITableView *)myTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexpath.row == TAGSVIEW_ROWINDEX)
return calculated_height_value;
return default_row_height;
}
If the Cell with tagsView is not predefined with rowIndex, you can also store that rowIndex in one integer variable in cellForRowAtIndexPath().

Multiple Cells in TableView

I am writing an app for the first time and I need it to pull images from a database and list them for the user to click on. My apologies ahead of time if this is hard to follow. The problem I am having is I need to put multiple images in one cell (Or as mentioned in the title simply have multiple cells per row), and I need each image to direct to its own unique detail page. So two questions: Is it possible to put multiple cells in one row (based off of the screen width)? and Is there an exception that I can write to stop looping when an Array goes out of bounds?
The images I am pulling all have the same dimensions (360x125) so I grab the width and height of the container based off of the orientation and adjust their size according to how many I can fit in a cell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Array the images are stored in.
Companies *item = _feedItems[indexPath.row];
// I grab the screen size and adjust if the screen is in landscape
CGRect result = [UIScreen mainScreen].bounds;
CGFloat width = CGRectGetWidth(result);
CGFloat height = CGRectGetHeight(result);
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
if(UIInterfaceOrientationIsPortrait(interfaceOrientation)){
result.size = CGSizeMake(width, height);
} else if(UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
result.size = CGSizeMake(height, width);
}
// if the width is less than 480 I fit one image and set the cells height,
// else I set the height to that if there were two images.
if(result.size.width <= 480) {
return (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds);
}
if(result.size.width > 480) {
return (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds) / 2;
}
return (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds);
}
If the width is greater than 480 then I put two images instead of one. This may increase to three images per line later, I have not got around to testing this on the iPad yet.
Then I draw the images into the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIndentifier = #"BasicCell";
UITableViewCell *myCell = [tableView dequeueReusableCellWithIdentifier:cellIndentifier];
myCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentifier];
CGRect result = [UIScreen mainScreen].bounds;
CGFloat width = CGRectGetWidth(result);
CGFloat height = CGRectGetHeight(result);
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
if(UIInterfaceOrientationIsPortrait(interfaceOrientation)){
result.size = CGSizeMake(width, height);
} else if(UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
result.size = CGSizeMake(height, width);
}
UIImageView *myImageView;
UIImageView *myImageView2;
if(result.size.width <= 480) {
Companies *item = _feedItems[indexPath.row];
myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,CGRectGetWidth(self.listTableView.bounds), (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds))];
myImageView.tag = 1;
myImageView.image = item.imageName;
[myCell addSubview:myImageView];
}
if(result.size.width > 480) {
Companies *item = _feedItems[indexPath.row * 2];
Companies *item2 = _feedItems[(indexPath.row * 2) + 1];
myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,CGRectGetWidth(self.listTableView.bounds) / 2, (item.imageName.size.height / item.imageName.size.width) / 2 * CGRectGetWidth(self.listTableView.bounds))];
myImageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetWidth(self.listTableView.bounds) / 2,0,CGRectGetWidth(self.listTableView.bounds) / 2, (item.imageName.size.height / item.imageName.size.width) / 2 * CGRectGetWidth(self.listTableView.bounds))];
myImageView.tag = 1;
myImageView2.tag = 2;
myImageView.image = item.imageName;
myImageView2.image = item2.imageName;
[myCell addSubview:myImageView];
[myCell addSubview:myImageView2];
}
return myCell;
}
This is the code I have so far, and it will work normally when the orientation is portrait. However when the orientation is set to landscape I get an expected error of NSRangeException index beyond bounds. Because of this
Companies *item = _feedItems[indexPath.row * 2];
Companies *item2 = _feedItems[(indexPath.row * 2) + 1];
I want to pull both the image and the image that will come after it, to put in the same cell (Sorry if that is confusing). This is why I use y = 2x and y = 2x + 1. The problem is when x exceeds the highest value for y. So if the Array has 8 images [0 - 7], it will loop through the cell draw process 8 times, if I draw two at once it will draw the 8th image on the 3rd loop: 0[0,1] 1[2,3] 2[4,5] 3[6,7] The error will come up on the 4th loop: 4[8,9] index 8 beyond bounds 7. Is there an exception I can write to avoid this? Or am I using an incorrect thought process that should be handled differently?
How can I make it so that once I do put two images in the same row, each one will have their own on-click event handler and redirect to their own unique page (right now the call is on the table cell view, so that when I click either image in the same row, the page will redirect to the same one).
My restrictions are: I need to pull the data as I am from a mysql database through a php page. The number of images that will be pulled will be random. The images are 360 x 125 so I cannot stretch them too much, and thus need to have multiple ones on the same row should the user flip to landscape view or use a larger tablet or phone. Is there a better way to do this?
To prevent out of bounds error, just do a check on item count like this:
if (_feedItems.count < (indexPath.row*2+1)){
Companies *item2 = _feedItems[(indexPath.row * 2) + 1];
// rest of set up for second image
}
For detecting image press events, add a tap gesture recognizer to each image.
myImageView.userInteractionEnabled = YES; // This is important!
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onImageTap:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[tap setDelegate:self];
[myImageView addGestureRecognizer:tap];
In onImageTap method:
-(void)onImageTap:(UITapGestureRecognizer *)recognizer
{
int tag = recognizer.view.tag;
// rest of code based on image tag
}
Once you get this working and are comfortable with table views, you should look into custom table view cells by subclassing UITableViewCell and also understand table view cell reuse.
If you are feeling adventurous, I would recommend checking out UICollectionView as it will let you display two items in a row and treat each item as a different cell. You will have to manage the spacing between cells if you want strictly two items per row.

iOS7 Loading three images in one cell on iPad is lagy, scrolling freez

I have big problem with my iPad application which I am working on. I will explain you what is a problem.
I have list of products. I have table view to show that products. I try to load 3 images (products) in one cell in landscape mode and 2 in portrait mode. For that I use this code in my controller class
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"];
[cell setBackgroundColor:[UIColor clearColor]];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0ul);
dispatch_async(queue, ^{
for(int i = 0; i < self.cols; i++){
int index = (indexPath.row * self.cols) + i;
if([self.productList count] > 0 && index < [self.productList count]){
Product *tmpProduct = nil;
if([self.productList isKindOfClass:[NSMutableSet class]]){
tmpProduct = (Product *)[[NSArray arrayWithArray:[((NSSet *)self.productList) allObjects]] objectAtIndex:index];
}else{
tmpProduct = (Product *)[((NSArray *)self.productList) objectAtIndex:index];
}
int x = i * self.itemWidth + (50 * (i + 1));
int y = 0;
int imageWidth = self.itemWidth;
int imageHeight = self.itemHeight - 45;
UIView *awesomeView = [[UIView alloc] initWithFrame:CGRectMake(x, y, imageWidth, self.itemHeight)];
awesomeView.backgroundColor = [UIColor clearColor];
awesomeView.userInteractionEnabled = YES;
awesomeView.tag = 700;
UIImage *image;
if([tmpProduct getImageFilePath]){
image = [UIImage imageWithContentsOfFile:[tmpProduct getImageFilePath]];
}else{
image = [UIImage imageNamed:#"no-image.png"];
}
dispatch_sync(dispatch_get_main_queue(), ^{
UIImageView *productImage = [[UIImageView alloc] initWithImage: image];
CGRect frameRect = productImage.frame;
frameRect.size.width = imageWidth;
frameRect.size.height = imageHeight;
productImage.frame = frameRect;
productImage.contentMode = UIViewContentModeScaleAspectFill;
productImage.tag = index;
productImage.backgroundColor = [UIColor grayColor];
productImage.contentMode = UIViewContentModeScaleAspectFit;
[awesomeView addSubview:productImage];
[cell.contentView addSubview:awesomeView];
[cell setNeedsLayout];
});
}
}
});
return cell;
}
You can see that when cell want to show and "cellForRowAtIndexPath" method is called I initiate background queue. Start "for" for 3 or 2 images in one cell. Get product for that cell based on row. Calculating image width and height and position. Initiate one helper view. Than load image. If image exists in product object I load product image if not I loaddefault image (no-image). Place that image view in my helper view. After that I load my helper view to cell in main queue. (I use that helper view "awesomeView" because I have more thing to display, some labels, i removed that to simplified this question).
Here is youtube video which I have recorded to see which problem i have. You can see that when I scroll up or down it is very lagy. It freeze for part of second every time when script load next cell. YOUTUBE Video: http://youtu.be/hE3KI0SVrPk
Can some one help me I do not know where I am wrong.
I tried couple of combinations with queues and also I tried without queues and result is almost the same.
Thanks in advanced.
The proper way to do this is as follows:
create some mutable collection to hold images
when you find you need an image, and its not in the collection, do (somewhat) as you are doing - get the image in the background
keep an index or some id of what you are currently fetching, so you don't do it more than once.
when you get the image, then dispatch_async to a method in your view controller
the receiving method receives the image, and examines all the visible cells in the table (there's a method for that), to see if the image applies to any of them. If so it updates that cell. If not it puts the image into the mutable collection.
In my case I had a known productID I could use as a key to use with a mutable dictionary. I would when first fetching an image add an entry of [NSNull null] for the object of the key - that made it easy to test for whether or not I already was fetching the image. Once I got the image, the image replaced the nsnull placeholder.

UICollectionView cell borders in a flow layout

I'm using UICollectionView to lay out a bunch of cells that are sectioned by first letter of their title. Each cell should have a very thin border around it, and the section headers should have borders above and below. Here's my current prototype:
I achieve the current appearance with the following rules:
Stroke the right and bottom edge of each cell.
Stroke the bottom edge of each section heading.
This is very close to what I want, but there are two defects:
If the line before a section heading isn't full, then the border along the top of the heading stops short of the right edge of the screen.
It's not visible in this screenshot, but if a line is full, the right border of the last cell in the line is still drawn, which looks a little odd against the edge of the screen.
My best idea to fix this is to somehow tell each cell if it's in the last row of a section or the last cell in a row; then the cell would turn off the offending borders, section headings would draw a top border as well as a bottom, and everything would be hunky-dory. I don't know how to achieve that, though.
Any thoughts on how to manage that, or another way to get the look I'm going for? I'm currently using a UICollectionViewFlowLayout.
I ended up subclassing UICollectionViewFlowLayout and applying several heuristics after the flow layout had calculated the attributes for each cell:
If center.y is equal to center.y of the last item in the section, the cell is in the last row of the section.
If CGRectGetMaxY(frame) is equal to CGRectGetMaxY(self.collectionView.bounds), then the cell is agains the right edge of the collection view.
I then stored the results of these calculations in a subclass of UICollectionViewLayoutAttributes, and wrote a UICollectionViewCell subclass whose -applyLayoutAttributes: method would adjust the borders its background view draws based on the additional properties.
I've put the whole mess into a fairly enormous gist so you can see exactly what I did. Happy hacking.
My best idea to fix this is to somehow tell each cell if it's in the last row of a section or the last cell in a row; then the cell would turn off the offending borders, section headings would draw a top border as well as a bottom, and everything would be hunky-dory. I don't know how to achieve that, though.
What you describe is more or less what I did in a similar scenario. I added a border property to my cell:
typedef NS_OPTIONS(NSInteger, TLGridBorder) {
TLGridBorderNone = 0,
TLGridBorderTop = 1 << 0,
TLGridBorderRight = 1 << 1,
TLGridBorderBottom = 1 << 2,
TLGridBorderLeft = 1 << 3,
TLGridBorderAll = TLGridBorderTop | TLGridBorderRight | TLGridBorderBottom | TLGridBorderLeft,
};
#interface TLGridCellView : UIView
#property (nonatomic) TLGridBorder border;
#end
Then I set the border in my view controller's cell configuration:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
TLGridCellView *cell = ...;
if (indexPath.item == self collectionView:collectionView numberOfItemsInSection:indexPath.section - 1) {
cell.border = TLGridBorderLeft;
} else {
cell.border = TLGridBorderLeft | TLGridBorderRight;
}
return cell;
}
I solve this problem in a simple way. I didn't add boarder to cell, instead I add a label with boarder into the cell. For the first column, the frame of the label is the same with the cell. For the other label, I set the x coordinate -0.5 to make their boarder overlap. Hope it helps.
Here is the code:
- (UICollectionViewCell *) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
// Then use it
UILabel *label = nil;
if (cell.contentView.subviews.count > 0) {
label = cell.contentView.subviews[0];
} else {
label = [[UILabel alloc] init];
}
label.text = #"北京";
[label setTextAlignment:NSTextAlignmentCenter];
[label setFont:[UIFont systemFontOfSize:14]];
[label setCenter:cell.contentView.center];
CGRect frame = label.frame;
if (indexPath.row%4 == 0) {
frame.origin.x = 0;
} else {
frame.origin.x = -0.5;
}
frame.origin.y = 0;
frame.size.width = self.collectionView.frame.size.width / 4;
frame.size.height = self.collectionView.frame.size.height / 9;
[label setFrame:frame];
if (cell.contentView.subviews.count == 0) {
[[cell contentView] addSubview:label];
}
label.layer.borderWidth = 0.5;
label.layer.borderColor = [[UIColor lightGrayColor] CGColor];
cell.backgroundColor = [UIColor whiteColor];
return cell;
}

UITableView has an "ugly" background image for dynamic height rows

I've created a UITableViewController which calculates the height for each row depending on the amount of text it contains. The code to calculate the row height looks like this:
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), 20000.0f);
Message *message = [[[[SessionData sharedSessionData] sessions] objectAtIndex:[indexPath section]] objectAtIndex:[indexPath row]];
CGSize size = [[message text] sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
return size.height + (CELL_CONTENT_MARGIN * 2) + 38;
}
As you might notice, I use a separate class as UITableViewDatasource called SessionData. Inside the SessionData class I draw the rows. Each row has a top image, center image (which is repeated depending on the row height) and bottom image. My problem is the following: the center image is drawn a bit darker then the bottom and top images. My guess is this has something to do with the repeating nature of the image, as the top and bottom images are drawn fine. The following code is part of my [tableView:cellForRowAtIndexPath:] message:
- (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] autorelease];
}
// other stuff here, most likely not related to my issue ...
Message *message = [[sessions objectAtIndex:[indexPath section]] objectAtIndex:[indexPath row]];
CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), 20000.0f);
CGSize size = [[message text] sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
[label setText:[message text]];
[label setFrame:CGRectMake(CELL_CONTENT_MARGIN, CELL_CONTENT_MARGIN - 5.0f, CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), size.height)];
[label setBackgroundColor:[UIColor clearColor]];
viewTop = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"im1 top.png"]];
viewMid = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"im1 mid.png"]];
viewBot = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"im1 bot.png"]];
[viewTop setFrame:CGRectMake(0.0, 0.0, 300.0, 17.0)];
[viewMid setFrame:CGRectMake(0.0, 17.0, 300.0, size.height - 10)];
[viewBot setFrame:CGRectMake(0.0, 17.0 + size.height - 10, 300.0, 31.0)];
[viewTop setBackgroundColor:[UIColor clearColor]];
[viewMid setBackgroundColor:[UIColor clearColor]];
[viewBot setBackgroundColor:[UIColor clearColor]];
[cell.backgroundView setBackgroundColor:[UIColor clearColor]];
[((UIImageView *)cell.backgroundView) addSubview:viewTop];
[((UIImageView *)cell.backgroundView) addSubview:viewMid];
[((UIImageView *)cell.backgroundView) addSubview:viewBot];
[[cell backgroundView] addSubview:label];
return cell;
}
My guess is I would need to create a containerView with the total size (width, height) for the top, center and bottom imageViews and add the containerView to the cell, but this didn't seem to work. Anyone have any idea on how to fix this problem?
PLEASE NOTE: My UITableViewController has a gradient as background image. So the top / center / bottom images are drawn on top of the background gradient. Perhaps this is part of the problem as well.
A few things to note here.
Firstly, there's a lot of processing going on in your cells which will cause very poor scrolling performance on the device. You should make better use of requeuing cells and performing most of the cell setup that's common to all the cells here:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// Do cell layout in here!
}
That way cell's and their subviews can be reused instead of recreated every time a cell appears on screen (use the tag properties and viewWithTag: methods of views).
Secondly, you may wish to look at stretchable images.
Thirdly, you will notice much better performance by manually drawing cells. Create a custom view (which you add to the content view), override drawRect:, draw images onto them using UIImage's drawInRect: method. This will speed things up significantly! Draw everything you can because subview laying out is expensive! Also, ensure you use no UIView (inc. UIImageView, UILabel, etc...) transparency if possible, set opaque = YES and set background colors. It is preferable to draw text in drawRect:, using NSString's drawInRect: method which will give the look of a transparent UILabel.
However, if you have VERY few cells, like < 4 or something, then perhaps the drawing method is too much. However, it is still very much advised!
Hope this helps!
Thanks for your suggestions Michael. I could indeed fix my graphic issues by using the following code:
[bubbleView setImage:[bubble stretchableImageWithLeftCapWidth:165 topCapHeight:30]];
Instead of using 3 separate images to create chat bubbles I can now use 1 image and it stretches gracefully. A very nice suggestion indeed :)
Even though I didn't seem to have much speed issues, I did rewrite my code partially as you suggested, mainly by constructing the main part of the cells in the cell initializer part of the code. I did notice a minor speed improvement, perhaps it's more noticeable on a device (as compared to the simulator).
Currently I still draw my images using ImageView (as you might notice), I'll look at [UIImage drawInRect:] whenever I need more speed in rendering.
Thanks for your suggestions, much appreciated.

Resources