Ruby on Rails, iOS, Git...

We spend a lot of time thinking about these things. If we have something helpful to share, we'll put it here.

Request a free RaddOnline® proposal.

iPhone SDK: Using a UITableViewCell with a UITextView for Entering Text, Part 1 of 2

Posted by Tim Stephenson, RaddOnline® on Saturday, March 07, 2009

When a Simple Text Field Won’t Do

iphone-notesWhen entering lots of text, sometimes a simple text field won’t do the trick. Especially if you want to imitate the look of Apple Applications such as iCal. You need to use a table view and a custom table view cell, as I will demonstrate. Apple provides a good example of this using code. I’ll demonstrate how to do the same trick using Interface Builder. Apple’s sample: UICatalog

This is a two part blog. Part one describes how to create a text entry field. Part two, will describe displaying variable amounts of text in a table view cell, and adjusting the height of the cell to hold all of the text. Creating a controller for entering text, and a controller to display all of the text in a table view cell is the goal.

UITextView Project Overview

In this project, I created an application with two screens. The first screen provides a button to open the second screen where text is entered. Both screens use grouped UITableViewControllers. You will build the app, move from screen to screen, enter any amount of text into the field, and save it.

Get Started with a Navigation-Based Project

  1. First, create a new project in Xcode and select the Navigation-Based Project. I named mine UITextView.
  2. Open the RootViewController.xib file in the resources group, select the Table View and type Command + 1 to open the inspector. Change the style from Plain to Grouped and save the file.
  3. Control click on the classes group and select Add > Add New File. Select Cocoa Touch Classes, and select the UITableViewController Subclass template. I named mine AddNoteViewController. This is the controller to manage the text view.
  4. Open AddNoteViewController.h and add the following code:
@interface AddNoteViewController : UITableViewController <UITextViewDelegate> {
    IBOutlet UITableView *tbView;
    NSString *aNote;
}
@property (nonatomic, retain) NSString *aNote;

- (void)save:(id)sender;

@end

Be sure to make the class a UITextViewDelegate.

With an IBOutlet for the table view, create the nib file.

  1. Control click on the Resources group in Xcode.
  2. Select Add > Add New File and then click on User Interfaces. Click on Empty XIB and then click on Next and name the file AddNoteView.xib.
  3. Double click on the new file to open it, it should have only the File’s Owner and First Responder objects in it.
  4. Drag a UITableView from the Library into the file.
  5. Click on File’s Owner and then type Command + 4 to open the Class Identity inspector.
  6. Set the class of File’s Owner to our controller AddNoteViewController. You should see the tbView outlet that we defined previously.addnote-ibconnections
  7. Now control click on the File’s Owner. When the gray box pops up, connect the tbView outlet to the Table View.
  8. Also connect the view outlet to the Table View.
  9. Then control click on the table view and connect the datasource and delegate outlets to be the files owner.
  10. Lastly, with the Table View selected, click Command + 1 to bring up the inspector and change the style of the table to Grouped.

When done, save and close the nib file.

Load the New Controller from the RootViewController

With the initial parts of the AddNoteViewController in place, wire up the RootViewController such that the AddNoteView loads when clicked. Open RootViewController.h and enter this code:

#import <UIKit/UIKit.h>
#import "AddNoteViewController.h" 

@interface RootViewController : UITableViewController {
    AddNoteViewController *addNoteController;
    NSString *aNote;
}

@property (nonatomic, retain) AddNoteViewController *addNoteController;
@property (nonatomic, retain) NSString *aNote;
@end

This imports the new controller and adds a variable for it. I also added a variable to hold a note string. I won’t be using it just yet.

Next, open the RootViewController.m file. First synthesize our two variables.

@synthesize addNoteController;
@synthesize aNote;

I added a title in the viewDidLoad method.

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"My Note";
}

Change the table view methods as follows:

#pragma mark Table view methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 1;
}

// Customize the appearance of table view cells.
- (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];
    }

    // Set up the cell...
    cell.text = @"Add a Note";
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    return cell;
}

Now we have a cell to click on. To handle the click, edit the tableView:didSelectRowAtIndexPath method as follows:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    addNoteController = [[AddNoteViewController alloc] initWithNibName:@"AddNoteView" bundle:nil];
    addNoteController.title = @"My Notes";
    addNoteController.aNote = self.aNote;

    [self.navigationController pushViewController:addNoteController animated:YES];
    [addNoteController release];    
}

Build and run the application. Click on the Add a Note row, and the AddNoteViewController will load up. It won’t do anything yet, but this is a good time to debug and clean up any errors.

