Tuesday, February 10, 2009

Long Text Generic Editing Controller

Another in my series of generic controller classes intended to be used in a navigation application. This one is for editing longer text. It uses a UITextView instead of a UITextField. There seems to be a bug in UITextView that's keeping me from changing the insertion point to the end of the text, but I have a bug report open with Apple on that, so hopefully either Apple will fix it, or tell me how to work around it. In the meantime, when editing existing values, the user will have to tap at the end of the text before typing.



Its use is nearly identical to the previous generic controller classes I posted. Just set your controller class as the generic controller class' delegate, set the string value you want to let the user edit, then implement the delegate method to get notified of the new value if the user makes any changes.

And here is the code

LongTextFieldViewController.h
//
// LongTextFieldViewController.h
// iContractor
//
// Created by Jeff LaMarche on 2/10/09.
// Copyright 2009 Jeff LaMarche Consulting. All rights reserved.
//

#import <UIKit/UIKit.h>

@protocol LongTextFieldEditingViewControllerDelegate <NSObject>
@required
- (void)takeNewString:(NSString *)newValue;
- (UINavigationController *)navController; // Return the navigation controller
@end


@interface LongTextFieldViewController : UITableViewController
{
NSString *string;
UITextView *textView;

id<LongTextFieldEditingViewControllerDelegate> delegate;
}
@property (nonatomic, retain) NSString *string;
@property (nonatomic, retain) IBOutlet UITextView *textView;
@property (nonatomic, assign) id <LongTextFieldEditingViewControllerDelegate> delegate;
- (void)cancel;
- (void)save;
@end



LongTextFieldViewController.m
//
// LongTextFieldViewController.m
// iContractor
//
// Created by Jeff LaMarche on 2/10/09.
// Copyright 2009 Jeff LaMarche Consulting. All rights reserved.
//

#import "LongTextFieldViewController.h"


@implementation LongTextFieldViewController
@synthesize string;
@synthesize textView;
@synthesize delegate;
- (void)cancel
{
[[self.delegate navController] popViewControllerAnimated:YES];
}
- (void)save
{
[self.delegate takeNewString:textView.text];
[[self.delegate navController] popViewControllerAnimated:YES];
}
#pragma mark -
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{


NSUInteger firstRowIndices[] = {0,0};
NSIndexPath *firstRowPath = [NSIndexPath indexPathWithIndexes:firstRowIndices length:2];
UITableViewCell *firstCell = [self.tableView cellForRowAtIndexPath:firstRowPath];
UITextView *firstCellTextField = nil;
for (UIView *oneView in firstCell.contentView.subviews)
{
if ([oneView isMemberOfClass:[UITextView class]])
firstCellTextField = (UITextView *)oneView;
}
[firstCellTextField becomeFirstResponder];



UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]
initWithTitle:NSLocalizedString(@"Cancel", @"Cancel - for button to cancel changes")
style:UIBarButtonItemStylePlain
target:self
action:@selector(cancel)]
;
self.navigationItem.leftBarButtonItem = cancelButton;
[cancelButton release];
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithTitle:NSLocalizedString(@"Save", @"Save - for button to save changes")
style:UIBarButtonItemStylePlain
target:self
action:@selector(save)]
;
self.navigationItem.rightBarButtonItem = saveButton;
[saveButton release];

[super viewWillAppear:animated];
}
- (void)dealloc
{
[string release];
[textView release];
[super dealloc];
}

#pragma mark Tableview methods
- (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 *LongTextFieldCellIdentifier = @"LongTextFieldCellIdentifier";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:LongTextFieldCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:LongTextFieldCellIdentifier] autorelease];
UITextView *theTextView = [[UITextView alloc] initWithFrame:CGRectMake(10.0, 10.0, 280.0, 161.0)];
theTextView.editable = YES;
theTextView.text = string;
theTextView.font = [UIFont systemFontOfSize:14.0];
[theTextView becomeFirstResponder];
self.textView = theTextView;
[[cell contentView] addSubview:theTextView];
[theTextView release];
}
// This doesn't work - no matter where I put it. It's almost as if this property is readonly
textView.selectedRange = NSMakeRange([string length], 0);;


return cell;
}

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
return nil;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Nothing for now
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 181.0;
}
@end






7 comments:

Seventoes said...

Beautiful! Thanks!

blog said...

Mmmmm, abstract classes...thank you very much!

John Galt said...

Hey Jeff.

I couldn't get your code to work, but learned from it, and created my own version.

Anyway, by

textView.selectedRange = NSMakeRange([string length], 0);

I think you were trying to set the insertion point after the "string" of text, right?

Well, I found a kind of workaround - add the setting of the text view to viewDidAppear as in:


- (void)viewDidAppear:(BOOL)animated {
self.textView.text = string;
}


(Sorry, but italics seemed to be the only way to differentiate code from rest of the comment)

For best viewing effect, set .text value in both places: tableView's cellForRow... and viewDidAppear - this will mininize the user shock on view getting modified after being displayed.

PS: Thanks for the book. It got me started on iPhone dev :) I think its the best book out there. Your book, and its sample code are helping me with my first iPhone project. And now, this blog. You are awesome.

Ajay Gautam
http://www.ajaygautam.com/iPhone.html

John Galt said...

PS: Setting the becomeFIrstResponse in viewDidAppear makes the visual effect even better :)

Ajay Gautam

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

CHEAPSOCCERUNIFORM said...

Many thanks for your themed, a good post for me.
Now it will be very cold in winter, in order to keep warm yourself, moncler company has provide jackets for you, so it is better to own moncler yourselif as soon as possible. Eeveryone love fashion clothing, polo ralph lauren is very popular all over world, that is my dream to get ralph lauren.We know Ray Ban by America soldier, all of them wear ray ban sunglasses when they walk on the road, it is fashionable for you. Most of people like to wear jeans, it is very modern when wearing true religion jeans in the street.

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels