Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

crash in resignFirstResponder in uitableview

In my UITableView i have a set of custom cells that contain an UITextField. I found out (the hard way), that apparently leaving the current view (either by pushing a new view controller or dismissing the active view ('go back')) will make my app crash, when the keypad is still visible.

To hide the keypad when the user is still editing an UITextField, but the view is changed, i added [self.view endEditing:YES]; just before i push the new view controller, and also in the viewWillDisappear method.

Now, my app is crashing only 1 out of 5 trys to hide the keypad. Here i probably learned about the reason for this: When the cell is moved offscreen, it is destroyed/recycled, so it can be dequeued again when needed. This means, once my cell and the contained textfields moves off screen, sending the resignFirstResponder message to it (either manually or by [self.view endEditing:YES];, the app will crash. This is the backtrace:

#0  0x012e309b in objc_msgSend ()
#1  0x05956888 in dyld_stub_usleep ()
#2  0x003ff056 in -[UIResponder resignFirstResponder] ()
#3  0x003c697f in -[UITextField resignFirstResponder] ()
#4  0x003c6ab1 in -[UIView(UITextField) endEditing:] ()
#5  0x00012c0a in -[MyController viewWillDisappear:] (self=0x7419e90, _cmd=0x52aa27e, animated=1 '\001') at MyController:121
#6  0x003ee9a2 in -[UINavigationController _startTransition:fromViewController:toViewController:] ()
#7  0x003e932a in -[UINavigationController _startDeferredTransitionIfNeeded] ()
#8  0x003e8fb6 in -[UINavigationController _popViewControllerWithTransition:allowPoppingLast:] ()
#9  0x003e9142 in -[UINavigationController popViewControllerAnimated:] ()
#10 0x003e857a in -[UINavigationController navigationBar:shouldPopItem:] ()
#11 0x00389260 in -[UINavigationBar _popNavigationItemWithTransition:] ()
#12 0x0039261b in -[UINavigationBar _handleMouseUpAtPoint:] ()
#13 0x00354ded in -[UIWindow _sendTouchesForEvent:] ()
#14 0x00335c37 in -[UIApplication sendEvent:] ()
#15 0x0033af2e in _UIApplicationHandleEvent ()
#16 0x01ad5992 in PurpleEventCallback ()
#17 0x0115e944 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ ()
#18 0x010becf7 in __CFRunLoopDoSource1 ()
#19 0x010bbf83 in __CFRunLoopRun ()
#20 0x010bb840 in CFRunLoopRunSpecific ()
#21 0x010bb761 in CFRunLoopRunInMode ()
#22 0x01ad41c4 in GSEventRunModal ()
#23 0x01ad4289 in GSEventRun ()
#24 0x0033ec93 in UIApplicationMain ()
#25 0x00001e18 in main (argc=1, argv=0xbffff0a0) at main.m:24

My question now is, how do I correctly hide the keypad of the UITextField inside my UITableViewCell in all situations? (table view disappears, new view controller is pushed, cell/textfield was moved off screen, etc.)

Any help is highly appreciated, I just can't get rid of the crashes!

Ill include some more code:

1) Custom cell class:

#import <UIKit/UIKit.h>

enum Type
{
   tpText = 0,
   tpInteger,
   tpDecimal,
   tpNumber
};

@protocol TextInputCellDelegate <NSObject>
@required
- (void)setNewText:(NSString*)newText forIndex:(NSIndexPath*)index;
@end

@interface TextInputCell : UITableViewCell <UITextFieldDelegate> {
   IBOutlet UILabel* mainText;
   IBOutlet UITextField* textField;
   NSNumberFormatter* numberFormatter;
   int type;
   id <TextInputCellDelegate> delegate;
}

- (void) initDelegateWithType:(int)aType;
- (void) save:(NSString*)text;
- (void) startEditing;

@property (nonatomic, retain) UILabel* mainText;
@property (nonatomic, retain) UITextField* textField;
@property (nonatomic, retain) NSNumberFormatter* numberFormatter;
@property (nonatomic, assign) int type;
@property (nonatomic, assign) id <TextInputCellDelegate> delegate;