Build the UITextView

Before we can load the AddNoteViewController with the TextView, we need to define a custom cell to hold it. We’ll then load that cell into the table view.

  1. In Xcode, control click on the Classes group and select Add > Add New File.
  2. Select Cocoa Touch Classes, and then click on UITableViewCell subclass and click next. Name your file TextViewCell.
  3. Open the TextFieldCell.h file and add the following code:
#import <UIKit/UIKit.h>

// cell identifier for this custom cell
extern NSString *kCellTextView_ID;

@interface TextViewCell : UITableViewCell {
    IBOutlet UITextView *textView;
}
+ (TextViewCell*) createNewTextCellFromNib;

@property (nonatomic, retain) UITextView *textView;

@end

This defines an IBOutlet to access the UITextView. We also define a method to create the cell from a nib file. Before we create the nib file, let’s finish the implementation. Open TextViewCell.m and add the following:

#import "TextViewCell.h" 

// cell identifier for this custom cell
NSString* kCellTextView_ID = @"CellTextView_ID";

@implementation TextViewCell
@synthesize textView;

//Helper method to create the workout cell from a nib file...
+ (TextViewCell*) createNewTextCellFromNib { 
    NSArray* nibContents = [[NSBundle mainBundle] loadNibNamed:@"TextViewCell" owner:self options:nil]; 
    NSEnumerator *nibEnumerator = [nibContents objectEnumerator]; 
    TextViewCell* tCell = nil; 
    NSObject* nibItem = nil; 
    while ( (nibItem = [nibEnumerator nextObject]) != nil) { 
        if ( [nibItem isKindOfClass: [TextViewCell class]]) { 
            tCell = (TextViewCell*) nibItem; 
            if ([tCell.reuseIdentifier isEqualToString: kCellTextView_ID]) 
                break; // we have a winner 
            else 
                tCell = nil; 
        } 
    } 
    return tCell; 
} 

- (void)dealloc
{
    [textView release];
    [super dealloc];
}

@end

This initializes our cell using a nib file that we haven’t created yet. Let’s do that now.

Creating a Cell Template in Interface Builder

TextViewCell

  1. First, control click on the resources folder in Xcode and select Add > Add New File.
  2. Select User Interfaces, and then select Empty XIB. Name the file TextViewCell.xib, and then double click to open it.
  3. In the Library, find a UITableViewCell and drag it into the nib file.
  4. Select the cell, and click Command + 4 to open the class inspector.
  5. Set the class of the cell to TextViewCell, the class that we defined earlier.
  6. Click Command + 1 to open the Attributes inspector and enter “CellTextView_ID” in the Identifier field.
  7. Click on the Size tab of the inspector and enter 150 in the height field.
  8. Double click on the table view cell to open it and drag a TextView onto the cell.
  9. Size the TextView to fit into the cell nicely. Then double click it and remove the sample text from the field.
  10. Finally, control click on the cell and drag from the textView outlet to the Text View that we added.

When done, save the file and close it. We’re getting close now, I promise.

Implement the AddNoteViewController

This is our final step. Open the AddNoteViewController.m file and add this:

#import "AddNoteViewController.h" 
#import "RootViewController.h"

I’m importing the root view controller here only to access the note variable to simulate saving the data. In a real application I would use a model class and save the data to a database or file.

//Text View contstants
#define kUITextViewCellRowHeight 150.0

Store a value that will be used to define the height of the cell in the table view.

@implementation AddNoteViewController
@synthesize aNote;

- (void)save:(id)sender
{
    /*
     Save data from the text view to the variable and then pop back to the root view.
     Normally , this is where I would save data to the database. To keep the example simple
     I am simply setting the variable in the root controller to match that of my note.
    */

    TextViewCell *cell = (TextViewCell *) [tbView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    self.aNote = [cell.textView text];
    NSLog(self.aNote);
    RootViewController *rootController = [self.navigationController.viewControllers objectAtIndex:0];
    rootController.aNote = self.aNote;
    [self.navigationController popViewControllerAnimated:YES];
}

The save method retrieves the value of the TextView and assigns it to the variable aNote. In this case I also assign it to the variable in the root view controller to use the next time the AddNoteViewController is loaded.

In the viewDidLoad method, add a button to save our text. Since I am popping back to the root view controller after saving, I don’t have to dismiss the keyboard.

- (void)viewDidLoad {
    [super viewDidLoad];

    // provide a Save button to dismiss the keyboard
    UIBarButtonItem* saveItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(save:)];
    self.navigationItem.rightBarButtonItem = saveItem;
    [saveItem release];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}

