UIView поверх клавиатуры, похожей на приложение iMessage - PullRequest
26 голосов
/ 11 декабря 2011

В настоящее время я пытаюсь реализовать и точную копию Apples iMessage App.

Это означает, что мне нужен UITextView, который закреплен в нижней части экрана и перемещается вверх, когда он становится firstResponder. - Это довольно легко на самом деле. Существует несколько способов сделать это, и два из наиболее распространенных - это, конечно, анимация представления вверх или вниз, если получено уведомление . Другой - сделать это через inputAccessoryView . К сожалению, некоторые функции у одного есть, у другого нет. И они кажутся взаимоисключающими.

Большая проблема - вращение .

Я пробежал примерно по крайней мере семь различных проектов github, каждый из которых повторно реализовывал ту же функциональность / поведение, которого я пытаюсь достичь, но буквально все они терпели неудачу с треском.

Например, HPGrowingTextView, который используют официальные приложения Facebook / FacebookMessenger / (и, возможно, WhatsApp), представляет собой большой кусок мусорного кода. Возьмите iDevice, откройте приложение Facebook, перейдите в чат, нажмите на клавиатуру и поверните устройство. Если вы обратите внимание, вы заметите, что панель ввода слегка подпрыгивает и оставляет некоторое пространство между рамкой клавиатуры и ее собственной. Затем посмотрите на реализацию Apples в iMessage, когда клавиатура отображается. Это прекрасно.

Кроме этого, взлом содержимогоOffset и EdgeInset, которые использует библиотека HPGrowingTextView, дает мне кошмары.

Так что я хотел сделать это сам и начать с нуля.

Прямо сейчас у меня есть очень изящная, элегантная и без взлома реализация растущего UITextView, но одна часть отсутствует.

Элегантное вращение.

Когда я просто подгоняю фреймы к их соответствующим новым позициям в методе willRotateToInterfaceOrientation: duration: все работает идеально, НО у меня та же проблема, что и у HPGrowingTextView (см. Приложение Facebook). Небольшой промежуток между входным обзором и клавиатурой во время вращения.

Я обнаружил, что при повороте устройства в альбомную ориентацию портретная клавиатура, которая отображается в данный момент, не «морфируется», а скорее исчезает (отправляет уведомление «willHide»), и вновь появляется альбомная версия (отправляя уведомление «willShow») , Переход является очень тонким исчезновением и, возможно, некоторым изменением размера.

Я заново реализовал свой проект, используя inputAccessoryView, чтобы увидеть, что происходит потом, и я был приятно удивлен. InputAccessoryView вращается в идеальной синхронизации с клавиатурой. Там нет пробела / промежутка между ними.

К сожалению, мне еще не пришла в голову идея, как разместить док-станцию ​​inputAccessoryView в нижней части экрана и NOT исчезнуть / выйти из нее вместе с клавиатурой ...

То, что я не хочу, - это хакерские решения, такие как ... "немного уменьшив фрейм в CoordinateSystem toInterfaceOrientation, а затем переместив его обратно вверх, когда вызывается didRotateFrom ...".

Я знаю еще одно приложение, которому удалось реализовать такое поведение, и это "Kik Messenger".

У кого-нибудь есть идея, совет или ссылка, которую я еще не видел по этой теме?

Спасибо большое!

Примечание: Как только эта проблема будет решена, я открою исходный код проекта для всех, чтобы получить прибыль, потому что почти каждая реализация, которую я смог найти за последние несколько дней, - беспорядок.

Ответы [ 5 ]

11 голосов
/ 16 ноября 2013

Недавно я столкнулся с той же проблемой, и мне пришлось разработать собственное решение, поскольку я не был полностью доволен доступными сторонними библиотеками.Я разделил эту реализацию на собственный проект GitHub:

MessageComposerView

Из простого тестирования на iOS 6.1 7 и 8 симуляторы, кажется, что вращения правильно следуют за клавиатурой.Представление также будет расти вместе с текстом и автоматически изменять его размер при повороте.

MessageComposerView

Вы можете использовать очень простую функцию init, например, чтобы создать ее с шириной экрана и высотой по умолчанию, например:

self.messageComposerView = [[MessageComposerView alloc] init];
self.messageComposerView.delegate = self;
[self.view addSubview:self.messageComposerView];

Существует также несколько других инициализаторов, которые позволяют настроить рамку, смещение клавиатуры и максимальную высоту просмотра текста.Смотрите readme для больше!

