Как изменить размер UITextView, когда клавиатура показана в iOS 7

У меня есть контроллер просмотра, который содержит полный экран UITextView, Когда клавиатура отображается, я хотел бы изменить размер текстового представления, чтобы оно не было скрыто под клавиатурой.

Это довольно стандартный подход с iOS, как описано в этом вопросе:

Как изменить размер UITextView на iOS при появлении клавиатуры?

Однако в iOS 7, если пользователь нажимает на текстовое представление в нижней половине экрана, когда текстовое представление изменяется, курсор остается за кадром. Текстовое представление прокручивается только для отображения курсора, если пользователь нажимает клавишу ввода.

8 ответов

Решение

Хотя ответ, данный @Divya, привел меня к правильному решению (поэтому я получил награду), это не очень четкий ответ! Вот это подробно:

Стандартный подход к обеспечению того, чтобы текстовое представление не было скрыто экранной клавиатурой, заключается в обновлении его фрейма при отображении клавиатуры, как подробно описано в этом вопросе:

Как изменить размер UITextView на iOS при появлении клавиатуры?

Однако в iOS 7, если вы измените рамку просмотра текста в вашем обработчике для UIKeyboardWillShowNotification Уведомление, курсор будет оставаться вне экрана, как описано в этом вопросе.

Решение этой проблемы - изменить рамку текстового представления в ответ на textViewDidBeginEditing метод делегата вместо:

@implementation ViewController {
    CGSize _keyboardSize;
    UITextView* textView;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    textView = [[UITextView alloc] initWithFrame:CGRectInset(self.view.bounds, 20.0, 20.0)];    textView.delegate = self;
    textView.returnKeyType = UIReturnKeyDone;
    textView.backgroundColor = [UIColor greenColor];
    textView.textColor = [UIColor blackColor];
    [self.view addSubview:textView];


    NSMutableString *textString = [NSMutableString new];
    for (int i=0; i<100; i++) {
        [textString appendString:@"cheese\rpizza\rchips\r"];
    }
    textView.text = textString;

}

- (void)textViewDidBeginEditing:(UITextView *)textView1 {
    CGRect textViewFrame = CGRectInset(self.view.bounds, 20.0, 20.0);
    textViewFrame.size.height -= 216;
    textView.frame = textViewFrame;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    CGRect textViewFrame = CGRectInset(self.view.bounds, 20.0, 20.0);
    textView.frame = textViewFrame;
    [textView endEditing:YES];
    [super touchesBegan:touches withEvent:event];
}

@end

ПРИМЕЧАНИЕ: к сожалению textViewDidBeginEdting пожары перед UIKeyboardWillShowNotification уведомление, следовательно, необходимо жестко кодировать высоту клавиатуры.

Я читаю документы, которые говорят об этой самой теме. Я перевел его на Свифт, и он прекрасно сработал для меня.

Это используется для полной страницы UITextView, как iMessage.

Я использую iOS 8.2 и Swift на XCode 6.2, и вот мой код. Просто позвони setupKeyboardNotifications от твоего viewDidLoad или другой метод инициализации.

func setupKeyboardNotifications() {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)
}

func keyboardWasShown(aNotification:NSNotification) {
    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as NSValue
    let kbSize = infoNSValue.CGRectValue().size
    let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
    codeTextView.contentInset = contentInsets
    codeTextView.scrollIndicatorInsets = contentInsets
}

func keyboardWillBeHidden(aNotification:NSNotification) {
    let contentInsets = UIEdgeInsetsZero
    codeTextView.contentInset = contentInsets
    codeTextView.scrollIndicatorInsets = contentInsets
}

Также, если у вас есть проблемы с тем, что каретка находится в нужном месте при повороте, проверьте изменение ориентации и прокрутите до нужного положения.

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
    scrollToCaretInTextView(codeTextView, animated: true)
}

func scrollToCaretInTextView(textView:UITextView, animated:Bool) {
    var rect = textView.caretRectForPosition(textView.selectedTextRange?.end)
    rect.size.height += textView.textContainerInset.bottom
    textView.scrollRectToVisible(rect, animated: animated)
}

Свифт 3:

func configureKeyboardNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(aNotification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(aNotification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}

func keyboardWasShown(aNotification:NSNotification) {
    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
    let kbSize = infoNSValue.cgRectValue.size
    let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
    textView.contentInset = contentInsets
    textView.scrollIndicatorInsets = contentInsets
}

func keyboardWillBeHidden(aNotification:NSNotification) {
    let contentInsets = UIEdgeInsets.zero
    textView.contentInset = contentInsets
    textView.scrollIndicatorInsets = contentInsets
}

С Auto Layout намного проще (если вы понимаете Auto Layout) обрабатывать:

Вместо того, чтобы пытаться идентифицировать и изменять размеры затронутых представлений, вы просто создаете родительский фрейм для всего содержимого вашего представления. Затем, если появляется kbd, вы изменяете размер фрейма, и если вы правильно установили ограничения, представление будет корректно переупорядочивать все свои дочерние представления. Нет необходимости возиться с большим количеством трудно читаемого кода для этого.

На самом деле, в похожем вопросе я нашел ссылку на этот замечательный учебник по этой технике.

Кроме того, другие примеры здесь, которые действительно используют textViewDidBeginEditing вместо UIKeyboardWillShowNotification, имеют одну большую проблему:

Если к пользователю подключена внешняя клавиатура Bluetooth, то элемент управления все равно будет нажат, даже если на экране не появится клавиатура. Это не хорошо.

Итак, подведем итог:

  1. Использовать автоматическое расположение
  2. Используйте уведомление UIKeyboardWillShowNotification, а не события TextEditField для принятия решения о том, когда изменять размеры ваших представлений.

Кроме того, проверьте ответ LeoNatan. Это может быть даже более чистое и простое решение (я еще не пробовал себя).

Не изменяйте размер текстового представления. Вместо этого установите contentInset а также scrollIndicatorInsets снизу до высоты клавиатуры.

Смотрите мой ответ здесь: /questions/45930375/uitextview-kursor-nizhe-kadra-pri-smene-kadra/45930497#45930497


редактировать

Я внес следующие изменения в ваш пример проекта:

- (void)textViewDidBeginEditing:(UITextView *)textView
{
    _caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(_scrollCaretToVisible) userInfo:nil repeats:YES];
}

- (void)_scrollCaretToVisible
{
    //This is where the cursor is at.
    CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];

    if(CGRectEqualToRect(caretRect, _oldRect))
        return;

    _oldRect = caretRect;

    //This is the visible rect of the textview.
    CGRect visibleRect = self.textView.bounds;
    visibleRect.size.height -= (self.textView.contentInset.top + self.textView.contentInset.bottom);
    visibleRect.origin.y = self.textView.contentOffset.y;

    //We will scroll only if the caret falls outside of the visible rect.
    if(!CGRectContainsRect(visibleRect, caretRect))
    {
        CGPoint newOffset = self.textView.contentOffset;

        newOffset.y = MAX((caretRect.origin.y + caretRect.size.height) - visibleRect.size.height + 5, 0);

        [self.textView setContentOffset:newOffset animated:NO];
    }
}

Поначалу убрана установка старой позиции каретки, а также отключена анимация. Теперь, кажется, работает хорошо.

Следующее на работает на меня:

.h файл

@interface ViewController : UIViewController <UITextViewDelegate> {

    UITextView *textView ;

}

@property(nonatomic,strong)IBOutlet UITextView *textView;

@end

.m файл

@implementation ViewController
@synthesize textView;
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 424.0f);
    //UITextView *textView = [[UITextView alloc] initWithFrame:textViewFrame];
    textView.frame = textViewFrame;
    textView.delegate = self;
    textView.returnKeyType = UIReturnKeyDone;
    textView.backgroundColor = [UIColor greenColor];
    textView.textColor = [UIColor blackColor];
    [self.view addSubview:textView];

}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
    NSLog(@"textViewShouldBeginEditing:");
    return YES;
}
- (void)textViewDidBeginEditing:(UITextView *)textView1 {
    NSLog(@"textViewDidBeginEditing:");
   CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 224.0f);

    textView1.frame = textViewFrame;

}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView{
    NSLog(@"textViewShouldEndEditing:");
       return YES;
}
- (void)textViewDidEndEditing:(UITextView *)textView{
    NSLog(@"textViewDidEndEditing:");
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
       return YES;
}

- (void)textViewDidChange:(UITextView *)textView{
    NSLog(@"textViewDidChange:");
}

- (void)textViewDidChangeSelection:(UITextView *)textView{
    NSLog(@"textViewDidChangeSelection:");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"touchesBegan:withEvent:");
    CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 424.0f);

    textView.frame = textViewFrame;
    [self.view endEditing:YES];
    [super touchesBegan:touches withEvent:event];
}
@end

Я сделал это и его работу полностью.

  #define k_KEYBOARD_OFFSET 95.0

-(void)keyboardWillAppear {
    // Move current view up / down with Animation
    if (self.view.frame.origin.y >= 0)
    {
        [self moveViewUp:NO];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self moveViewUp:YES];
    }
}

-(void)keyboardWillDisappear {
    if (self.view.frame.origin.y >= 0)
    {
        [self moveViewUp:YES];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self moveViewUp:NO];
    }
}

-(void)textFieldDidBeginEditing:(UITextField *)sender
{
    //if ([sender isEqual:_txtPassword])
   // {
        //move the main view up, so the keyboard will not hide it.
        if  (self.view.frame.origin.y >= 0)
        {
            [self moveViewUp:YES];
        }
    //}
}

