7

My code was working fine under iOS 6, but under iOS 7 I can't seem to make my UITextView the height of the device's screen minus the keyboard (in other words, when the keyboard is up, have the UITextView still be fullscreen, but not go under the keyboard).

For one, when I put the UITextView in my view controller (which is embedded in a navigation controller) it has to be under the nav bar as well, otherwise it starts too far down.

From there I tried all of these examples:

self.textView.contentInset = UIEdgeInsetsMake(0, 0, 230, 0);
self.textView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, 230, 0);
self.textView.textContainerInset = UIEdgeInsetsMake(0, 0, 230, 0);

Where in each the keyboard still goes over the textview at some points. I also tried setting a height constraint and manipulating the constant, but no luck.

self.height.constant = self.height.constant - 240.0;

(Where height is the constraint's outlet.)

These have all been tried before and after a [self.textField becomeFirstResponder]; call.

How am I to accomplish this? I just want a full screen UITextView where the caret thing won't go under the keyboard, but it such basic functionality seems crazy under iOS 7.

Doug Smith
  • 29,668
  • 57
  • 204
  • 388

2 Answers2

3

The "correct" way to do this pre-iOS7 has always been to adjust the contentInset property of the UITextView (a UIScrollView subclass) when the keyboard shows or hides. I haven't investigated personally but this appears not to work well in iOS7 because the bottom inset is either not honored or there is some issue with the cursor still going below the keyboard. See this question for reference.

In your case where you're using auto-layout, and all you want is a "full screen" text view you can simply adjust a single constraint when the keyboard shows or hides. This will adjust the height of your text view:

@implementation TSViewController
{
    IBOutlet NSLayoutConstraint* _textViewSpaceToBottomConstraint;
}

- (void) dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver: self];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

- (void) keyboardWillShow: (NSNotification*) n
{
    NSValue* bv = n.userInfo[UIKeyboardFrameEndUserInfoKey];
    CGRect br = [bv CGRectValue];

    _textViewSpaceToBottomConstraint.constant = br.size.height;
}

- (void) keyboardWillHide: (NSNotification*) n
{
    _textViewSpaceToBottomConstraint.constant = 0;
}

@end

In your storyboard, drop your UITextView on your view-controller view and add 4 constraints to glue its edges to the edges of the view-controller view. Connect the bottom space constraint to the IBOutlet _textViewSpaceToBottomConstraint in the view controller.

You can likely play with this a bit and adjust the size inside an animation block, borrowing the animation time and curve from the keyboard notification.

I'd be interested in seeing a version of this that sets the contentInset and has it work properly...

enter image description here

EDIT

Here is another SO question that covers this topic, with a solution. Apparently the issue with the caret (cursor) going out of frame is the same issue I mentioned above re. the "correct" way to do this, which is to set the contentInset property. So a fix for this problem should enable you to simply adjust the contentInset vs. changing the text view frame (via .frame or via a constraint).

EDIT 2

One last thought on this. In iOS7, the keyboard is translucent. The user should be able to see content behind the keyboard. By resizing the UITextView to be above the keyboard, this won't ever happen. So the "correct" solution is still to adjust the contentInset for the bottom of the text view, to add the height of the keyboard to the scrollable area of the text view. Then, in an ideal world, you would adjust the contentOffset of the text view to keep the caret in view when the keyboard appears. Finally, add to that the 'fix' to keep the caret in position above the keyboard when it's displayed but the user is entering newlines.

Community
  • 1
  • 1
TomSwift
  • 39,369
  • 12
  • 121
  • 149
  • Using this solution it ends up like this when I type a certain amount: http://cl.ly/image/2A320J3y2d3A – Doug Smith Nov 04 '13 at 18:38
  • This is apparently an iOS7 bug. See this question: http://stackoverflow.com/questions/19259886/uitextview-cursor-not-positioning-properly-when-editing-in-ios-7-why I dropped the workaround into your sample and it fixes it. – TomSwift Nov 04 '13 at 19:37
  • Check out this question as well. It covers the caret-out-of-frame issue, as well as combining the solution with the contentInset approach (vs. changing the text view frame.): http://stackoverflow.com/questions/18431684/uitextview-cursor-below-frame-when-changing-frame – TomSwift Nov 04 '13 at 19:51
  • That second solution seemed like a ton more work for the same result as the first, so I went with the first and it worked well. Cheers. :) (Will award bounty when it lets me.) – Doug Smith Nov 04 '13 at 20:01
  • That only works part way... yes, it does keep the text above the keyboard, but rotate the device, and it does strange things. Text is now confined to the center of the view. I had this issue in iOS6 and found a fix that worked in all orientations. – Marc Watson Nov 05 '13 at 08:31
  • Try this to handle different orientations: `_textViewSpaceToBottomConstraint.constant = UIInterfaceOrientationIsPortrait(self.interfaceOrientation) ? br.size.height : br.size.width;` – fal Jan 16 '14 at 21:25