5 голосов
/ 12 декабря 2011

Мне удалось решить проблему довольно элегантным образом (я думаю, ...).

Код будет выпущен на Github на следующей неделе и связан с этим ответом.

-

Как это сделано: я сделал вращение, выбрав inputAccessoryView-способ сделать это.

Номенклатура:

  1. 'MessageInputView' - это UIView, содержащий мой 'GrowingUITextView' (он также содержит кнопку "Отправить" ифоновое изображение).
  2. 'ChatView' - это представление, которое принадлежит ChatViewController, которое отображает все Chatbubbles и имеет мой 'MessageInputView' , прикрепленный внизу.
  3. 'keyboardAccessoryView' - пустой размер UIView: CGRect (0,0,0,0).

Мне нужно было выяснить, как получить MessageInputView остается на экране, когда клавиатура была закрыта.Это была сложная часть.Я сделал это, создав другое представление ( keyboardAccessoryView ), и мой GrowingUITextView использовал его в качестве своего inputAccessoryView.Я сохранил keyboardAccessoryView , потому что позже мне понадобится ссылка на него.

Затем я вспомнил кое-что из того, что сделал в другой попытке (анимация MessageInputView создает рамки вокруг экрана всякий раз, когда поступает уведомление от клавиатуры).

Я добавил свой MessageInputView в качестве подпредставления к моему ChatView (в самом низу).Всякий раз, когда он активируется и методы willShow: вызываются уведомлением с клавиатуры, я вручную анимирую кадр MessageInputView в его назначенную позицию вверху.Когда анимация заканчивается и блок завершения выполняется, я удаляю подпредставление из ChatView и добавляю его в keyboardAccessoryView .Это приводит к тому, что другое уведомление запускается, потому что клавиатура перезагружается КАЖДЫЙ раз, когда изменяется рамка / границы inputAccessoryView !.Вы должны знать об этом и обращаться с ним надлежащим образом!

Когда клавиатура скоро будет закрыта, я конвертирую кадр MessageInputView в мой ChatView .Система координат и добавить его в качестве подпредставления.Таким образом он удаляется из моей клавиатуры AccessoryView .Затем я изменяю размер кадра keyboardAccessoryView до CGRect (0,0,0,0), потому что в противном случае UIViewAnimationDuration не будет совпадать!Затем я позволяю отклонить клавиатуру, и мой MessageInputView следует за ним сверху и в итоге закрепляется в нижней части экрана.

Это довольно много работы для очень небольшого усиленияхотя.

-

Берегите себя.

PS: Если кто-то найдет более простой способ сделать это (идеально), дайте мне знать.

2 голосов
/ 15 апреля 2016

Вот подкласс UITextView, который правильно работает на iOS 9.3.1 и 8.3.1 . Он заботится о том, чтобы расти и сжиматься с ограничениями, сохраняя каретку всегда в нужном месте и плавно анимируя.

Накладывать вид на клавиатуру тривиально, так как многие решения легко найти, поэтому она не покрыта ...

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

Комментарии к коду должны дать вам представление о том, что происходит.

Я поделился этим на моем Github , Вклад с благодарностью.

Примечания

  • Не протестировано для поддержки ландшафта
  • Не тестировался на i6 +

Демо

enter image description here

(после того, как элемент максимальной высоты становится прокручиваемым. Забыл перетащить демонстрацию, но это также работает как ожидалось ...)

Подкласс

class ruuiDynamicTextView: UITextView {
    var dynamicDelegate: ruuiDynamicTextViewDelegate?
    var minHeight: CGFloat!
    var maxHeight: CGFloat?
    private var contentOffsetCenterY: CGFloat!

    init(frame: CGRect, offset: CGFloat = 0.0) {
        super.init(frame: frame, textContainer: nil)
        minHeight = frame.size.height

        //center first line
        let size = self.sizeThatFits(CGSizeMake(self.bounds.size.width, CGFloat.max))
        contentOffsetCenterY = (-(frame.size.height - size.height * self.zoomScale) / 2.0) + offset

        //listen for text changes
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(textChanged), name: UITextViewTextDidChangeNotification, object: nil)

