December 8 2008
UITextView in a UITableView on the iPhone
While writing FilePile, I wanted to use a UITextView in a UITableView, both subclasses of UIScrollView. The file information view needed a preview section and a list of attributes, so the two classes worked well together for previewing a text file and showing its file attributes. However, the UITextView was regularly blank, on the iPhone Simulator and the device. Scrolling the view would cause its contents to appear. This UI bug was especially problematic when I had a text preview that didn’t stretch beyond the frame. It didn’t scroll, so it wouldn’t appear just by touching it; the view was just blank until it was drawn again. See the screenshot below.
The problem is easily reproducible with Apple’s UICatalog. Just make the following two changes.
- TextViewController.m:86: change “myTableView.scrollEnabled = NO;” to “myTableView.scrollEnabled = YES;”</li>
- TextViewController.m:146 change “return 1;” to “return 10;”</li>
Launch the project, and click on “TextView”. Slowly scroll down the screen, and you’ll see that the contents of the UITextViews don’t consistently appear. I wrote a quick sample project to demonstrate it. You can find it on Google Code or download it as a zip file. Below is a short movie showing the problem.
I filed a bug (rdar://6429549) with Apple about the issue, but in the mean time, I found a couple ways to deal with it: reuse and setNeedsLayout. UICatalog doesn’t reuse the UITextView in TextViewController. Understandable given UICatalog only used the view once, but for these purposes, reuse is a good idea. This change cuts down on the number of UITextViews that are blank. Now when I scroll one of the blank views, that reused view appears correctly from then on, implying the problem was in initialization.
Still, reuse doesn’t solve my original problem. I only had a single UITextView in FilePile’s file view, and I want it to always show up, without requiring a scroll event. Fortunately, a simple NSTimer in the UITableView’s datasource method solves the issue. In - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath, just call [NSTimer scheduledTimerWithTimeInterval:.5 target:cell selector:@selector(setNeedsLayout) userInfo:nil repeats:NO]; when the cell is a UITextView (or UIScrollView subclass). Below is the full method.
With that code in place, here is a movie of the result.
UPDATE: I filed this bug under rdar://6429549. Of course, it was a duplicate and rolled under rdar://6201360.