1

The solution to this in iOS7 is to put the tableview into a container view, which becomes your primary view. You can add constraints to it so that its attached to the topLayoutGuide. Add a second placeholder view that is constrained to the bottom of the table on top, and to the bottomLayoutConstraint on the bottom. Add a constraint to this placeholder view so its height is 0, and keep an ivar pointing to it. You can provide a UITableViewController subclass that overrides the tableView property with the real tableview.

When the keyboard is ready to pop, you can get the height of the table from the notification (code below), and animate the setting of the constraint property of the placeholder view to the height of the keyboard.

The keyboard notification code is below:

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

    NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
    [defaultCenter addObserver:self selector:@selector(keyboardMoving:) name:UIKeyboardWillShowNotification object:nil];
    [defaultCenter addObserver:self selector:@selector(keyboardMoving:) name:UIKeyboardWillHideNotification object:nil];
    [defaultCenter addObserver:self selector:@selector(keyboardMoving:) name:UIKeyboardDidHideNotification object:nil];
}

-(void)viewWillDisappear:(BOOL)animated
{
    NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
    [defaultCenter removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [defaultCenter removeObserver:self name:UIKeyboardWillHideNotification object:nil];
    [defaultCenter removeObserver:self name:UIKeyboardDidHideNotification object:nil];

    [super viewWillDisappear:animated];
}
- (void)keyboardMoving:(NSNotification *)note
{
    NSString *msg = note.name;

    if([msg isEqualToString:UIKeyboardWillShowNotification] && !_keyboardUp) {
        _keyboardUp = YES;
        [[note.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&_animationDuration];
        [[note.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&_keyboardRect];
        NSLog(@"ORIG KEYBOARD %@", NSStringFromCGRect(_keyboardRect));
        _keyboardRect = [self.view convertRect:_keyboardRect fromView:nil];
        NSLog(@"NEW KEYBOARD %@", NSStringFromCGRect(_keyboardRect));
        _animate = YES;
    } else
    if([msg isEqualToString:UIKeyboardWillHideNotification] && _keyboardUp) {
        _keyboardUp = NO;
        _animate = YES;
    } else
    if([msg isEqualToString:UIKeyboardDidHideNotification]) {
        _keyboardUp = NO;
        _animate = NO;
    }
}

- (BOOL)isKeyboardMovingUp
{
    return _keyboardUp == YES && _animate == YES;
}
- (BOOL)isKeyboardMovingDown
{
    return _keyboardUp == NO && _animate == YES;
}

- (BOOL)isKeyboardDown
{
    return _keyboardUp == NO && _animate == NO;
}

What you will need to do is before the keyboard pops, or just always, create a empty container view and add it to , add the table to that view, add

David H
  • 40,852
  • 12
  • 92
  • 138
  • If I understand your code correctly you're manipulating rects, right? iOS has pushed Auto Layout very hard lately, so I'd prefer to use a solution involving that. – Doug Smith Oct 28 '13 at 13:24
  • So you can get the animation speed and size of the keyboard. Lets assume your UITextView is in the primary vc.view. Add a constraint at the bottom, with a height of 0 between the bottom of the textView and the superview. When the keyboard pops, create a UIView animation of the same time period as the keyboard pops, and set the bottom constraint constant to the height of the keyboard. Voila! Now your UITextView will compress at the same rate as the keyboard pops. – David H Oct 28 '13 at 17:16
  • I am having the same issue is similar to Doug's... everything worked fine in iOS6 and now it doesn't automatically scroll up. My difference is that I am using Notifications. I can manually scroll though. You have a variable noted... _keyboardUp... what kind of variable is it? Just a BOOL? – Marc Watson Oct 29 '13 at 13:07
  • @MarcWatson yes - just a bool ivar to hold state. – David H Oct 29 '13 at 13:09
  • I don't see at what point in this code the textview gets manipulated? – Doug Smith Nov 04 '13 at 15:40