        //update offsets
        layoutSubviews()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        //Use content size if more than min size, compensate for Y offset
        var height = max(self.contentSize.height - (contentOffsetCenterY * 2.0), minHeight)
        var updateContentOffsetY: CGFloat?
        //Max Height
        if maxHeight != nil && height > maxHeight {
            //Cap at maxHeight
            height = maxHeight!
        } else {
            //constrain Y to prevent odd skip and center content to view.
            updateContentOffsetY = contentOffsetCenterY
        }
        //update frame if needed & notify delegate
        if self.frame.size.height != height {
            self.frame.size.height = height
            dynamicDelegate?.dynamicTextViewDidResizeHeight(self, height: height)
        }
        //constrain Y must be done after setting frame
        if updateContentOffsetY != nil {
            self.contentOffset.y = updateContentOffsetY!
        }
    }

    func textChanged() {
        let caretRect = self.caretRectForPosition(self.selectedTextRange!.start)
        let overflow = caretRect.size.height + caretRect.origin.y - (self.contentOffset.y + self.bounds.size.height - self.contentInset.bottom - self.contentInset.top)
        if overflow > 0 {
            //Fix wrong offset when cursor jumps to next line un explisitly
            let seekEndY = self.contentSize.height - self.bounds.size.height
            if self.contentOffset.y != seekEndY {
                self.contentOffset.y = seekEndY
            }
        }
    }
}

protocol ruuiDynamicTextViewDelegate {
    func dynamicTextViewDidResizeHeight(textview: ruuiDynamicTextView, height: CGFloat)
}
0 голосов
/ 27 мая 2017

Как мне решить эту проблему для меня:

У меня есть ChatViewController и FooterViewController как UIContainerView. Кроме того, у меня есть выход contentView в FooterViewController. Тогда в ChatViewController у меня есть:

override func becomeFirstResponder() -> Bool {
    return true
}

override var inputAccessoryView: UIView? {
    if let childViewController = childViewControllers.first as? FooterViewController {
        childViewController.contentView.removeFromSuperview()
        return childViewController.contentView
    }
    return nil
}

enter image description here

Другой способ - создать представление программно и вернуть как inputAccessoryView.

0 голосов
/ 06 октября 2013

Недавно я написал сообщение в блоге об этой конкретной проблеме, которую вы описали, и о том, как решить ее коротким и элегантным способом с помощью клавиатурных уведомлений, но без использования inputAccessoryView. И хотя этот вопрос довольно старый, эта тема все еще актуальна, поэтому вот ссылка на пост: Синхронизация анимации вращения между клавиатурой и приложенным представлением

Если вы не хотите углубляться в подробное объяснение, описанное в сообщении в блоге, вот краткое описание с примером кода:

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

Пример поворота без отмены анимации, настраиваемой при изменении ориентации интерфейса: image

Пример поворота с отменой анимации при изменении ориентации интерфейса: image

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

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

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

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

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    self.animatingRotation = YES;
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    self.animatingRotation = NO;
}

- (void)adjustViewForKeyboardNotification:(NSNotification *)notification {
    NSDictionary *notificationInfo = [notification userInfo];

    // Get the end frame of the keyboard in screen coordinates.
    CGRect finalKeyboardFrame = [[notificationInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];

    // Convert the finalKeyboardFrame to view coordinates to take into account any rotation
    // factors applied to the window’s contents as a result of interface orientation changes.
    finalKeyboardFrame = [self.view convertRect:finalKeyboardFrame fromView:self.view.window];

    // Calculate new position of the commentBar
    CGRect commentBarFrame = self.commentBar.frame;
    commentBarFrame.origin.y = finalKeyboardFrame.origin.y - commentBarFrame.size.height;

    // Update tableView height.
    CGRect tableViewFrame = self.tableView.frame;
    tableViewFrame.size.height = commentBarFrame.origin.y;

    if (!self.animatingRotation) {
        // Get the animation curve and duration
        UIViewAnimationCurve animationCurve = (UIViewAnimationCurve) [[notificationInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
        NSTimeInterval animationDuration = [[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];

        // Animate view size synchronously with the appearance of the keyboard. 
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:animationDuration];
        [UIView setAnimationCurve:animationCurve];
        [UIView setAnimationBeginsFromCurrentState:YES];

        self.commentBar.frame = commentBarFrame;
        self.tableView.frame = tableViewFrame;

        [UIView commitAnimations];
    } else {
        self.commentBar.frame = commentBarFrame;
        self.tableView.frame = tableViewFrame;
    }
}
...