Ответ 1
Обычно есть два подхода, которые вы можете использовать, чтобы держать над клавиатурой вид, который он оживляет. Как вы знаете, первым является прослушивание UIKeyboardWillShowNotification
и использование сопутствующих значений продолжительности/кривой/кадра в userData, чтобы помочь вам позиционировать и анимировать ваш вид над клавиатурой.
Второй подход заключается в предоставлении inputAccessoryView
для представления (UITextField
, здесь), вызывающего клавиатуру. (я понимаю, что это не даст эффекта, о котором вы просите, то есть "толкнуть" панель инструментов/текстовое поле, как только клавиатура начнет работать в нее. Но об этом позже.) iOS будет родительский элемент inputAccessoryView к представлению, который также родит клавиатуру и оживляет их вместе. По моему опыту это обеспечивает лучшую анимацию. Я не думаю, что у меня когда-либо была анимация совершенная, использующая подход UIKeyboardWillShowNotification
, особенно сейчас в iOS7, где немного отскок в конце анимации клавиатуры. Вероятно, с UIKit Dynamics можно применить этот отскок и к вашему представлению, но сделать его идеально синхронизированным с клавиатурой было бы трудно.
Вот то, что я делал в прошлом для сценария, подобного вашему: есть нижний позиционированный UIToolbar
, имеющий UITextField
в элементе элемента панели customView для ввода. В вашем случае это расположено выше UITabBar
. ITextField
имеет настраиваемый набор inputAccessoryView
, который другой UIToolbar
с другим UITextField
.
Когда пользователь вступает в текстовое поле и становится первым ответчиком, клавиатура анимируется на месте со второй панелью инструментов/текстовым полем вместе с ней (и этот переход выглядит очень красиво!). Когда мы замечаем это, мы переходим первыйResponder из первого текстового поля ко второму, так что он имеет мигающую каретку, когда клавиатура на месте.
Трюк - это то, что нужно делать, когда вы определяете время завершения редактирования. Во-первых, вы должны resignFirstResponder во втором текстовом поле, но если вы не будете осторожны, система вернет статус первого ответчика обратно в исходное текстовое поле! Поэтому вам нужно это предотвратить, потому что иначе вы будете в бесконечном цикле прохождения вперед первого ответчика, и клавиатура никогда не будет уволена. Во-вторых, вам нужно отразить любой текстовый ввод во втором текстовом поле обратно в первое текстовое поле.
Здесь код для этого подхода:
@implementation TSViewController
{
IBOutlet UIToolbar* _toolbar; // parented in your view somewhere
IBOutlet UITextField* _textField; // the customView of a UIBarButtonItem in the toolbar
IBOutlet UIToolbar* _inputAccessoryToolbar; // not parented. just owned by the view controller.
IBOutlet UITextField* _inputAccessoryTextField; // the customView of a UIBarButtonItem in the inputAccessoryToolbar
}
- (void) viewDidLoad
{
[super viewDidLoad];
_textField.delegate = self;
_inputAccessoryTextField.delegate = self;
_textField.inputAccessoryView = _inputAccessoryToolbar;
}
- (void) textFieldDidBeginEditing: (UITextField *) textField
{
if ( textField == _textField )
{
// can't change responder directly during textFieldDidBeginEditing. postpone:
dispatch_async(dispatch_get_main_queue(), ^{
_inputAccessoryTextField.text = textField.text;
[_inputAccessoryTextField becomeFirstResponder];
});
}
}
- (BOOL) textFieldShouldBeginEditing: (UITextField *) textField
{
if ( textField == _textField )
{
// only become first responder if the inputAccessoryTextField isn't the first responder.
return ![_inputAccessoryTextField isFirstResponder];
}
return YES;
}
- (void) textFieldDidEndEditing: (UITextField *) textField
{
if ( textField == _inputAccessoryTextField )
{
_textField.text = textField.text;
}
}
// invoke this when you want to dismiss the keyboard!
- (IBAction) done: (id) sender
{
[_inputAccessoryTextField resignFirstResponder];
}
@end
Есть одна окончательная возможность, о которой я могу думать. Приведенный выше подход имеет недостаток двух отдельных панелей инструментов/текстовых полей. То, что вы в идеале хотите, это всего лишь один набор из них, и вы хотите, чтобы оно показалось, что клавиатура "толкает" их (или тянет их). На самом деле анимация достаточно быстрая, что я не думаю, что большинство людей заметят, что для вышеупомянутого подхода есть два набора, но, возможно, вам это не нравится.
Этот последний подход прослушивает клавиатуру для отображения/скрытия и использует CADisplayLink
для синхронизации анимации панели инструментов/текстового поля, поскольку он обнаруживает изменения в позиции клавиатуры в реальном времени. В моих тестах это выглядит довольно хорошо. Главный недостаток, который я вижу, заключается в том, что позиционирование панели инструментов немного отстает. Я использую авто-макет и переход на традиционное позиционирование кадров может быть быстрее. Другим недостатком является то, что зависимость от иерархии представления клавиатуры не меняется резко. Это, вероятно, самый большой риск.
Есть еще один трюк с этим. Панель инструментов расположена в моей раскадровке с использованием ограничений. Есть два ограничения для расстояния от нижней части представления. Один из них привязан к IBOutlet "_toolbarBottomDistanceConstraint", и это то, что использует код для перемещения панели инструментов. Это ограничение является ограничением "вертикального пространства" с "равным" отношением. Я устанавливаю приоритет 500. Существует второе параллельное ограничение "вертикального пространства" с отношением "больше или равно". Постоянная на этом минимальное расстояние до нижней части окна (например, над панелью вкладок), а приоритет - 1000. С этими двумя ограничениями я могу установить панели инструментов на расстояние от основания до любого значения я например, но он никогда не опустится ниже моего минимального значения. Это является ключом к тому, чтобы заставить себя казаться, что клавиатура нажимает/тянет панель инструментов, но при этом "отбрасывает" анимацию в определенную точку.
Наконец, возможно, вы могли бы сделать гибрид такого подхода с тем, что у вас уже есть: использовать обратный вызов CADisplayLink, чтобы определить, когда клавиатура "набежала" на вашу панель инструментов, а затем вместо того, чтобы вручную позиционировать панель инструментов на оставшуюся часть анимации, используйте реальную анимацию UIView для анимации вашей панели инструментов. Вы можете установить длительность продолжительности клавиатуры-дисплея-анимации минус время, которое уже появилось.
@implementation TSViewController
{
IBOutlet UITextField* _textField;
IBOutlet UIToolbar* _toolbar;
IBOutlet NSLayoutConstraint* _toolbarBottomDistanceConstraint;
CADisplayLink* _displayLink;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
- (void) viewDidLoad
{
[super viewDidLoad];
[self.view addGestureRecognizer: [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector( dismiss:) ]];
_textField.inputAccessoryView = [[UIView alloc] init];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(keyboardWillShowHide:)
name: UIKeyboardWillShowNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(keyboardWillShowHide:)
name: UIKeyboardWillHideNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(keyboardDidShowHide:)
name: UIKeyboardDidShowNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(keyboardDidShowHide:)
name: UIKeyboardDidHideNotification
object: nil];
}
- (void) keyboardWillShowHide: (NSNotification*) n
{
_displayLink = [CADisplayLink displayLinkWithTarget: self selector: @selector( tick: )];
[_displayLink addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
}
- (void) keyboardDidShowHide: (NSNotification*) n
{
[_displayLink removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
}
- (void) tick: (CADisplayLink*) dl
{
CGRect r = [_textField.inputAccessoryView.superview.layer.presentationLayer frame];
r = [self.view convertRect: r fromView: _textField.inputAccessoryView.superview.superview];
CGFloat fromBottom = self.view.bounds.size.height - r.origin.y;
_toolbarBottomDistanceConstraint.constant = fromBottom;
}
- (IBAction) dismiss: (id) sender
{
[self.view endEditing: YES];
}
@end
Здесь иерархия представления и ограничения: