UIView на клавиатуре похож на iMessage App
В настоящее время я пытаюсь в основном реализовать и точную копию приложения Apple iMessage.
Это означает, что мне нужен UITextView, который состыкован в нижней части экрана и перемещается вверх, когда он становится первымResponder. - Это довольно легко. Там bazillion способы сделать это, и два из наиболее распространенных, конечно, оживляют представление вверх или вниз, если получено уведомление. Другой - сделать это через inputAccessoryView. К сожалению, некоторые из особенностей, которые есть у одного, другой нет. И они кажутся взаимоисключающими.
Большая проблема - вращение.
Я проработал примерно по крайней мере семь различных проектов github, все из которых перепрофилируют ту же функциональность/поведение, которую я пытаюсь достичь, но буквально все они терпят неудачу.
HPGrowingTextView, например, который использует официальное приложение Facebook/FacebookMessenger/(и, возможно, WhatsApp), является одним большим куском нежелательного кода. Возьмите iDevice, откройте приложение Facebook, зайдите в чат, нажмите клавиатуру и поверните устройство. Если вы обратите внимание, вы заметите, что входной бар слегка подпрыгивает и оставляет пустое пространство между рамкой клавиатуры и ее собственным. Затем взгляните на реализацию Apple в iMessage, когда отображается клавиатура. Это прекрасно.
Кроме того, что использование contentOffset и EdgeInset-взлома, которое использует библиотека HPGrowingTextView, дает мне кошмары.
Поэтому я хотел сделать это сам и начать с нуля.
Сейчас у меня очень тонкая, элегантная и безрадостная реализация растущего UITextView, но одна часть отсутствует.
Элегантное вращение.
Когда я просто настраиваю рамки на свои новые позиции в методе willRotateToInterfaceOrientation: duration:, все заканчивается отлично, но у меня та же проблема, что и HPGrowingTextView (см. приложение для Facebook). Небольшой пробел между входом и клавиатурой во время вращения.
Я узнал, что при повороте устройства в ландшафт, портретная клавиатура, которая в настоящее время отображается, не "морфирует", а скорее исчезает (отправляет уведомление "willHide" ), и появляется альбомная версия (отправка уведомления "willShow" ), Переход - очень тонкое исчезновение и, возможно, изменение размера.
Я снова реализовал свой проект, используя inputAccessoryView, чтобы узнать, что происходит потом, и я был приятно удивлен. InputAccessoryView вращается в идеальной синхронизации с клавиатурой. Между ними нет пробела/зазора.
К сожалению, мне еще предстоит придумать, как иметь док-станцию inputAccessoryView в нижней части экрана и НЕ исчезнуть/выйти из нее вместе с клавиатурой...
То, что я не хочу, это hack-y-решения, например,... "немного опустив кадр в системе координат toInterfaceOrientation CoordinateSystem, а затем переместив его обратно, когда вызывается didRotateFrom..."
Я знаю одно другое приложение, которому удалось реализовать такое поведение, и это "Kik Messenger".
Есть ли у кого-нибудь идея, совет или ссылка, которую я еще не видел, покрывая эту тему?
Спасибо, куча!
Примечание. Как только эта проблема будет решена, я открою исходный проект для всех, чтобы получить прибыль, потому что почти каждая реализация, которую я смог найти в течение последних нескольких дней, является беспорядком.
Ответы
Ответ 1
Мне удалось решить проблему довольно элегантно (я думаю,...).
Код будет выпущен на Github на следующей неделе и связан с этим ответом.
-
Как это делается: я сделал работу по вращению, выбирая метод inputAccessoryView.
Обозначения:
- 'MessageInputView' - это UIView, содержащий мой "GrowingUITextView" (он также содержит кнопку "Отправить" и фоновое изображение).
- "ChatView" - это вид, который принадлежит ChatViewController, который отображает все Chatbubbles и имеет мой "MessageInputView", состыкованный внизу.
- 'keyboardAccessoryView' - это пустой размер UIView: CGRect (0,0,0,0).
Мне нужно было выяснить, как включить MessageInputView на экране, когда клавиатура была уволена. Это была сложная часть. Я сделал это, создав другое представление (keyboardAccessoryView), и мой GrowingUITextView использовал его как свой входной экземпляр. Я сохранил клавиатуруAccessoryView, потому что мне понадобится ссылка на нее позже.
Затем я вспомнил некоторые вещи, которые я сделал в своей другой попытке (анимация кадров MessageInputView вокруг экрана всякий раз, когда пришло уведомление о клавиатуре).
Я добавил свой MessageInputView в качестве подзаголовка в свой ChatView (в самом низу). Всякий раз, когда он активируется, и методы willShow: вызывается с помощью уведомления клавиатуры, я вручную анимирую рамку MessageInputView, чтобы она обозначала позицию вверху. Когда анимация заканчивается и выполняется блок завершения, я удаляю subview из ChatView и добавляю его в клавиатуру AccessView. Это приводит к отключению другого уведомления, потому что клавиатура перезагружается КАЖДОЕ время, когда изменяются рамки/границы вводаAccessoryView!. Вы должны знать об этом и обращаться с ним соответствующим образом!
Когда клавиатура собирается уволить, я конвертирую свой кадр MessageInputView в мою систему координат ChatView и добавляю ее как подвью. Таким образом, он удаляется из моей клавиатурыAccessoryView. Затем я изменю размер рамки keyboardAccessoryView на CGRect (0,0,0,0), потому что иначе UIViewAnimationDuration не будет соответствовать! Затем я разрешаю клавиатуре отклоняться, и мой MessageInputView следует за ней сверху и в конечном итоге состыковывается в нижней части экрана.
Это довольно много работы для очень небольшого выигрыша.
-
Позаботьтесь.
PS: Если кто-то выяснит более простой способ сделать это (отлично), дайте мне знать.
Ответ 2
Недавно я столкнулся с одной и той же проблемой и должен был разработать собственное решение, поскольку я не был полностью доволен доступными сторонними библиотеками. Я отделил эту реализацию от своего проекта GitHub:
MessageComposerView
Из некоторого простого тестирования на iOS 6.1 7 и 8 симуляторов вращения, похоже, правильно следуют за клавиатурой. Представление также будет расти с текстом и автоматически изменяться при повороте.
Вы можете использовать очень базовую функцию init
, чтобы создать ее с шириной экрана и высотой по умолчанию, например:
self.messageComposerView = [[MessageComposerView alloc] init];
self.messageComposerView.delegate = self;
[self.view addSubview:self.messageComposerView];
Существует несколько других инициализаторов, которые также позволяют настраивать фрейм, смещение клавиатуры и максимальную высоту текста. Подробнее см. Readme!
Ответ 3
Здесь подкласс UITextView
, который работает правильно на iOS 9.3.1 и 8.3.1. Он заботится о том, чтобы расти и сокращаться с ограничениями, сохраняя каретку всегда в нужном месте и плавно анимируя.
Наложение вида на клавиатуру тривиально, и многие решения можно легко найти, поэтому они не покрываются...
Я не смог найти готовые решения, готовые к выпуску, поэтому я закончил работу с нуля. На этом пути мне пришлось решить немало проблем.
Комментарии кодов должны дать вам представление о том, что происходит.
Я поделился этим с мой Github, взносы получили высокую оценку.
Примечания
- Не тестируется для поддержки ландшафта
- Не проверен на i6 +
Demo
(после того, как элемент max height станет прокручиваемым. Забыл перетащить демоверсию, но это работает так же, как и ожидалось...)
Подкласс
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)
}
Ответ 4
Недавно я написал сообщение в блоге об этой точной проблеме, которую вы описали, и о том, как ее решить коротким и элегантным способом, используя клавиатурные уведомления, но не используя inputAccessoryView
. И хотя этот вопрос довольно старый, эта тема по-прежнему актуальна, поэтому вот ссылка на сообщение: Синхронизация анимации вращения между клавиатурой и прикрепленным представлением
Если вы не хотите погружаться в длинное объяснение, описанное в блоге, здесь приводится краткое описание с примером кода:
Основным принципом является использование того же метода, который каждый использует - наблюдения за уведомлениями клавиатуры, чтобы оживить прикрепленное представление вверх и вниз. Но в дополнение к этому, вы должны отменить эти анимации, когда уведомления о клавиатуре запускаются из-за изменения ориентации интерфейса.
Пример вращения без отмены анимации при изменении ориентации интерфейса:
Пример поворота с отменой анимации при изменении ориентации интерфейса:
- (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;
}
}
Ответ 5
Как я исправляю эту проблему для меня:
У меня 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
}
Другой способ - создать представление программно и вернуться в качестве inputAccessoryView.