"reset" cells after changing orientation - iphone

I added the interfaceOrientation to my app. It works fine concerning the views. Some of the table-cells I defined by CGRects to position the text in the cell. In portrait-mode the cell is 300px long, in landscape-mode 420px. I use the following code to change the CGRects depending the orientation:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.interfaceOrientation == UIDeviceOrientationPortrait) {
NSString *currentLanguage = [[NSString alloc] initWithContentsOfFile:[NSHomeDirectory() stringByAppendingPathComponent:#"/Documents/sprache.txt"]];
static NSString *TableViewTableCellIdentifier = #"TableViewTableCellIdentifier";
UITableViewCell *cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:TableViewTableCellIdentifier];
CGRect cellRect = CGRectMake(0, 0, 300, 175);
cell.backgroundColor = [UIColor darkGrayColor];
cell = [[[UITableViewCell alloc] initWithFrame:cellRect reuseIdentifier:TableViewTableCellIdentifier] autorelease];
CGRect keyLabelRect = CGRectMake(0, 5, 5, 20);
UILabel *keyLabel = [[UILabel alloc]initWithFrame:keyLabelRect];
keyLabel.tag = 100; //.........
} else {
NSString *currentLanguage =
[[NSString alloc] initWithContentsOfFile:[NSHomeDirectory() stringByAppendingPathComponent:#"/Documents/sprache.txt"]];
static NSString *TableViewTableCellIdentifier = #"TableViewTableCellIdentifier";
UITableViewCell *cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:TableViewTableCellIdentifier];
CGRect cellRect = CGRectMake(0, 0, 450, 175);
cell.backgroundColor = [UIColor darkGrayColor];
cell = [[[UITableViewCell alloc] initWithFrame:cellRect reuseIdentifier:TableViewTableCellIdentifier] autorelease];
CGRect keyLabelRect = CGRectMake(0, 5, 5, 20);
UILabel *keyLabel = [[UILabel alloc] //.....
}
}
My problem is, when the table is visible and the orientation is changed, I need to scroll to see the new "layout". How can I manage to "reload" the view after changing the orientation?

First of all, this will leak like mad. You create cells and dequeue them but you never release them. You just create an entirely new cell every time a cell is requested. There is no reason to recreate a cell if you dequeue and vice versa. More importantly, your creating as many cells as you have rows in your logical table. That will eat all your memory very quickly.
When you dequeue a cell, you need to check if it's frame is the right size and reuse it if it is. If it is not, then you need to release the cell and create another one.
Second, a table will not ask for new cells until you scroll the existing cells off the screen. This is why your cells do not change until you scroll. It's the expected behavior of a tableview.
My standing recommendation for any moderately complex view is to use different view-controller/view pairs for each orientation. It seems like more work but usually I find it it actually takes less and it's easier to manage. In this case, having two separate tables will probably save you a lot of grief.

I'm pretty sure that you do not need to specify frame during default UITableViewCell construction. Usually frame size is handled by UITableView itself.
But if you wish you can send setNeedsLayout and/or setNeedsDisplay message to a tableView to force it update cell layout (in a orientation handler).

Related

iPhone - Create custom UITableViewCell top and bottom borders

I have been looking everywhere and have not quite found my answer.
I populating a UITableView with dynamic cells from JSON and I am trying to hide any extra cells. I turned off the separators in IB, and of course all the cell separators disappear. How do I add a line to the bottom and top of each tableviewcell so that only the cells that have information show a border? I have imported Quartz and have been playing with CALayer but can't find a solution.
I found a similar question here, but the only answer was not very helpful.
What would be a better, different way of doing this?
Here are my cellForRowAtIndexPath and my numberOfRowsInSection:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
//set equal to the information in the array
return [_jsonDataArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//create Dictionary of data in row
NSDictionary *jsoninfo = [_jsonDataArray objectAtIndex:indexPath.row];
//get required keys from dictionary and assign to vairables
NSString *title = [jsoninfo objectForKey:#"title"];
NSString *subtitle = [jsoninfo objectForKey:#"subtitle"];
NSURL *imageURL = [NSURL URLWithString:[jsoninfo objectForKey:#"series_image_URL"]];
//download the images.
NSData *imgData = [NSData dataWithContentsOfURL:imageURL];
UIImage *img = [[UIImage alloc] initWithData:imgData];
//set boarder for custom cells... I need to have a border on the top and bottom of the cells I am creating so xcode does not autofill the empty space.
//fill in text to cells
cell.textLabel.text = title;
cell.detailTextLabel.text = subtitle;
cell.imageView.image = img;
return cell;
}
I also think it's not the best idea, but if you really want to do this, here's code that will achieve what you want:
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
// Draw top border only on first cell
if (indexPath.row == 0) {
UIView *topLineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 1)];
topLineView.backgroundColor = [UIColor grayColor];
[cell.contentView addSubview:topLineView];
}
UIView *bottomLineView = [[UIView alloc] initWithFrame:CGRectMake(0, cell.bounds.size.height, self.view.bounds.size.width, 1)];
bottomLineView.backgroundColor = [UIColor grayColor];
[cell.contentView addSubview:bottomLineView];
Put this code in the tableView:cellForRowAtIndexPath: method. The final look of your UITableView will be like this:
Take into account that this is not very good for performance, especially if you have a lot of cells. If you have a bigger amount of data, refer to this SO question for help on how to optimize the drawing.
Only try this at tableView:cellForRowAtIndexPath: method
[cell.contentView.layer setBorderColor:[UIColor grayColor].CGColor];
[cell.contentView.layer setBorderWidth:1.0f];
It sounds like a sub-optimal solution to try to "distinguish" your valid cells from empty ones with lines. A superior approach would be to clean up the data source before populating the table with it.
This is not the answer to the question, but the clean solution for the original problem.
Add an empty UIView as the footer of the UITableView. Then the empty cells will be hidden. See this answer.
Use Custom Cells. Your Datasoure (Models) should drive the information into the Custom Cells. Create a setter within the Custom Cell class that can be set at each row. as in....
Allocation your Custom Cell with reuse Id,
Pass the property that is determing if the line should show:
[cell setCustomLines:Model.property];
return the cell;
You will have far more flexibility to design the CustomCell any way you want, Images, Lines, Colors, or other ways of letting your user's see a difference among your cells.
Technically, Marco's Answer will work, and good job on a simple solution. But you will not be able to expand this very much farther this this.

iPhone - Objective C - UIScrollView inside UITableView rendering funny

I'm currently working on an iPhone app that's doing some strange things with a UIScrollView inside a UITableView. This is my first foray into iPhone dev, so I'm sure it's something silly I'm missing.
In each UITableViewCell I am putting in a basic UITableViewCell. In that UITableViewCell is a Label and a UIScrollView.
The label and scrollview is setup and working properly, but when it first displays it is offset about 30 pixels down on the y axis than it should be, or is positioned by the XIB/NIB. I am not moving it around manually. The label shows up in the right spot. at 0,0. The UIScrollView should be showing up at 0,22 but is showing up closer to 0,40.
When I swipe to scroll the containing UITableView, then all the UIScrollViews will show up in the right spot assuming that when the UITableView scrolled that UITableViewCell went offscreen.
Here is the code for the UITableView.cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"GalleryRowCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
[cell.layer setMasksToBounds:TRUE];
[cell.layer setCornerRadius:10.0];
Contagion *c = (Contagion *)[self.dataSet objectAtIndex:indexPath.row];
GalleryRowViewController *subView = [[GalleryRowViewController alloc] initWithContagion:c];
[cell.contentView addSubview:subView.view];
subView.contagionName.text = c.name;
subView.contagion = c;
return cell;
}
Here is the code for my GalleryRowViewController.viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
self.imageScroll.delegate = self;
[self.imageScroll setBackgroundColor:[UIColor blackColor]];
[self.imageScroll setCanCancelContentTouches:NO];
self.imageScroll.indicatorStyle = UIScrollViewIndicatorStyleWhite;
self.imageScroll.clipsToBounds = NO;
self.imageScroll.scrollEnabled = YES;
self.imageScroll.pagingEnabled = NO;
NSInteger x = 0;
CGFloat xPos = 0;
for (x=0;x<=10;x++) {
UIImage *image = [UIImage imageNamed:#"57-icon.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[imageView setBackgroundColor:[UIColor yellowColor]];
CGRect rect = imageView.frame;
rect.size.height = 70;
rect.size.width = image.size.width;
rect.origin.x = xPos;
rect.origin.y = 5;
imageView.frame = rect;
[self.imageScroll addSubview:imageView];
xPos += imageView.frame.size.width+5;
}
[self.imageScroll setContentSize:CGSizeMake(xPos, [self.imageScroll bounds].size.height)];
}
--- EDIT FOR IMAGES ---
After App Loads: http://img809.imageshack.us/img809/4576/screenshot20110927at427.png
After Scrolling the rows offscreen and back: http://img690.imageshack.us/img690/9461/screenshot20110927at428.png
Well, as my previous response was at too low a level, let me take another shot at it.
First, I just noticed the core problem that you're using a viewcontroller for each cell. To quote Apple, " "A single view controller typically manages the views associated with a single screen’s worth of content." That would also get rid of your XIB (just manually configuring your scrollview), which I bet will get rid of your problem.
To proceed, your main choice is whether to create a ContagionTableViewCell class or not as suggested by Scott.
If so, following the Elements example, create a subclass of UITableViewCell ContagionTableViewCell with properties of a scrollView, a labelview and a contagion. Like they use a custom setter for the element, use one for the contagion, so that whenever it is assigned, it also updates the cells label (and associated pictures).
Move your imageScroll code from GalleryRowViewController.viewDidLoad into the ContagionTableViewCell init code. Put the image code into a new routine, which will be called from the contagion setter.
If NOT, then move the GalleryRowView Controller code into your UITableView. I suggest you take a look at cellForRowAtIndexPath in Apple's tableViewSuite, the fourth example on subviews. In particular, it shows this pattern of separating the creation of a cell (when you need a brand new one) vs configuring the cell (when reusing it). As you have 10 imageViews inside your scrollView, you'll have to decide whether to delete all those (and/or the scrollview), or just reach inside and update their images when a cell is reused.
Can you post a screenshot. Its a bit hard to visualize what you are describing. I'm not sure how you are computing y origin to be 22.
As a side note I believe its cleaner to do this by creatint your own TableViewCell subclass and use that instead of the default UITableViewCell. There is an example called Elements which shows how to do this properly: http://developer.apple.com/library/ios/#samplecode/TheElements/Introduction/Intro.html
Well, I don't know it's the cause of your problem, but you've definitely got an issue. Note that every time you are asked for a cell, you're adding the galleryRow subview. When a cell goes off-screen, it's put on the reusableCell queue. Then you're asked for another cell; you get it from the queue, it still has the old galleryRow subview, and now you add another one; so that's not good. You should either reuse or delete the old one.
Finally, why are you using UITableViewCellStyleSubtitle, and then not using any of the default fields in that UITableView?

Trying to calculate frame size of UILabel based on the amount of text for subview of cell

The code below has code that determines the frame size of a UILabel, and I think it does work, however when I place it within my rowAtIndexPath for a UItable I get wonky results.
Perhaps, I dont fully understand how or what the reuseIdentifier does, but I placed the code to calculate the frame only when the cell is nil. What happens is that the heights are calculated only for the first three cells, then it repeats in sequence for the rest of the cells. For example, cell one's height is used for cell four's height.
Maybe someone can point me in the right direction as to how I should setup my calculations.
Thanks!
if(cell == nil){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:DisclosureButtonCellIdentifier] autorelease];
//start adding custom subviews to the table cell
//addSubview for Description
UILabel *descValue = [[UILabel alloc] init];
NSString *descString = rowData.summary;
CGSize maximumSize = CGSizeMake(185, 130);
UIFont *descFont = [UIFont fontWithName:#"HelveticaNeue" size:12];
CGSize descStringSize = [descString sizeWithFont:descFont
constrainedToSize:maximumSize
lineBreakMode:descValue.lineBreakMode];
CGRect descFrame = CGRectMake(125, 60, 185, descStringSize.height);
descValue.frame = descFrame;
descValue.backgroundColor = [UIColor redColor];
descValue.font = descFont;
descValue.tag = kDescriptionValueTag;
descValue.lineBreakMode = UILineBreakModeWordWrap;
descValue.numberOfLines = 0;
[cell.contentView addSubview:descValue];
[descValue release];
}
UILabel *desc = (UILabel *)[cell.contentView viewWithTag:kDescriptionValueTag];
desc.text = rowData.summary;
Using NSString UIKit Additions, you can get the width of a string using a particular font:
[myString sizeWithFont:myFont];
There are several other functions in that Additions set that can help figure out how big a piece of text will be, with different layout options, for single and multi-line text, etc.
The purpose of the reuseIdentifier is to let you reuse a cell -- with particular subviews in particular places -- without the device having to spend the execution time to do all that layout. Definitely more useful back in the iPhone 1 days when the processor was much slower. Until you are more familiar with reuseIdentifiers, I would suggest you just create a new cell every time that function is called.
Each time the OS calls your cellForRowAtIndexPath, you need to fill out the content correctly. Anything that needs to get resized or changed depending on the row should be set.
Frankly, I did not try understanding your code completely. That is nearly impossible especially because you close the method with brackets without returning anything but cellForRowAtIndexPath which I assume you are referring to, requires the return of a UITableViewCell object.
Apparently we are looking only at some fraction of the code.
However, layouting a cell properly is not the full task. You need to tell the table the height of each cell.
You may want to implement
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
for that task. That method is good if the cell height varies.
If the cell hight is different from the standard but does not vary from cell to cell then setting of the rowHeight property of the tableView will be sufficient.
I think that in your case the implementation of the method heightForRowAtIndexPath is mandatory.
BTW, you may want to look at subclassing the UITableCellView and implement the layoutSubviews method in your subclass.
You didn't show all of your code but usually to implement tableView:cellForRowAtIndexPath: you start with the following:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// get a cell from the queue of reusable cells, if any
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: DisclosureButtonCellIdentifier];
if (cell == nil) {
// No cell to reuse, need to create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:DisclosureButtonCellIdentifier] autorelease];
// add cell subviews here
}
// fill in cell content and resize subviews here
...
return cell;
}
Once you have created 3 (in your case) cells they get reused as the table scrolls so you don't have to keep creating cells. The call to dequeueReusableCellWithIdentifier will return one of the previously created cells if it was not longer in use (it scrolled off the top or the bottom).