//Custom method to move the view up/down whenever the keyboard is appeared / disappeared
-(void)moveViewUp:(BOOL)bMovedUp
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.4]; // to slide the view up

    CGRect rect = self.view.frame;
    if (bMovedUp) {
        // 1. move the origin of view up so that the text field will come above the keyboard
        rect.origin.y -= k_KEYBOARD_OFFSET;

        // 2. increase the height of the view to cover up the area behind the keyboard
        rect.size.height += k_KEYBOARD_OFFSET;
    } else {
        // revert to normal state of the view.
        rect.origin.y += k_KEYBOARD_OFFSET;
        rect.size.height -= k_KEYBOARD_OFFSET;
    }

    self.view.frame = rect;

    [UIView commitAnimations];
}

- (void)viewWillAppear:(BOOL)animated
{
    // register keyboard notifications to appear / disappear the keyboard
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillAppear)
                                                 name:UIKeyboardWillShowNotification
                                               object:nil];

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

- (void)viewWillDisappear:(BOOL)animated
{
    // unregister for keyboard notifications while moving to the other screen.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillShowNotification
                                                  object:nil];

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillHideNotification
                                                  object:nil];
}

@ Джонстон нашел хорошее решение. Вот вариант с использованием UIKeyboardWillChangeFrameNotification который правильно учитывает изменения размера клавиатуры (т.е. показывает / скрывает панель QuickType). Он также правильно обрабатывает случай, когда текстовое представление встроено в контроллер навигации (т. Е. Когда contentInset иначе не ноль). Это также написано в Swift 2.

override func viewDidLoad() {
    :

    NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillChangeFrameNotification, object: nil, queue: nil) { (notification) -> Void in
        guard let userInfo = notification.userInfo,
            let keyboardFrameEndValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
            else { return }

        let windowCoordinatesKeyboardFrameEnd = keyboardFrameEndValue.CGRectValue() // window coordinates
        let keyboardFrameEnd = self.view.convertRect(windowCoordinatesKeyboardFrameEnd, fromView: nil) // view coordinates

        var inset = self.textView.contentInset
        inset.bottom = CGRectGetMaxY(self.textView.frame) - CGRectGetMinY(keyboardFrameEnd) // bottom inset is the bottom of textView minus top of keyboard
        self.textView.contentInset = inset
        self.textView.scrollIndicatorInsets = inset
    }
}

Это мое решение, июль 2015 года с использованием Swift 1.2 на Xcode 6.4 для iOS 7.1 - комбинация нескольких подходов. Заимствовал клавиатуру Johnston, передающую код Swift. Это немного взломать, но это просто, и это работает.

У меня есть ванильный UITextView внутри одного представления.

Я не хотел вставлять его в UIScrollView в соответствии с документацией Apple. Я просто хотел, чтобы UITextView был изменен в размерах, когда появилась программная клавиатура, и изменил размеры до оригинального, когда клавиатура была закрыта.

Это основные шаги:

  1. Настройте уведомления клавиатуры
  2. Установите ограничение макета в "Интерфейсном Разработчике" (TextView к нижнему краю в моем случае)
  3. Создайте IBOutlet для этого ограничения в соответствующем файле кода, чтобы вы могли настроить его программно
  4. Используйте клавиатурные уведомления для перехвата событий и получения размера клавиатуры
  5. Программно настроить ограничение IBOutlet, используя размер клавиатуры, чтобы изменить размер TextView.
  6. Положите все обратно, когда клавиатура распущена.

Итак, на код.

Я установил вывод ограничений в верхней части файла кода с помощью обычного перетаскивания в конструкторе интерфейса: @IBOutlet weak var myUITextViewBottomConstraint: NSLayoutConstraint!

Я также установил глобальную переменную, в которой я могу сделать резервную копию состояния до появления клавиатуры: var myUITextViewBottomConstraintBackup: CGFloat = 0

Реализуйте уведомления клавиатуры, вызовите эту функцию в viewDidLoad или любом другом разделе запуска / настройки:

func setupKeyboardNotifications() {

    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)

    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)

    }

Затем эти две функции будут вызваны автоматически, когда клавиатура будет показана / отклонена:

func keyboardWasShown(aNotification:NSNotification) {

    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
    let kbSize = infoNSValue.CGRectValue().size

    let newHeight = kbSize.height

    //backup old constraint size
    myUITextViewBottomConstraintOld = myUITextViewBottomConstraint.constant 

    // I subtract 50 because otherwise it leaves a gap between keyboard and text view. I'm sure this could be improved on.
    myUITextViewBottomConstraint.constant = newHeight - 50 

func keyboardWillBeHidden(aNotification:NSNotification) {
    //restore to whatever AutoLayout set it before you messed with it
    myUITextViewBottomConstraint.constant = myUITextViewBottomConstraintOld 

}

Код работает, с незначительной проблемой:

  • Он не реагирует на интеллектуальную текстовую ленту над открытием / закрытием клавиатуры. Т.е. при вызове клавиатуры будет приниматься во внимание его состояние, но если вы будете двигать ее вверх или вниз, пока отображается клавиатура, ограничение не будет изменено. Это отдельное событие, которое необходимо обработать. Его недостаточно для того, чтобы беспокоиться о функциональности.
Другие вопросы по тегам