UITextView in a UITableViewCell smooth auto-resize shows and hides keyboard on iPad, but works on iPhone

13,854

Solution 1

you should return NO in:

 -(BOOL) textViewShouldEndEditing:(UITextView *)textView

if you would like to show keyboard at all times. You should handle cases, which keyboard should be hidden, by returning YES to this delegate function.

edit:

I dug a little more, when [tableView endUpdates] called, it basically does 3 things :

  1. Disables user interaction on the tableView
  2. Updates cell changes
  3. Enables user interaction on the tableView

The difference between SDKs(platforms) is at [UIView setUserInteractionEnabled] method. As UITableView does not overrite setUserInteractionEnabled method, it is called from super (UIView).

iPhone when setUserInteractionEnabled called, looks for a private field _shouldResignFirstResponderWithInteractionDisabled which returns NO as default, so does not resign the first responder (UITextView)

But on iPad there is no such check AFAIK, so it resignes UITextView on step 1, and sets focus and makes it first responder on step 3

Basically, textViewShouldEndEditing, which allows you to keep focus, according to SDK docs, is your only option ATM.

This method is called when the text view is asked to resign the first responder status. This might occur when the user tries to change the editing focus to another control. Before the focus actually changes, however, the text view calls this method to give your delegate a chance to decide whether it should.

Solution 2

I had the same issue for an iPad app and came up with another solution without having calculating the height of the text itself.

First create a custom UITableViewCell in IB with an UITextField placed in the cell's contentView. It's important to set the text view's scrollEnabled to NO and the autoresizingMask to flexibleWidth and flexibleHeight.


In the ViewController implement the text view's delegate method -textViewDidChanged: as followed, where textHeight is a instance variable with type CGFloat and -tableViewNeedsToUpdateHeight is a custom method we will define in the next step.

- (void)textViewDidChange:(UITextView *)textView
{
    CGFloat newTextHeight = [textView contentSize].height;

    if (newTextHeight != textHeight)
    {
        textHeight = newTextHeight;
        [self tableViewNeedsToUpdateHeight];
    }
}

The method -tableViewNeedsToUpdateHeight calls the table view's beginUpdates and endUpdates, so the table view itself will call the -tableView:heightForRowAtIndexPath: delegate method.

- (void)tableViewNeedsToUpdateHeight
{
    BOOL animationsEnabled = [UIView areAnimationsEnabled];
    [UIView setAnimationsEnabled:NO];
    [table beginUpdates];
    [table endUpdates];
    [UIView setAnimationsEnabled:animationsEnabled];
}

In the table view's -tableView:heightForRowAtIndexPath: delegate method we need to calculate the new height for the text view's cell based on the textHeight.

First we need to resize the text view cells height to the maximum available height (after subtracting the height of all other cells in the table view). Then we check if the textHeight is bigger than the calculated height.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{   
    CGFloat heightForRow = 44.0;

    if ([indexPath row] == kRowWithTextViewEmbedded)
    {
        CGFloat tableViewHeight = [tableView bounds].size.height;
        heightForRow = tableViewHeight - ((kYourTableViewsNumberOfRows - 1) * heightForRow);

        if (heightForRow < textHeight) 
        {
            heightForRow = textHeight;
        }
    }

    return heightForRow;
}

For a better user experience set the table view's content insets for bottom to e.g. 50.0.

I've tested it on the iPad with iOS 4.2.1 and works as expected.

Florian

Share:
13,854

Related videos on Youtube

Jason
Author by

Jason

Updated on July 17, 2020