How to increase the speed of the scroll in the table view when images are being loaded in each cell?

Check out the last lines before [return cell]..After the images are being loaded the scrolling speed is decreasing..it seems the scroll gets stuck
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
NSString *itemDescription=[[stories objectAtIndex: storyIndex]
objectForKey:#"title"];
CGRect aframe = CGRectMake(80, 30, 250, 40);
textLabel = [[UILabel alloc] initWithFrame:aframe];
textLabel.font = [UIFont boldSystemFontOfSize:14];
textLabel.numberOfLines=0;
textLabel.textColor = [UIColor darkTextColor];
textLabel.backgroundColor = [UIColor whiteColor];
[cell.contentView addSubview:textLabel];
textLabel.text=itemDescription;
CGRect frame = CGRectMake(0, 0, 70,80);
UIImageView *TopImageView = [[UIImageView alloc] init];
[cell.contentView addSubview:TopImageView];
TopImageView.frame=frame;
m_strImage = [m_imglinkArray objectAtIndex:storyIndex];
TopImage = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:m_strImage]]];
TopImageView.image=TopImage;
return cell;
}
Could you guys help me to increase the speed of the scroll?
Regards
Arun
I believe you may have two problems here. First is the issue of not multi-threading the image loads and second is how you're using UITableViewCell.
iPhone calls cellForRowAtIndexPath whenever a cell appears on the screen - if you scroll down, everything above is unloaded and reloaded when you scroll back up again. As already noted this can be solved with multithreading patterns - either the link posted to markj.net, and/or by using NSThread. The advantage of NSThread is you have more flexibility to handle problems or load more than just image data. (I would use both)
In order to do this efficiently you need to have a custom UITableViewCell. This is good to have regardless because you can make the table cell responsible for it's content rather than your view controller, and you can efficiently manipulate cell formatting and content. Here is a link to a great post about UITableCellView: http://blog.atrexis.com/index.cfm/2009/1/6/iPhone--Customize-the-UITableCellView
When you implement UITableViewCell, do your best to put all addSubView and formatting calls in the "if(cell==nil)" block, only if you can't put them in your custom initWithFrame. You'll find the iPhone is stacking every subview you add -- it's not clearing the old view. You can see this at work by setting all the background colors to "UIColor clearColor" and then change text between hits -- you'll see a stack of text on top of each other. This isn't normally visible because solid-filled backgrounds draw over all the "old" subviews.
Before you combine these two methods, considering implementing a "model". You've got a view and a controller but all of your data should be in a model. All of the image URLs, content, etc. should be in something like an NSMutableArray, a custom object of your own, or maybe via SQLite. Once you have a model, you can implement your caching of the images. With caching, all of the images will be retained between loads of your application which will save batteries, bandwidth, and time.
I would:
1. put all your data in a model of some kind (NSMutableArray, SQLLite, something)
2. Implement your own custom UITableViewCell
3. Make sure you have 0 addSubView, init, or alloc calls inside the active block of cellForRowAtIndexPath
4. Use delegation example or NSThread to load all images in the background
5. Add local image caching
There are some good examples out in about and on the Apple forums on caching images.
nessence has a lot of good information. A few more thoughts:
You're leaking like crazy here. Every cell you create, you leak 2 UILabels, a UIImageView and a UIImage.
As noted before, not only are you leaking these, they're accumulating in your view because you're sticking one on top of the other with addSubview:.
Reaching out to the network during a cell draw is incredibly slow and is blocking your UI. If these URLs are local, then you can use UIImage's +imageWithContentsOfFile, if not, you need to load this in the background.
I don't think you need a thread here. NSURLConnection is an excellent way to load data in the background without incurring the overhead of threading.
nessence is completely correct that you need a model class for Story.
Your basic approach to reusable cell configuration is incorrect. You don't fetch a reusable cell and then add subviews to it. All your subviews should be added in the if() block to create the cell. Then, in each pass, you just change the values of things. I've rewritten some of your code below to demonstrate. This is still not correct code, because it is reaching out to the network during a cell draw, and this may be too many elements to be in a subview-cell (rather than a custom cell), but it's closer to the right idea. I don't even know if this compiles; I just typed it here.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
// Here we do all our cell creation
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
// Make the label
CGRect aframe = CGRectMake(80, 30, 250, 40);
UILabel *textLabel = [[[UILabel alloc] initWithFrame:aframe] autorelease]; // Note the -autorelease so we don't leak
textLabel.font = [UIFont boldSystemFontOfSize:14];
textLabel.numberOfLines=0;
textLabel.textColor = [UIColor darkTextColor];
textLabel.backgroundColor = [UIColor whiteColor];
textLabel.tag = DescriptionTag; // A tag so we can find it later (you'll need a constant for this)
[cell.contentView addSubview:textLabel];
// And the second label
aframe = CGRectMake(80, 30, 250, 40);
textLabel = [[[UILabel alloc] initWithFrame:aframe] autorelease];
textLabel.font = [UIFont boldSystemFontOfSize:14];
textLabel.numberOfLines=0;
textLabel.textColor = [UIColor darkTextColor];
textLabel.backgroundColor = [UIColor whiteColor];
textLabel.tag = TitleTag;
[cell.contentView addSubview:textLabel];
// The image view
CGRect frame = CGRectMake(0, 0, 70,80);
UIImageView *topImageView = [[[UIImageView alloc] init] autorelease];
topImageView.frame = frame;
topImageView.tag = TopImageTag;
[cell.contentView addSubview:topImageView];
}
// all the above was cell creation; we do that as seldom as possible.
// Now we do cell configuration. We want this to be fast.
UILabel *descriptionLabel = (UILabel*)[cell.contentView viewWithTag:DescriptionTag];
descriptionLabel.text = itemDescription;
UILabel *titleLabel = (UILabel*)[cell.contentView viewWithTag:TitleTag];
titleLabel.text =[[stories objectAtIndex:indexPath.row] objectForKey:#"title"];
NSString *imageURLString = [m_imglinkArray objectAtIndex:storyIndex]; // You should have a model class called Story, not two arrays.
UIImage *image = [[[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURLString]]] autorelease]; // This is still way too slow if it's a remote URL
UIImageView *imageView = (UIImageView*)[cell.contentView viewWithTag:TopImageTag];
imageView.image = image;
return cell;
}
I recommend you spend some quality time studying TableViewSuite, Practical Memory Management, and Coding Guidelines for Cocoa. Some time studying the basics of Cocoa would be useful as well, because the coding style here indicates that you may not have a solid foundation. Though it's a Mac book, I still recommend Cocoa Programming for Mac OS X. If you're interested in using this to learn iPhone, I've put together a syllabus that might help. I haven't reviewed it yet, but Stanford's online CS193P course looks promising.
this has been asked several times here, what you need to do is to load asynchronously the images for each cell in order to prevent the scroll to slow down.
This is called "Lazy image loading" and I'm referencing the apple tutorial here :
Lazy load images in UITableView
It seems that you are loading from a url every time the table view asks to get the cell for a row. This is happening in the main application thread (the User interface thread) and it blocks the user interface, making it not responsive.
Especially if this url is a resource that you load from the internet then this pause is big.
I would recommend that you should load the images in a background thread and display a placeholder image until they are fetched from the network.
You can try the solution provided in the following url...
http://www.markj.net/iphone-asynchronous-table-image/

