在键盘出现动画期间使UIView动画化

15
我正在使用 UIKeyboardWillShowNotificationUIKeyboardWillHideNotification,并且使用 UIKeyboardAnimationDurationUserInfoKeyUIKeyboardAnimationCurveUserInfoKeyUIKeyboardFrameEndUserInfoKey 来沿着键盘出现的动画来动画化一个视图。所有东西都很好,只要元素的起始位置在屏幕底部,我的元素(截图中的输入框)则在 UITabBarController 上方开始,所以如果我的动画从那里开始,键盘和 UITextField 之间会有间隙,这个间隙会随着动画收缩,直到它到达结束点。我正在寻找的是: "使用相同的动画曲线进行动画处理,但当键盘到达我的 maxY 位置时,开始移动"。如果我为开始动画添加延迟,那么它将不正确地与缓动配合,并且这可能会在未来的 iOS 版本中破坏它。如果您能与我分享您的想法,那将非常好。:-)

如果有人正在使用约束(constraints)......那么也许这个解决方案能对他们有所帮助......http://stackoverflow.com/questions/31356293/uitableview-and-uiview-with-keyboardwillshow/31356527#31356527 - Bhavin Bhadani
2个回答

15
通常,有两种方法可以在键盘动画到位时保持视图在其上方。 正如您所知,第一种方法是监听 UIKeyboardWillShowNotification 并使用 userData 中的配套 duration/curve/frame 值来帮助您定位和动画化视图以使其位于键盘上方。
第二种方法是为触发键盘的视图(这里是 UITextField)提供一个 inputAccessoryView。 iOS 将将您的 inputAccessoryView 与同样父级键盘的视图一起进行动画处理。 在我的经验中,这提供了最佳的动画效果。 我不认为我曾经使用过 UIKeyboardWillShowNotification 方法来获得完美的动画效果,特别是现在在 iOS7 中,键盘动画结束时会有一个小反弹。 可能有一种方法可以使用 UIKit 动力学将此反弹应用于您的视图,但使其与键盘完全同步可能很难。
以下是我以前在类似情况下所做的事情:底部位置有一个 UIToolbar,其中包含一个自定义视图栏按钮项的 UITextField 输入。 在您的情况下,这位于 UITabBar 上方。 ITextField 设置了一个自定义的 inputAccessoryView,这是另一个UIToolbar,其中包含另一个UITextField
当用户点击文本字段并它成为第一响应者时,键盘与第二个工具栏/文本字段一起动画化到位(这个过渡看起来非常好!)。 当我们注意到这种情况发生时,我们将第一响应者从第一个文本字段转换为第二个文本字段,以便在键盘到位后它具有闪烁的光标。
诀窍在于确定何时结束编辑。 首先,您必须在第二个文本字段上调用 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在实时检测键盘位置变化时同步动画工具栏/文本框。在我的测试中,它看起来非常好。我看到的主要缺点是工具栏的定位略微滞后。我正在使用自动布局,改用传统的框架定位可能会更快。另一个缺点是对键盘视图层次结构没有剧烈变化的依赖。这可能是最大的风险。
这还有一个技巧。我的storyboard中使用约束来定位工具栏。有两个与视图底部的距离的约束。一个绑定到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

我尝试采用你在iOS7中首先描述的方法。虽然我能够让光标出现在输入附件文本视图中,但输入仍然会进入原始文本视图(你称之为_textfield)。如果我强制它放弃响应者,键盘就会消失。有什么想法吗? - pickwick
@pickwick - 听起来第二个文本框的委托没有连接。请检查两个文本框是否共享相同的委托。 - TomSwift
谢谢你的帮助,但不幸的是那不是它。无论如何,我认为我仍然应该看到第二个文本字段中的文本,无论它的委托是什么。我会继续尝试... - pickwick
在iOS中,这个小任务就像火箭科学一样难啊,朋友们。 - Josh

1
我没有像Tom提供的那样详细的解决方案,但我有一个你可以尝试的想法。我一直在使用自动布局和约束做很多有趣的事情,你可以做一些惊人的事情。请注意,你不能用一个scrollView中的东西来约束scrollView中的项目。
所以你有你的主视图,我假设它是一个tableView或者其他的视图在scrollView中,所以你必须处理它。我建议的方法是拍摄一个视图快照,将当前视图保存在一个ivar中(你的table),并用一个“非常高的容器视图”替换它,这个视图被锚定在底部,在其中放置包含快照的UIImageView,并在它与容器视图之间设置一个常量为0的约束。对于用户来说,什么都没有改变。
在 "inputAccessoryView" 中,当将视图添加到superView中时(并且存在window属性),您可以删除图像和容器视图之间的约束,并添加一个新的约束,将文本字段的底部约束到输入附件视图的顶部,其中距离必须大于某个值。您需要尝试调整该值,因为它将是scrollView中textField的偏移量,调整后为任何contentValue。然后,您最有可能将该约束添加到窗口中(保留ivar以便稍后删除它)。
过去,我曾尝试键盘,您可以看到它被添加到窗口中,其框架偏移,因此仅位于屏幕底部以下(在iOS5中)。 它不在scrollView中。
当键盘完成滚动时,您可以看到图像视图已滚动到哪里,确定偏移量,然后从imageView切换回您的实际scrollView。
请注意,我已经成功地在过去中进行了快照、动画和替换视图。你需要花费一些时间来尝试,也许会成功,也许不会 - 但如果你拼凑一个简单的演示项目,你可以很快验证是否可以使用键盘输入附件视图上的约束将imageView本身放入容器视图中移动。一旦这个功能能够正常运行,你就可以进行实际操作。
编辑: 如Tom Swift所指出的,键盘位于另一个窗口,处于更高的“Z”级别,因此没有办法直接连接真实键盘和用户视图之间的约束。
然而,在键盘通知中,我们可以获得它的大小、动画持续时间,甚至是动画曲线。因此,当您收到第一个键盘通知时,请创建一个新的透明视图,并将其放置在特殊的“imageView”(快照)视图的底部。使用UIView动画以键盘的长度和曲线,你的透明视图将像键盘动画一样在你的窗口中进行动画。放置透明视图的约束,你应该能够实现你想要的精确行为(在iOS6中)。实际上,在这个时候支持iOS5 - 对于那10个还没有升级的人来说?!?!?
如果你必须支持iOS5,并且想要实现"bump"行为,那么需要计算动画何时达到与textField相交的大小,当键盘开始移动时,使用带有延迟的UIView动画,这样它不会立即开始移动,但是在移动时可以跟踪键盘。

@TomSwift 我只希望真正的工作不会阻止我自己尝试 - 明天我有一个关于应用程序的重要演示,没有时间去玩。你的情况可能会有所不同 :-) - David H
我不确定这会不会起作用。问题在于键盘(以及它的inputAccessoryView)位于与输入字段不同的窗口中。为了在两个视图之间添加约束,需要有一个共同的祖先视图,在这种情况下没有。你有什么想法? - TomSwift
@TomSwift - 真的,键盘在另一个窗口!我不记得在iOS5中是这种情况。但是,是的,你是正确的 - 如果有两个窗口,那么我建议的方法就没有希望能够奏效。唉。 - David H
许多系统UI组件位于不同的窗口中 - 警报、状态栏、键盘等。 - TomSwift
@TomSwift,不知道您是否可以将我提到的图像快照添加到键盘窗口中? - David H
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接