views:

721

answers:

4

I'm using iPhone SDK 3.0.

Say, I enabled 'shake to undo' by setting application.applicationSupportsShakeToEdit to YES in my app delegate. I created an UITextView in RootViewController and had it become first responder upon app launch.

I use - (BOOL)textView:(UITextView *)aTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text to make changes to the text view according to what the user types. The problem is that it messed up the default undoing action. Shaking the device no longer display 'Undo Typing'.

I tried to create an undo manager myself but have yet to success. Nothing shows up when I shake my device, even though I had the undo manager registered some actionprepareWithInvocationTarget.

Anyone has an idea?

EDIT: I have checked WriteRoom for iPhone's behavior and also TextExpander Touch SDK example code, both of which display the Undo option upon shaking until an auto-completion is made. That is, when a change is commited to the textView via setText:, I guess. So, changing the textView's content indeed has some effects on the undo mechanism.

EDIT 2: The question is that: How could I incorporate the undo feature in this case?

A: 

I found the implementation, at least in 3.1 to be buggy and opted to not use undo at all in my app. The bug involved the undo mechanism adding extra undos under certain circumstances, so making the message displayed to the user not make any sense.

Steve Weller
Could you elaborate about these 'certain circumstances'? I'm quite interested in having the undo feature in my app.
QAD
I looked this up. The problem I ran into is that self.editingView.text = someTextString; adds an undo event with no actionName to the stack. When the user goes to undo they get an undo pop-up that doesn't mean anything to them before any real one.
Steve Weller
+1  A: 

The default value for applicationSupportsShakeToEdit is YES so there really shouldn't be any need to turn that on.

I'm not sure exactly what your question/problem is without code, but it's possible that by returning NO on UITextViewDelegate textView:shouldChangeTextInRange you could be short-circuiting the UndoManager magic happening. You might need to review that logic inside that delegate method to make sure that is not happening, or perhaps use a different delegate message like textViewDidChange: instead.

slf
I also think that returning NO has done something to the undo mechanism, but couldn't figured out how to fix that. Unfortunately textView:shouldChangeTextInRange: is exactly what I need to use, since I have to make changes to the input itself.
QAD
Returning NO blocks any edits to the textview and shaking-to-undo displays a prompt labeled: "Nothing to undo".
bentford
A: 

Have you looked at the source code here: http://github.com/dcgrigsby/TallyTrucks/tree/master It has a basic (working) implementation of the NSUndoManager. Possibly it could help.

Mr-sk
Yeah, I've looked at that. No help though.
QAD
+2  A: 

How to incorporate your own undo? Here is the simplest approach possible.

NOTE: this does not respond to shake gesture in simulator.

UndoTestViewController.h:

@interface UndoTestViewController : UIViewController <UITextViewDelegate, UIAccelerometerDelegate>{
    IBOutlet UITextView *textview;
    NSString *previousText;
    BOOL showingAlertView;
}
@property (nonatomic,retain) NSString *previousText;
@end

UndoTestViewController.m:

@implementation UndoTestViewController
@synthesize previousText;

- (void)viewDidLoad {
    [super viewDidLoad];

    //disable built-in undo
    [UIApplication sharedApplication].applicationSupportsShakeToEdit = NO;

    showingAlertView = NO;

    [UIAccelerometer sharedAccelerometer].delegate = self; 
    [UIAccelerometer sharedAccelerometer].updateInterval = kUpdateInterval;

    //set initial undo text
    self.previousText = textview.text;
}

#pragma mark UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {

    //save text before making change
    self.previousText = textView.text;

    //changing text in some way...
    textView.text = [NSString stringWithFormat:@"prepending text %@",textView.text];
    [textView resignFirstResponder];
    return YES;
}
#pragma mark -

#pragma mark UIAccelerometerDelegate
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration { 

    if( showingAlertView ) return;

    if ( acceleration.x > kAccelerationThreshold || 
        acceleration.y > kAccelerationThreshold || 
        acceleration.z > kAccelerationThreshold ) {


        showingAlertView = YES;
        NSLog(@"x: %f y:%f z: %f", acceleration.x, acceleration.y, acceleration.z);


        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Undo Typing", nil];
        alert.delegate = self;
        [alert show];
        [alert release];
    }
}
#pragma mark -

#pragma mark UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if( buttonIndex == 1 ) {
        textview.text = self.previousText;
    }
    showingAlertView = NO;
}
#pragma mark -
bentford
While it's not quite what I really want (love to use the built-in NSUndoManager), it's indeed a way to solve the problem. So here you go, 100 reps.
QAD
Thanks! ..........
bentford