After moving a UITableView row, then selecting it, it only turns blue at the edges

I have a UITableView with reorderable rows and I'm using the standard UITableViewCell.text property to display text. When I tap Edit, move a row, tap Done, then tap the row, the built-in UILabel turns completely white (text and background) and opaque, and the blue shade to the cell doesn't show behind it. What gives? Is there something I should be doing that I'm not? I have a hacky fix, but I want the real McCoy.
Here is how to reproduce it:
Starting with the standard "Navigation-Based Application" template in the iPhone OS 2.2.1 SDK:
Open RootViewController.m
Uncomment viewDidLoad, and enable the Edit button:
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
Specify that the table has a few cells:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 4;
}
In tableView:cellForRowAtIndexPath:, add a line to set the text property of a cell, and therefore to use the built-in UILabel subview:
// Set up the cell...
cell.text = #"Test";
To enable reordering, uncomment tableView:moveRowAtIndexPath:toIndexPath:. The default implementation is blank, which is fine in this case since the template doesn't include a data model.
Configure the project for the Simulator, OS 2.2.1, Build and Go. When the app comes up, tap Edit, then slide any row to a new position, tap Done, and then tap each row one at a time. Usually a tap will select a row, turn it blue, and turn its text white. But a tap on the row that you just moved does that and leaves the UILabel's background color as white. The result is a confusing white open space with blue strips on the edges. Oddly enough, after the first bogus tap, another tap appears to correct the problem.
So far I have found a hack that fixes it, but I'm not happy with it. It works by ensuring that the built-in UILabel is non-opaque and that it has no background color, immediately upon selection.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// hacky bugfix: when a row is reordered and then selected, the UILabel displays all crappy
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
for (UIView *view in cell.contentView.subviews) {
if ([[view class] isSubclassOfClass:[UILabel class]]) {
((UILabel *) view).backgroundColor = nil;
view.opaque = NO;
}
}
// regular stuff: only flash the selection, don't leave it blue forever
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
This appears to work, but I don't expect it to be a good idea forever. What is the Right Way to fix this?
This looks like a bug in UITableView's rendering, and you should file a Radar bug report on it. It's like the cells don't get refreshed properly after the move.
One way to work around this for now is to not use the built-in label, but roll your own in the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
CGRect frame = cell.contentView.bounds;
frame.origin.x = frame.origin.x + 10.0f;
UILabel *textLabel = [[UILabel alloc] initWithFrame:frame];
[textLabel setAutoresizingMask:UIViewAutoresizingFlexibleRightMargin];
textLabel.tag = 1;
textLabel.textAlignment = UITextAlignmentLeft;
textLabel.backgroundColor = [UIColor clearColor];
textLabel.textColor = [UIColor blackColor];
textLabel.font = [UIFont boldSystemFontOfSize:20.0];
textLabel.numberOfLines = 1;
textLabel.highlightedTextColor = [UIColor whiteColor];
[cell.contentView addSubview:textLabel];
[textLabel release];
}
UILabel *textLabel = (UILabel *)[cell viewWithTag:1];
textLabel.text = #"Test";
return cell;
}
I tried this, and it doesn't exhibit the same sort of white blank rectangle you see with the built-in label. However, adding another non-opaque view to the table cell might not be the best for overall rendering performance.
I don't know how major of a glitch this is, because Apple doesn't want you to persist a selection highlight on a table row (they've been enforcing this lately during the review process). You're supposed to place a checkmark or move on to the next level in the navigation hierarchy with a selection, at which point this white box would only be on the screen for a fraction of a second.
The trick in the solution from Brad appears to be:
textLabel.backgroundColor = [UIColor clearColor];
If you leave the background as the default you still get the problem even when you roll your own cells UITableViewCells.
The reason I left it as the default is because the documentation says it is less computationally costly to use opaque backgrounds. Ideally I wouldn't want to use [UIColor clearColor] to fix this bug.
Maybe a completely custom painted cell would somehow fix it. I haven't tried those before though.
Does anyone else have a solution for this?
Thanks for the info, I was searching how to erase the background color from a UILabel.
I used the following line:
textLabel.backgroundColor = [UIColor clearColor];
and worked perfectly!!!
thanks
Alejandra :)
Selections aren't meant to be shown for extended periods! (We got knocked on this for several of our apps)
??? That means Apple would not approve their own Calendar app on iPhone! When you go to edit the start and end times of the event, the start time is selected indefinitely, it only changes once the user taps to the next field.