Comments

  • Jason
    Jason almost 4 years

    I have implemented a custom UITableViewCell which includes a UITextView that auto-resizes as the user types, similar to the "Notes" field in the Contacts app. It is working properly on my iPhone, but when I am testing it in the iPad, I am getting some very strange behavior: When you get to the end of a line, the keyboard hides for a millisecond and then shows itself again immediately. I would write it off as just a quirky bug, but it actually causes some data loss since if you are typing, it loses a character or two. Here's my code:

    The Code

    // returns the proper height/size for the UITextView based on the string it contains.
    // If no string, it assumes a space so that it will always have one line.
    - (CGSize)textViewSize:(UITextView*)textView {
         float fudgeFactor = 16.0;
         CGSize tallerSize = CGSizeMake(textView.frame.size.width-fudgeFactor, kMaxFieldHeight);
         NSString *testString = @" ";
         if ([textView.text length] > 0) {
              testString = textView.text;
         }
         CGSize stringSize = [testString sizeWithFont:textView.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];
         return stringSize;
    }
    
    // based on the proper text view size, sets the UITextView's frame
    - (void) setTextViewSize:(UITextView*)textView {
         CGSize stringSize = [self textViewSize:textView];
         if (stringSize.height != textView.frame.size.height) {
              [textView setFrame:CGRectMake(textView.frame.origin.x,
                                            textView.frame.origin.y,
                                            textView.frame.size.width,
                                            stringSize.height+10)];  // +10 to allow for the space above the text itself 
         }
    }
    
    // as per: https://stackoverflow.com/questions/3749746/uitextview-in-a-uitableviewcell-smooth-auto-resize
    - (void)textViewDidChange:(UITextView *)textView {
    
         [self setTextViewSize:textView]; // set proper text view size
         UIView *contentView = textView.superview;
         // (1) the padding above and below the UITextView should each be 6px, so UITextView's
         // height + 12 should equal the height of the UITableViewCell
         // (2) if they are not equal, then update the height of the UITableViewCell
         if ((textView.frame.size.height + 12.0f) != contentView.frame.size.height) {
             [myTableView beginUpdates];
             [myTableView endUpdates];
    
             [contentView setFrame:CGRectMake(0,
                                              0,
                                              contentView.frame.size.width,
                                              (textView.frame.size.height+12.0f))];
         }
    }
    
    - (CGFloat)tableView:(UITableView  *)tableView heightForRowAtIndexPath:(NSIndexPath  *)indexPath {
         int height;
         UITextView *textView = myTextView;
         [self setTextViewSize:textView];
         height = textView.frame.size.height + 12;
         if (height < 44) { // minimum height of 44
              height = 44;
              [textView setFrame:CGRectMake(textView.frame.origin.x,
                                            textView.frame.origin.y,
                                            textView.frame.size.width,
                                            44-12)];
          }
          return (CGFloat)height;
    }
    

    The Problems

    So, here's what's happening

    1. This code is working 100% properly on my iPhone and in the iPhone simulator. As I type the text, the UITextView grows smoothly, and the UITableViewCell along with it.
    2. On the iPad simulator, however, it gets screwy. It works fine while you are typing on the first line, but when you get to the end of a line, the keyboard disappears and then reappears immediately, so that if the user continues typing the app misses a character or two.
    3. Here are some additional notes on the weird behaviors that I have noticed which may help explain it:
      • Also, I have found that removing the lines [myTableView beginUpdates]; [myTableView endUpdates]; in the function textViewDidChange:(UITextView *)textView makes the UITextView grow properly and also doesn't show and hide the keyboard, but unfortunately, then the UITableViewCell doesn't grow to the proper height.
      • UPDATE: Following these instructions, I am now able to stop the strange movement of the text; but the keyboard is still hiding and showing, which is very strange.

    Does anyone have any ideas as to how to get the keyboard to continually show, rather than hide and show when you get to the end of the line on the iPad?

    P.S.: I am not interested in using ThreeTwenty.

    • ImHuntingWabbits
      ImHuntingWabbits over 13 years
      Your keyboard is likely showing and hiding because the textfield is resigning first responder during the animation. Try putting a breakpoint on that method (symbolic breakpoint will do) in xCode and running your code to see whom is asking it to resign.
    • Jason
      Jason over 13 years
      Which animation are you talking about specifically? The UITableView's animation of re-drawing itself?
    • pasine
      pasine over 13 years
      maybe the problem is in [myTableView beginUpdates] or [myTableView endUpdates] since the issue disappear when you remove them: can you put some more code?
  • Jason
    Jason over 13 years
    This apparently has worked! However, it's not the whole story. By setting textViewShouldEndEditing to always return NO, then my functions to allow users to close the keyboard never work. So, I needed to set another property in my view controller which kept track of when the UITextViews should be allowed to close. This is working, but it feels like a hack. Is there a way to really get at why [UITableView beginUpdates]; [UITableView endUpdates]; was resigning the UITextView in the first place?
  • Deniz Mert Edincik
    Deniz Mert Edincik over 13 years
    Actually I guess iPad behavior is the correct one, [UITableView beginUpdates] should resign first responder. But I will dig deeper today to see, if there is a workaround
  • Deniz Mert Edincik
    Deniz Mert Edincik over 13 years
    updated my answer, seems textViewShouldEndEditing is your only option ATM
  • Ortwin Gentz
    Ortwin Gentz almost 13 years
    Works great and actually better than Jason's and Deniz' solution. I only had an issue when the keyboard wasn't shown right away. In this case, when the textView was tapped and the keyboard shown. the tableView scrolled unnecessarily far down to accommodate for the too large height of the textView. I found that I could always return textHeight directly in -tableview: heightForRowAtIndexPath:. Then, the cell is always just as high as the textView. Of course, one could also define a minimum height for it. Also, don't forget to initialize textHeight with a reasonable value in -viewDidLoad.
  • Joris Weimar
    Joris Weimar over 12 years
    i've very quickly implemented some excerpts of your code in my tableviewcontroller and im having no problems at all. i'm running ipad simulator on ios 5. are you still having problems? should i post some of my code?
  • Joris Weimar
    Joris Weimar over 12 years
    Unfortunately this method only works if you have one cell that contains a UITextView.
  • Florian Mielke
    Florian Mielke over 12 years
    Of course! That's what the question is about: "UITextView in a UITableViewCell ..."
  • Julian
    Julian over 11 years
    Actually this works fine with multiple cells containing UITextViews. You just have to track a few more things. The key point is the tableViewNeedsToUpdateHeight method which allows you to make changes to the row height without losing focus (and thus the keyboard).
  • Nate Cook
    Nate Cook almost 11 years
    At the beginning of your instructions you mention UITextField. That should actually say UITextView, correct?