@end

Implementation of custom cell:

#import "TextInputCell.h"

@implementation TextInputCell

@synthesize mainText;
@synthesize textField;
@synthesize numberFormatter;
@synthesize type;
@synthesize delegate;

- (void) initDelegateWithType:(int)aType {
   self.type = aType;
   textField.delegate = self;   
}


- (BOOL)textField:(UITextField *)aTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; {

   if (![string length] || type == tpText || (isnumber([string characterAtIndex:0]) && type == tpNumber))
   {
      [self save:[textField.text stringByReplacingCharactersInRange:range withString:string]];

      return YES;
   }

   char c = [string characterAtIndex:0]; 
   BOOL isSep = [string isEqualToString:[numberFormatter decimalSeparator]];
   BOOL isMinus = [string isEqualToString:@"-"];
   NSRange sep;
   sep.location = NSNotFound;
   sep.length = 0;

   if ([aTextField.text length])
      sep = [aTextField.text rangeOfString:[numberFormatter decimalSeparator]];

   if (isMinus)
   {
      // allow '-' only if type is tpNumber and field is empty

      if (type != tpNumber)
         return NO;

      if ([aTextField.text length])
         return NO;

      [self save:[textField.text stringByReplacingCharactersInRange:range withString:string]];
      return YES;
   }

   if (isnumber(c) || ((type == tpDecimal || type == tpNumber) && isSep && sep.location == NSNotFound))
   {
      // allow separator for decimal and number, but only if not in text already

      if (!isSep && sep.location != NSNotFound && type == tpDecimal)
      {
         // round after , (only for decimal type)

         NSString* text = [NSString stringWithFormat:@"%@%@", aTextField.text, string];
         double num = [[numberFormatter numberFromString:text] doubleValue];
         double res = ((int)(num / 0.5)) * 0.5;
         aTextField.text = [numberFormatter stringFromNumber:[NSNumber numberWithDouble:res]];

         [self save:aTextField.text];

         return NO;
      }

      [self save:[NSString stringWithFormat:@"%@%@", aTextField.text, string]];

      return YES;
   }

   [self save:[NSString stringWithFormat:@"%@%@", aTextField.text, string]];

   return NO;
}

- (void) save:(NSString*)text {

   UITableView* view = (UITableView*)[self superview];
   NSIndexPath* index =  [view indexPathForCell:self];

   if (delegate)
      [delegate setNewText:text forIndex:index];
}

- (BOOL)textFieldShouldEndEditing:(UITextField *)field 
{
   NSLog(@"should end");

//   if ([field becomeFirstResponder])
//      [field resignFirstResponder];

   return YES;
}

- (BOOL)textFieldShouldReturn:(UITextField *)field {

   if (type != tpText && [field.text length] == 0)
      field.text = @"0";

   NSLog(@"should return");

//   if ([field becomeFirstResponder])
//      [field resignFirstResponder];

   return YES;
}

- (void) startEditing {

   NSLog(@"should return");

   [self.textField becomeFirstResponder];
}

@end

View controller that contains 4 of those cells:

@implementation MailPrefController

@synthesize menuItems;
@synthesize mailTo;
@synthesize mailCc;
@synthesize mailBcc;
@synthesize mailSubject;
@synthesize mailBody;


#pragma mark -
#pragma mark Initialization


- (id)initWithStyle:(UITableViewStyle)style {
    // Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
    if ((self = [super initWithStyle:UITableViewStyleGrouped])) {
    }
    return self;
}


#pragma mark -
#pragma mark View lifecycle


- (void)viewDidLoad {
    [super viewDidLoad];

   // save button

   UIBarButtonItem* saveButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(saveMailPrefs)];
   self.navigationItem.rightBarButtonItem = saveButton;
   [saveButton release];

   self.navigationItem.title = NSLocalizedString(@"Mail template", nil);

   // menu items

   self.menuItems = [[NSArray alloc] initWithObjects:NSLocalizedString(@"To:", nil), NSLocalizedString(@"Cc:", nil), NSLocalizedString(@"Bcc:", nil), NSLocalizedString(@"Subject:", nil), NSLocalizedString(@"Body:", nil), nil];
}


- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}

#pragma mark -
#pragma mark Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // Return the number of sections.
    return 1;
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    return [menuItems count];
}


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

   if (indexPath.row < 4)
   {
      // title

      TextInputCell* textCell = (TextInputCell*)[tableView dequeueReusableCellWithIdentifier:@"TextInputCell"];

      if (textCell == nil)
      {
         NSArray* nibContents = [[NSBundle mainBundle] loadNibNamed:@"TextInputCell" owner:self options:nil];
         textCell = [nibContents lastObject]; 
         textCell.selectionStyle = UITableViewCellSelectionStyleNone;
         textCell.textField.textColor = [Service uiColor];
         textCell.delegate = self;
         [textCell initDelegateWithType:tpText];
      }

      textCell.mainText.text = [menuItems objectAtIndex:indexPath.row];

      switch (indexPath.row)
      {
         case 0: textCell.textField.text = self.mailTo; break;
         case 1: textCell.textField.text = self.mailCc; break;
         case 2: textCell.textField.text = self.mailBcc; break;
         case 3: textCell.textField.text = self.mailSubject; break;

         default: break;
      }

      return textCell;
   }

   // body text multiline

   TextInputMultilineCell* textMultilineCell = (TextInputMultilineCell*)[tableView dequeueReusableCellWithIdentifier:@"TextInputMultilineCell"];

   if (textMultilineCell == nil)
   {
      NSArray* nibContents = [[NSBundle mainBundle] loadNibNamed:@"TextInputMultilineCell" owner:self options:nil];
      textMultilineCell = [nibContents lastObject]; 
      textMultilineCell.selectionStyle = UITableViewCellSelectionStyleNone;
      textMultilineCell.textView.font = [UIFont systemFontOfSize:12];
      textMultilineCell.textView.textColor = [Service uiColor];
      textMultilineCell.delegate = self;
      [textMultilineCell initDelegate];

      CGRect rect = textMultilineCell.textView.frame;
      rect.size.height *= 2;
      textMultilineCell.textView.frame = rect;
   }

   textMultilineCell.mainText.text = [menuItems objectAtIndex:indexPath.row];
   textMultilineCell.textView.text = self.mailBody;

   return textMultilineCell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

   switch (indexPath.section)
   {
      case 0:
      {
         // general section

         if (indexPath.row < 4)
         {
            TextInputCell* cell = (TextInputCell*)[[self tableView] cellForRowAtIndexPath:indexPath];            
            [cell startEditing];
         }
      }

      default: break;
   }
}

The crash can be reproduced by clicking one of the cells with a UITextField, slide the cell off screen (without hiding keypad), then just dismissing the tableview (e.g. go back via navigation controller).

Could this be caused by manually opening the keypad when the cell is clicked? (startEditing method) I do this so the user does not have to hit the textfield, but editing also starts when he e.g. taps the cells textlabel.

like image 837
user826955 Avatar asked Jul 03 '11 14:07

user826955


2 Answers

Maybe a bit late, but I had a similar problem. I could see with zombies enabled my UITextView was trying to be messaged (with textViewDidEndEditing) after deallocation by scrolling out of the view. In the dealloc of my equivalent of your TextInputCell, i just set the UITextField delegate to nil. worked a treat.

like image 179
Doominator Avatar answered Nov 02 '22 19:11

Doominator


I had a similar issue today, with regular crashes caused by pressing the UINavigation Back Button whilst the keyboard was visible. My UITextField was contained within a UIScrollView to enable scrolling it up when the keyboard was visible.

I'm compiling my app under iOS 5.1 with ARC, and the solution for me was to set my UIScrollView delegate to nil in the dealloc call.

like image 22
Suman Roy Avatar answered Nov 02 '22 18:11

Suman Roy