沿键盘动画 UIView 出现动画
Posted
技术标签:
【中文标题】沿键盘动画 UIView 出现动画【英文标题】:Animate UIView along keyboard appear animation 【发布时间】:2013-10-31 14:52:32 【问题描述】:我正在使用UIKeyboardWillShowNotification
和UIKeyboardWillHideNotification
来为沿键盘的视图设置动画,使用UIKeyboardAnimationDurationUserInfoKey
、UIKeyboardAnimationCurveUserInfoKey
和UIKeyboardFrameEndUserInfoKey
出现动画。
只要元素的起始位置在屏幕底部,一切正常。我的元素(屏幕截图中的输入框)从 UITabBarController 上方开始,所以如果我的动画开始,键盘和 UITextField 之间会有间隙,它会沿着动画缩小,直到它到达终点。
我正在搜索的内容类似于:“使用相同的动画曲线制作动画,但如果键盘达到我的 maxY 位置,则开始移动”。
如果我要为启动动画添加延迟,那么缓动将不正确,这可能会在未来的 ios 版本中中断。
如果您能与我分享您的想法,那就太好了。 :-)
【问题讨论】:
如果有人使用约束......那么这个解决方案可能会帮助他们......***.com/questions/31356293/… 【参考方案1】:您通常可以使用两种方法将视图保持在键盘上方,因为它的动画就位。如您所知,第一个是监听UIKeyboardWillShowNotification
并使用 userData 中随附的持续时间/曲线/帧值来帮助您在键盘上方定位和动画化您的视图。
第二种方法是为调用键盘的视图(此处为UITextField
)提供inputAccessoryView
。 (我意识到这不会提供您所要求的效果,即一旦键盘碰到它就“推动”工具栏/文本字段。但稍后会详细介绍。) iOS 将将您的 inputAccessoryView 设置为也是键盘父级的视图,并将它们一起设置为动画。以我的经验,这提供了最好看的动画。我认为我从来没有使用UIKeyboardWillShowNotification
方法制作过完美 动画,尤其是现在在iOS7 中,键盘动画结束时会有一点反弹。 UIKit Dynamics 可能也有一种方法可以将此反弹应用到您的视图中,但要使其与键盘完美同步会很困难。
这是我过去为类似于您的场景所做的:底部定位的 UIToolbar
在 customView 栏按钮项中有一个 UITextField
用于输入。在您的情况下,它位于UITabBar
上方。 ITextField
有一个自定义 inputAccessoryView
集,它是 another UIToolbar
和 another 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
来同步工具栏/文本字段的动画,因为它实时检测到键盘位置的变化。在我的测试中,它看起来很不错。我看到的主要缺点是工具栏的定位有点滞后。我正在使用自动布局,切换到传统的框架定位可能会更快。另一个缺点是对键盘视图层次结构的依赖性不会发生显着变化。这可能是最大的风险。
还有另一个技巧。工具栏使用约束定位在我的故事板中。距视图底部的距离有两个约束。一个与 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
这是视图层次结构和约束:
【讨论】:
我已经尝试采用您在 iOS7 中首次描述的方法。虽然我能够让光标出现在输入附件文本视图中,但输入仍将转到原始文本视图(您称之为 _textfield)。如果我强迫它辞去响应者的职务,键盘就会消失。有什么想法吗? @pickwick - 听起来第二个文本字段的代表没有连接。检查两个文本字段是否共享相同的委托。 感谢您的帮助,但不幸的是,事实并非如此。无论如何,我认为我仍然应该在第二个文本字段中看到文本,无论它是代表。我会继续玩... 在 iOS 中,这个小任务是火箭科学,我的朋友们。【参考方案2】:我没有 Tom 提供的详细解决方案,但我确实有一个您可以尝试的想法。我一直在用自动布局和约束做很多有趣的事情,你可以做一些令人惊奇的事情。请注意,您不能将滚动视图中的项目与合二为一的事物进行约束。
所以你有你的主视图,我假设它是一个表格视图或滚动视图中的其他视图,所以你必须处理它。我建议的方法是拍摄视图的快照,将当前视图保存在 ivar(您的表)中,并将其替换为锚定在底部的“非常高的容器视图”,将包含快照的 UIImageView 放入这个视图,在它和常量 = 0 的容器视图之间有一个约束。对用户来说没有任何改变。
在“inputAccessoryView”中,当视图被添加到superView时(并且当有window属性时),可以去掉图片和容器视图之间的约束,添加一个新的约束底部的inputAccessoryView 顶部的文本字段,其中距离必须大于某个值。您必须四处寻找该值,因为它将是您的 scrollView 中该 textField 的偏移量,已针对任何 contentValue 进行了调整。然后,您很可能必须将该约束添加到窗口中(保留一个 ivar,以便稍后将其删除)。
过去我玩过键盘,你可以看到它被添加到窗口,它的框架偏移,所以它就在屏幕底部的下方(在 iOS5 中) - 它不在滚动视图中.
当键盘完成滚动时,您可以查看图像视图滚动的位置,确定偏移量,然后从图像视图切换回您的真实滚动视图。
请注意,我确实做了这个快照,动画,最后相当成功地替换了过去的视图。你会花一些时间在这上面,也许它会起作用,也许不会——但是如果你把一个简单的演示项目放在一起,你可以快速验证你是否可以在容器视图中获得一个 imageView 本身,以便使用键盘输入附件上的约束移动看法。一旦可行,您就可以“真正地”做到这一点。
编辑:正如 Tom Swift 所指出的,键盘位于更高“Z”级别的另一个窗口中,因此无法直接连接真实键盘和用户视图之间的约束。
但是 - 在键盘通知中,我们可以获得它的大小、动画持续时间,甚至动画曲线。因此,当您收到第一个键盘通知时,创建一个新的透明视图并将其放置,使其顶部位于您的特殊“imageView”(快照)视图的底部。使用键盘长度和曲线的 UIView 动画,您的透明视图将完全按照键盘动画进行动画 - 但在您的窗口中。将约束放在透明视图上,您应该能够实现您想要的确切行为(在 iOS6 中)。真的,在这一点上支持 iOS 5 - 对于尚未升级的 10 个人?!?!?。
如果您必须支持 iOS5,并且想要这种“碰撞”行为,则计算动画何时达到“撞击”您的 textField 的大小,以及当键盘开始移动时使用 UIView 动画延迟,所以它不会立即开始移动,但是当它移动时,它会跟踪键盘。
【讨论】:
啊,这很有趣:从占位符 inputAccessoryView 和输入字段中添加约束。现在我想试试这个:) @TomSwift 我只是希望真正的工作不会阻止我自己尝试这个 - 我明天有一个应用程序的大型演示,只是没有时间玩。 YMMV :-) 好主意!我会试一试,即使我必须支持 iOS5,这个“问题”也可以保留给 iOS5。 :) 我不确定这是否可行。问题是键盘(及其 inputAccessoryView)与输入字段位于不同的窗口中。为了在两个视图之间添加约束,需要有一个共同的祖先视图,在这种情况下没有。想法? @TomSwift - 真的,键盘在不同的窗口中!我不记得iOS5中的情况。但是,是的,你是对的——如果有两个窗口,我建议的工作就没有希望了。叹息。以上是关于沿键盘动画 UIView 出现动画的主要内容,如果未能解决你的问题,请参考以下文章
在 iOS7 中边缘滑动时,使键盘与 UIView 同步动画