首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将UIView沿键盘显示为动画

将UIView沿键盘显示为动画
EN

Stack Overflow用户
提问于 2013-10-31 14:52:32
回答 2查看 8.8K关注 0票数 15

我使用UIKeyboardWillShowNotificationUIKeyboardWillHideNotification在键盘上动画一个视图,使用UIKeyboardAnimationDurationUserInfoKeyUIKeyboardAnimationCurveUserInfoKeyUIKeyboardFrameEndUserInfoKey来显示动画。

只要元素的起始位置在屏幕底部,一切都可以正常工作。我的元素(屏幕截图中的输入框)开始于UITabBarController之上,所以如果我的动画开始,键盘和UITextField之间就会有差距,它沿着动画缩小,直到它到达它的末尾。

我正在寻找的是这样的东西:“如果键盘达到我的maxY位置,用相同的动画曲线动画,但启动移动。”

如果我添加一个延迟启动动画,它将不正确的放松,这可能会打破在未来的iOS发行版。

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

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-11-06 20:56:15

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

第二种方法是为正在调用键盘的视图(此处为UITextField)提供一个UITextField(我意识到这不能提供您所要求的效果,即一旦键盘运行到工具栏/文本字段,就会“推”它。但是稍后会有更多的介绍) iOS将使您的inputAccessoryView父级到视图,该视图也是键盘的父级,并将它们放在一起。根据我的经验,这是最好看的动画。我想我从来没有使用过完美的动画,尤其是在iOS7中,键盘动画的末尾有一点弹跳。也许有一种使用UIKit Dynamic的方法也可以将这种反弹应用到您的视图中,但是要使它与键盘完全同步是很困难的。

下面是我过去在类似于您的场景中所做的工作:在一个用于输入的UITextField按钮项中,有一个位于底部的UITextField。在您的例子中,这个位置位于一个UITabBar之上。ITextField有一个自定义inputAccessoryView集,它是另一个 UIToolbar另一个 UITextField

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

诀窍是,当您确定结束编辑的时间时,应该做什么。首先,您必须在第二个文本字段上使用resignFirstResponder,但是如果您不小心,系统将将第一个响应状态传递回原始文本字段!所以你必须防止这种情况,因为否则你会在一个无限循环中传递第一个响应器,而键盘永远不会被忽略。其次,您需要将任何文本输入镜像到第二个文本字段,返回到第一个文本字段。

下面是这种方法的代码:

代码语言:javascript
复制
@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来同步动态工具栏/textfield,因为它实时检测到键盘位置的变化。在我的测试中看上去很不错。我看到的主要缺点是工具栏的定位有点滞后。我正在使用自动布局和改变传统的帧定位可能会更快。另一个缺点是依赖于键盘视图层次结构,不会发生剧烈变化。这可能是最大的风险。

这还有另外一个窍门。工具栏位于我的故事情节提要中,使用约束。离视图底部的距离有两个限制。其中一个绑定到IBOutlet "_toolbarBottomDistanceConstraint",这是代码用来移动工具栏的内容。这种约束是具有“等”关系的“垂直空间”约束。我把优先权定在500。第二个平行的“垂直空间”约束具有“大于或相等”的关系。这上面的常量是到视图底部的最小距离(例如,在选项卡栏上方),优先级是1000。有了这两个约束,我可以将工具栏从底部的距离设置为我喜欢的任何值,但它永远不会降到我的最小值以下。这是使键盘看起来像是在推/拉工具栏,但让它在某一点上“放下”动画的关键。

最后,也许您可以将这种方法与已有的方法进行混合:使用CADisplayLink回调来检测键盘何时“运行”到您的工具栏上,然后使用真正的UIView动画将工具栏放在适当的位置,而不是手动定位工具栏的其余部分。您可以将持续时间设置为键盘显示动画持续时间减去已经显示的时间。

代码语言:javascript
复制
@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

以下是视图的层次结构和约束:

票数 15
EN

Stack Overflow用户

发布于 2013-11-06 22:29:46

我没有汤姆提供的详细解决方案,但我确实有一个想法,你可以玩。我已经做了很多有趣的事情与自动布局和限制,你可以做一些令人惊奇的事情。请注意,不能将滚动视图中的项限制在一个视图中。

所以您有您的主视图,我假设它是一个表视图或scrollView中的其他视图,所以您必须处理这个问题。我建议的方式是获取视图的快照,将当前视图保存在ivar (您的表)中,并将其替换为锚定在底部的“非常高的容器视图”,将包含快照的UIImageView放在此视图中,并在该视图与constant=0的容器视图之间设置一个约束。对于用户来说,没有什么改变。

在" inputAccessoryView“中,当视图被添加到superView (以及有一个窗口属性时),您可以删除图像和容器视图之间的约束,并添加一个新的约束,将文本字段的底部限制到inputAccessoryView的顶部,其中的距离必须大于某个值。您必须四处游玩,以获得值,因为它将是textField在您的scrollView中的偏移量,并对任何contentValue进行了调整。然后,您很可能必须将该约束添加到窗口中(保留一个ivar,以便您以后可以删除它)。

在过去,我玩键盘,你可以看到它被添加到窗口中,它的帧偏移量使得它刚好在屏幕底部(在iOS5中)--它不是在scrollView中。

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

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

编辑:正如Tom所指出的,键盘位于另一个窗口的"Z“级别较高,因此无法直接连接真正的键盘和用户视图之间的约束。

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

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19709749

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档