ios - 沿键盘动画 UIView 出现动画

标签 ios animation uikeyboard

我正在使用 UIKeyboardWillShowNotificationUIKeyboardWillHideNotification使用 UIKeyboardAnimationDurationUserInfoKey 为键盘上的 View 设置动画显示动画, UIKeyboardAnimationCurveUserInfoKeyUIKeyboardFrameEndUserInfoKey .

一切正常,只要元素开始位置在屏幕底部。我的元素(屏幕截图中的输入框)从 UITabBarController 上方开始,所以如果我的动画开始,键盘和 UITextField 之间会有间隙,它会沿着动画缩小,直到它结束。

我正在寻找的内容类似于:“使用相同的动画曲线制作动画,但如果键盘达到我的 maxY 位置,则开始移动”。

如果我为启动动画添加延迟,则缓动将不正确,这可能会在 future 的 iOS 版本中中断。

如果您能与我分享您的想法,那就太好了。 :-)

Keyboard closed
Keyboard opened
Keyboard animating (see the gap

最佳答案

通常有两种方法可以用来在键盘动画到位时将 View 保持在键盘上方。如您所知,首先是听UIKeyboardWillShowNotification并使用 userData 中随附的持续时间/曲线/帧值来帮助您在键盘上方定位 View 并为其设置动画。

第二种方法是提供 inputAccessoryView对于调用键盘的 View ( UITextField ,这里)。 (我意识到这不会提供您所要求的效果,即一旦键盘碰到工具栏/文本字段就“向上推”它。但稍后会详细介绍。) iOS 会将您的 inputAccessoryView 父 View 也作为键盘的父 View 并将它们一起设置为动画。根据我的经验,这提供了最好看的动画。我想我从来没有过 完美动画使用 UIKeyboardWillShowNotification方法,尤其是现在在 iOS7 中,键盘动画结束时会有一点反弹。 UIKit Dynamics 可能也有一种方法可以将这种反弹应用到您的 View 中,但是让它与键盘完美同步会很困难。

这是我过去为类似于您的场景所做的:底部定位 UIToolbar有一个 UITextField在用于输入的 customView 栏按钮项中。在您的情况下,它位于 UITabBar 上方. ITextField有定制inputAccessoryView设置,即 另一个 UIToolbar另一个 UITextField .

当用户点击文本字段并成为第一响应者时,键盘会随着第二个工具栏/文本字段一起动画到位(这个过渡看起来非常好!)。当我们注意到发生这种情况时,我们将 firstResponder 从第一个文本字段转换到第二个文本字段,这样一旦键盘就位,它就会有闪烁的插入符号。

诀窍是当您确定结束编辑的时间时该怎么做。首先,您必须在第二个文本字段上 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同步动画工具栏/文本字段,因为它实时检测键盘位置的变化。在我的测试中,它看起来很不错。我看到的主要缺点是工具栏的位置有点滞后。我正在使用自动布局,转换为传统的框架定位可能会更快。另一个缺点是对键盘 View 层次结构的依赖没有显着变化。这可能是最大的风险。

这还有一个技巧。工具栏使用约束定位在我的 Storyboard中。与 View 底部的距离有两个约束。一个绑定(bind)到 IBOutlet "_toolbarBottomDistanceConstraint",这就是代码用来移动工具栏的内容。此约束是具有“相等”关系的“垂直空间”约束。我将优先级设置为 500。还有一个具有“大于或等于”关系的平行“垂直空间”约束。这个常量是到 View 底部的最小距离(例如,在标签栏上方),优先级是 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

这是 View 层次结构和约束:

enter image description here

关于ios - 沿键盘动画 UIView 出现动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19709749/

相关文章:

ios - 为 UITextField 弹出键盘时 UIView 消失

ios - 将第三方框架打包到我自己的 iOS 库/框架中

ios - 我如何在Apple Store上升级iOS Flutter应用程序?

Android:Android 资源中是否有可用的原生/默认动画?

ios7 - iOS 7 上的 UIPopoverController 和键盘导致奇怪的动画

ios - 如何使用 Swift 防止 "0"作为第一个字符并限制 UITextField 中允许的字符数?

ios - 将 .swift 中的全局变量传递给特定的 viewController

ios - 将 UIImage 从 iPhone 传递到 Apple Watch 会导致 watch 无响应

WPF:动画不流畅

wpf - 可见性动画从隐藏到折叠,反之亦然