15

我正在使用UIKeyboardWillShowNotificationUIKeyboardWillHideNotification为沿键盘的视图设置动画,使用UIKeyboardAnimationDurationUserInfoKey,UIKeyboardAnimationCurveUserInfoKeyUIKeyboardFrameEndUserInfoKey

一切正常,只要元素的起始位置在屏幕底部。我的元素(屏幕截图中的输入框)从 UITabBarController 上方开始,因此如果我的动画开始,则键盘和 UITextField 之间存在间隙,该间隙沿着动画缩小,直到它到达终点。

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

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

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

键盘关闭 键盘打开 键盘动画(见间隙

4

2 回答 2

15

通常,您可以使用两种方法在键盘动画就位时将视图保持在键盘上方。如您所知,第一个是监听UIKeyboardWillShowNotification并使用 userData 中随附的持续时间/曲线/帧值来帮助您在键盘上方定位和动画化您的视图。

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

这是我过去为类似于您的场景所做的事情:在 customView 栏按钮项中UIToolbar有一个底部定位用于输入。UITextField在您的情况下,它位于 a 上方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在实时检测到键盘位置的变化时使用 a 来同步工具栏/文本字段的动画。在我的测试中,它看起来很不错。我看到的主要缺点是工具栏的定位有点滞后。我正在使用自动布局,切换到传统的框架定位可能会更快。另一个缺点是对键盘视图层次结构的依赖性不会发生显着变化。这可能是最大的风险。

这还有另一个技巧。工具栏使用约束定位在我的故事板中。距视图底部的距离有两个约束。一个与 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

这是视图层次结构和约束:

在此处输入图像描述

于 2013-11-06T20:56:15.323 回答
1

我没有汤姆提供的详细解决方案,但我确实有一个你可以尝试的想法。我一直在用自动布局和约束做很多有趣的事情,你可以做一些令人惊奇的事情。请注意,您不能将滚动视图中的项目与合二为一的事物进行约束。

所以你有你的主要视图,我假设它是一个表格视图或滚动视图中的其他视图,所以你必须处理它。我建议的方法是拍摄视图的快照,将当前视图保存在 ivar(您的表)中,并将其替换为锚定在底部的“非常高的容器视图”,将包含快照的 UIImageView 放入这个视图,在它和常量 = 0 的容器视图之间有一个约束。对用户来说没有任何改变。

在“inputAccessoryView”中,当视图被添加到superView时(并且当有window属性时),可以去掉图片和容器视图之间的约束,添加一个新的约束文本字段底部的到 inputAccessoryView 的顶部,距离必须大于某个值。您必须四处寻找该值,因为它将是您的滚动视图中该文本字段的偏移量,已针对任何 contentValue 进行了调整。然后,您很可能必须将该约束添加到窗口(保留一个 ivar,以便稍后将其删除)。

过去我玩过键盘,你可以看到它被添加到窗口,它的框架偏移,所以它就在屏幕底部的下方(在 iOS5 中) - 它不在滚动视图中。

当键盘完成滚动时,您可以查看图像视图滚动的位置,确定偏移量,然后从图像视图切换回您的真实滚动视图。

请注意,我确实做了这个快照,动画,最后相当成功地替换了过去的视图。你会花一些时间在这上面,也许它会起作用,也许不会——但是如果你把一个简单的演示项目放在一起,你可以快速验证你是否可以在容器视图中获得一个 imageView 本身,以便使用键盘输入附件上的约束移动看法。一旦可行,您就可以“真正地”做到这一点。

编辑:正如汤姆斯威夫特所指出的,键盘位于更高“Z”级别的另一个窗口中,因此无法直接连接真实键盘和用户视图之间的约束。

然而——在键盘通知中,我们可以得到它的大小、动画持续时间,甚至动画曲线。因此,当您收到第一个键盘通知时,创建一个新的透明视图并将其放置,使其顶部位于您的特殊“imageView”(快照)视图的底部。使用键盘长度和曲线的 UIView 动画,您的透明视图将完全按照键盘动画进行动画 - 但在您的窗口中。将约束放在透明视图上,您​​应该能够实现您想要的确切行为(在 iOS6 中)。真的,在这一点上支持 iOS 5 - 对于尚未升级的 10 个人?!?!?。

如果你必须支持 iOS5,并且你想要这种“碰撞”行为,那么计算动画何时达到它“击中”你的 textField 的大小,以及当键盘开始移动时使用延迟的 UIView 动画,这样它就不会不会立即开始移动,但当它移动时,它会跟踪键盘。

于 2013-11-06T22:29:46.150 回答