Анимация UIView по клавиатуре появляется анимация

Я использую UIKeyboardWillShowNotification и UIKeyboardWillHideNotification для анимации вида вдоль клавиатуры, используя анимацию с помощью UIKeyboardAnimationDurationUserInfoKey, UIKeyboardAnimationCurveUserInfoKey и UIKeyboardFrameEndUserInfoKey.

Все работает отлично, пока позиция начала элементов находится в нижней части экрана. Мой элемент (поле ввода на скриншоте) запускается над UITabBarController, поэтому, если начинается моя анимация, между клавиатурой и UITextField существует разрыв, который сжимается вдоль анимации до тех пор, пока он не достигнет конца.

То, что я ищу, это что-то вроде: "Анимация с той же кривой анимации, но начните движение, если клавиатура достигнет моей позиции maxY".

Если бы я добавил задержку для запуска анимации, это было бы неправильно с ослаблением, и это может сломаться в будущих выпусках iOS.

Было бы здорово, если бы вы поделились своими идеями со мной.: -)

Keyboard closedKeyboard openedKeyboard animating (see the gap

Ответы

Ответ 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

Здесь иерархия представления и ограничения:

enter image description here

Ответ 2

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

Таким образом, у вас есть основной вид, я предполагаю, что его вид таблицы или какой-либо другой вид внутри scrollView, так что вам нужно иметь дело с этим. Я предлагаю сделать снимок представления, сохранить текущий вид в ivar (вашей таблице) и заменить его "очень высоким представлением контейнера", который закреплен внизу, поставить UIImageView, содержащий моментальный снимок, в это представление, с ограничением между ним и контейнером с константой = 0. Пользователю ничего не изменилось.

В "inputAccessoryView", когда представление добавлено в superView (и когда есть свойство окна), вы можете удалить ограничение между изображением и представлением контейнера и добавить новый, который ограничивает нижнюю часть текстовое поле вверху вашего ввода Access_View, где расстояние должно быть больше некоторого значения. Вы должны поиграть, чтобы получить значение, так как это будет смещение этого текстового поля в вашем scrollView, настроенном для любого contentValue. Тогда вам, скорее всего, придется добавить это ограничение в окно (сохраняя ivar, чтобы вы могли его удалить позже).

В прошлом я играл с клавиатурой, и вы можете видеть, что она добавляется в окно, с его смещением кадра, так что он находится чуть ниже нижней части экрана (был в iOS5) - это было не в scrollView.

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

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

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

Однако - в уведомлениях о клавиатуре мы можем получить размер, продолжительность анимации, даже кривую анимации. Итак, когда вы получаете первое уведомление о клавиатуре, создайте новый прозрачный вид и поместите его так, чтобы его верх находился в нижней части вашего специального вида "imageView" (моментальный снимок). Используйте анимацию UIView длины и кривой клавиатуры, и ваш прозрачный вид будет анимироваться точно так же, как клавиатура - это анимация, но в вашем окне. Поместите ограничения на прозрачный вид, и вы сможете достичь точного поведения, которое вы хотите (в iOS6). Действительно, поддержка iOS 5 на данный момент - для 10 человек, которые еще не обновились?!?!?.

Если вам нужно поддерживать iOS5, и вам нужно это поведение "bump", тогда вычислите, когда анимация достигнет размера, где он "ударит" вашего текстового поля, и когда клавиатура начнет движение, используйте анимацию UIView с задержкой, поэтому что он не сразу начинает двигаться, но когда он движется, он отслеживает клавиатуру.