#pragma mark Table view methods
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    return UITableViewCellEditingStyleNone;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 1;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat result;
    result = kUITextViewCellRowHeight;    
    return result;
}

This sets the height of the cell to the value of the constant that was defined earlier.

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    TextViewCell *cell = (TextViewCell *) [tableView dequeueReusableCellWithIdentifier:kCellTextView_ID];

    if (cell == nil) {
        cell = [TextViewCell createNewTextCellFromNib];
    }

    // Set up the cell...
    cell.textView.text = self.aNote;
    [cell.textView becomeFirstResponder];
    cell.textView.delegate = self;
    return cell;
}

Here we call the method in our TextViewCell, createNewTextCellFromNib, only if the cell is nil. Set the text property of the textView to the value of the aNote variable, and make the textView the first responder to display the keyboard. Finally set the delegate to self.

#pragma mark UITextView delegate methods
- (void)textViewDidBeginEditing:(UITextView *)textView
{

}

- (void)dealloc {
    [super dealloc];
}
@end

I didn’t do anything with the delegate method, but I left it to show how things can be handled.

At this point, everything should be in place. You can build and run the project. Click on Add a Note, enter a note and click save. When clicking on Add a Note a second time, you should see your previous note.

Here’s the sample project: UITextView

Stay tuned for part 2, which will be ready at the end of the week.

Comments

Jarmo said on Friday, May 15, 2009:

Wow! I'm shocked that something so simple even requires a tutorial. But add to that the amount of non-trivial code that had to be written plus the weirdness of the interactions with the xcode IDE and you will surely come to the conclusion that Apple does not have a clue how to craft decent developer tools.

Yoga said on Thursday, June 11, 2009:

Hi,

Can you help me in creating a custom table view cell with two UIButtons with similar coding style?

Thanks in Advance

Tim Stephenson said on Friday, June 12, 2009:

Sure. I'm planning a blog on the subject so I'll let you know when it is done. I'll email you the sample project before I finish the blog.

In the mean time, the sample projects at developer.apple.com may help. Have you looked at the UICatalog sample?

krye said on Friday, June 19, 2009:

I'd love to see how you do the same thing with an image. I'd love to be able to drill down to an image view from a table cell.

John said on Friday, June 19, 2009:

Thanks for the excellent posts Tim. In this one, you said 'when a simple textfield won't do', however I can't even find a sample of a UITableView that allows in place editing using a simple text field. Would you happen to know of any samples?

Steve said on Sunday, June 21, 2009:

You know your 2nd view that is called from the UITableView can be a simple UIView with a UITextView on it right?

This is overly complicated for such a simple example.

Tim Stephenson said on Monday, June 22, 2009:

Hi Steve

Yes. In this example I wanted to make the UI match that of various Apple apps. I also had a need to place other data in the table around it.

Rahul Jain said on Sunday, July 12, 2009:

Hi Tim,

I am trying to create a User Interface similar to iphone email compose screen but have no clue where to start. Can you please guide me what should i start with? What controls i should use?

Mike said on Monday, September 21, 2009:

Hey man, awesome post. I'm trying to do a very similar thing, and for the most part have succeeded. The only issue I'm having is that the UITextView is not responding to touch events; it seems the table cell is absorbing the event. This means that when the auto correct bubble pops up the user can't get rid of it. I haven't been able to reproduce this issue in your code, but I also can't tell what the difference is between your example and my code base. Any ideas what might be the root of the problem?

Thanks.

Phil said on Wednesday, December 09, 2009:

I second John's request!

Will Willett said on Tuesday, December 22, 2009:

can you help me please i keep following different tutorials but whenever i get to the point when i open my controller identity i only have class identity and interface identity builder in the form/window but no where can i find class action or class outlets i have latest sdk and mac pro with snow leopard i have tried command + 4, inspector in tools and double clicking file Owner is it anything to do with with only being free member do i have to pay $99 to use SDK

schwiz said on Sunday, June 13, 2010:

This is exactly why I prefer making Android apps. Thanks for the tutorial that I shouldn't need!

david said on Sunday, September 05, 2010:

hi;

good tut. but i need to know how i can add a another NSString like aNote?

i would like to have 3 rows in the same view and each rows have different view.

thk

kldy said on Thursday, September 23, 2010:

Can it good work for more than one textview and more than one rows in tableview?

Climbing Rope said on Monday, October 18, 2010:

ohh…wonderful submit but truly?/? :P