【问题标题】:UIView atop the Keyboard similar to iMessage App类似于 iMessage 应用程序的键盘顶部的 UIView
【发布时间】:2012-01-17 15:52:18
【问题描述】:

目前我正在尝试基本上实现和精确复制 Apples iMessage 应用程序。

这意味着我需要一个停靠在屏幕底部的 UITextView,并在它成为 firstResponder 时向上移动。 - 这实际上很容易。有无数种方法可以做到这一点,其中两种最常见的方法当然是在收到 通知 时向上或向下设置视图动画。另一种是通过inputAccessoryView 来实现。可悲的是,一个有一些功能,另一个没有。而且它们似乎是相互排斥的。

最大的问题是旋转

我已经挖掘了大约至少七个不同的 github 项目,所有这些项目都重新实现了我想要实现的相同功能/行为,但实际上所有这些项目都惨遭失败。

例如,官方 Facebook/FacebookMes​​senger/(可能还有 WhatsApp)应用程序使用的 HPGrowingTextView 就是一大段垃圾代码。拿起您的 iDevice,打开 Facebook 应用程序,进入聊天室,弹出键盘并旋转您的设备。如果您注意,您会注意到输入栏略有跳跃,并在键盘框架和它自己的框架之间留下一些空白空间。然后在显示键盘时查看 Apple 在 iMessage 中的实现。很完美。

除了 HPGrowingTextView 库使用的 contentOffset 和 EdgeInset-hacking 给我带来了噩梦。

所以我想自己做,从头开始。

现在,我有一个非常流畅、优雅且无需 hack 的不断增长的 UITextView 实现,但缺少一部分。

优雅的旋转。

当我在 willRotateToInterfaceOrientation:duration: 方法中简单地将框架调整到它们各自的新位置时,一切都会完美运行,但我遇到了与 HPGrowingTextView(请参阅 Facebook 应用程序)相同的问题。旋转发生时,输入视图和键盘之间有一点空间。

我发现当将设备旋转到横向时,当前显示的纵向键盘不会“变形”而是消失(发送“willHide”通知)并且横向版本重新出现(发送“willShow”通知) .过渡是一个非常微妙的淡入淡出,可能会调整大小。

我使用 inputAccessoryView 重新实现了我的项目,看看会发生什么,我感到非常惊喜。 inputAccessoryView 与键盘完美同步旋转。两者之间没有空间/差距。

遗憾的是,我还没有想出如何让 inputAccessoryView 停靠在屏幕底部并且在键盘旁边消失/移出...

我不想要的是 hack-y 解决方案,例如...“在 toInterfaceOrientation 的 CoordinateSystem 中稍微降低框架,然后在调用 didRotateFrom... 时将其向上移动。”

我知道另一个应用程序已经成功实现了这种行为,它是“Kik Messenger”。

是否有人有我尚未看到的涵盖该主题的想法、建议或链接?

非常感谢!

注意:一旦解决了这个问题,我将开源该项目以供所有人获利,因为在过去几天中我能够找到的几乎所有实现都是一团糟。

【问题讨论】:

  • 你能拦截keyboardShouldHide通知并手动隐藏键盘吗?
  • 好吧,我不是想隐藏键盘,只是让我的栏(位于键盘顶部)与键盘完美同步旋转。我不应该担心键盘动画 - 我认为 - (只是试图理解它们)。此外我无法访问键盘动画属性的方式(好吧,反正不是直接)......
  • 将其设为输入附件有什么问题?旋转不完美吗?
  • 另外,请记住有两个阶段的轮换。其他一些需要研究的东西是石英框架和 UIKit 文档。
  • inputaccessoryview 的问题在于,即使关闭键盘,我也需要 inputaccessoryview 保持在屏幕上(请参阅消息应用程序以供参考)。我还没有找到使这成为可能的方法。但是,是的,inputaccessoryview 完美地与键盘同步旋转。

标签: ios uitextview uikeyboard


【解决方案1】:

我如何为我解决这个问题:

我有ChatViewControllerFooterViewController 作为UIContainerView。另外,我在FooterViewController 有 contentView 出口。然后在ChatViewController 我有:

override func becomeFirstResponder() -> Bool {
    return true
}

override var inputAccessoryView: UIView? {
    if let childViewController = childViewControllers.first as? FooterViewController {
        childViewController.contentView.removeFromSuperview()
        return childViewController.contentView
    }
    return nil
}

另一种方法是以编程方式创建视图并作为 inputAccessoryView 返回。

【讨论】:

    【解决方案2】:

    这是一个在 iOS 9.3.1 和 8.3.1 上正常工作的 UITextView 子类。它负责限制增长和缩小,同时保持插入符号始终在正确的位置并流畅地制作动画。

    将视图粘贴在键盘上很简单,有很多解决方案很容易找到,所以没有涵盖...

    我找不到任何可用于生产的定制解决方案,所以我最终从头开始着手解决这个问题。一路上我不得不解决很多小问题。

    代码 cmets 应该让您了解正在发生的事情。

    我已在my Github 上分享此内容,非常感谢您的贡献。

    备注

    • 未测试支持横向
    • 未在 i6+ 上测试

    演示

    (在最大高度元素变为可滚动之后。忘记拖动演示,但这也按预期工作......)

    子类

    class ruuiDynamicTextView: UITextView {
        var dynamicDelegate: ruuiDynamicTextViewDelegate?
        var minHeight: CGFloat!
        var maxHeight: CGFloat?
        private var contentOffsetCenterY: CGFloat!
    
        init(frame: CGRect, offset: CGFloat = 0.0) {
            super.init(frame: frame, textContainer: nil)
            minHeight = frame.size.height
    
            //center first line
            let size = self.sizeThatFits(CGSizeMake(self.bounds.size.width, CGFloat.max))
            contentOffsetCenterY = (-(frame.size.height - size.height * self.zoomScale) / 2.0) + offset
    
            //listen for text changes
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(textChanged), name: UITextViewTextDidChangeNotification, object: nil)
    
            //update offsets
            layoutSubviews()
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            //Use content size if more than min size, compensate for Y offset
            var height = max(self.contentSize.height - (contentOffsetCenterY * 2.0), minHeight)
            var updateContentOffsetY: CGFloat?
            //Max Height
            if maxHeight != nil && height > maxHeight {
                //Cap at maxHeight
                height = maxHeight!
            } else {
                //constrain Y to prevent odd skip and center content to view.
                updateContentOffsetY = contentOffsetCenterY
            }
            //update frame if needed & notify delegate
            if self.frame.size.height != height {
                self.frame.size.height = height
                dynamicDelegate?.dynamicTextViewDidResizeHeight(self, height: height)
            }
            //constrain Y must be done after setting frame
            if updateContentOffsetY != nil {
                self.contentOffset.y = updateContentOffsetY!
            }
        }
    
        func textChanged() {
            let caretRect = self.caretRectForPosition(self.selectedTextRange!.start)
            let overflow = caretRect.size.height + caretRect.origin.y - (self.contentOffset.y + self.bounds.size.height - self.contentInset.bottom - self.contentInset.top)
            if overflow > 0 {
                //Fix wrong offset when cursor jumps to next line un explisitly
                let seekEndY = self.contentSize.height - self.bounds.size.height
                if self.contentOffset.y != seekEndY {
                    self.contentOffset.y = seekEndY
                }
            }
        }
    }
    
    protocol ruuiDynamicTextViewDelegate {
        func dynamicTextViewDidResizeHeight(textview: ruuiDynamicTextView, height: CGFloat)
    }
    

    【讨论】:

    • 谢谢!添加到描述中。
    • 太好了,我还添加了一个拉取请求 :-) 我听说你喜欢贡献。
    • 太棒了!!我明天去看看。
    【解决方案3】:

    我最近遇到了同样的问题,由于我对可用的 3rd 方库并不完全满意,因此不得不构建一个自定义解决方案。我已将此实现拆分为它自己的 GitHub 项目:

    MessageComposerView

    从对 iOS 6.1 7 和 8 模拟器的一些简单测试来看,旋转似乎正确地跟随键盘。视图也会随文本一起增长,并在旋转时自动调整大小。

    你可以使用一个非常基本的init 函数来创建它,它具有屏幕宽度和默认高度,例如:

    self.messageComposerView = [[MessageComposerView alloc] init];
    self.messageComposerView.delegate = self;
    [self.view addSubview:self.messageComposerView];
    

    还有几个其他的初始化器也可以让你自定义框架、键盘偏移和 textview 最大高度。请参阅自述文件了解更多信息!

    【讨论】:

    • 不错的解决方案,评论很好,功能齐全!你能把它设置成 Cocoapod 吗?
    • 这是一个很好的建议!我会尽快进行设置 - 希望在接下来的几周内。
    • 您好 Raspu,抱歉耽搁了。我已将项目设置为 Cocoapod。我还没有将它添加到 CocoaPods 规范中。如果有任何问题,请告诉我。
    • 谢谢,我今天试试!
    • 感谢埃文的好话 :)
    【解决方案4】:

    最近我写了一篇关于您所描述的确切问题的博客文章,以及如何通过使用键盘通知但不使用inputAccessoryView 以一种简短而优雅的方式解决它。虽然这个问题已经很老了,但这个话题仍然很重要,所以这里是帖子的链接:Synchronizing rotation animation between the keyboard and the attached view

    如果您不想深入了解博文中描述的冗长解释,这里是带有代码示例的简短描述:

    基本原理是使用每个人都使用的相同方法 - 观察键盘通知以使附加视图上下动画。但除此之外,当由于界面方向更改而触发键盘通知时,您必须取消这些动画。

    在界面方向更改时没有自定义动画取消的旋转示例:

    在界面方向更改时取消动画的旋转示例:

    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
    
        [[NSNotificationCenter defaultCenter]
                addObserver:self selector:@selector(adjustViewForKeyboardNotification:)
                name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]
                addObserver:self selector:@selector(adjustViewForKeyboardNotification:)
                name:UIKeyboardWillHideNotification object:nil];
    }
    
    - (void)viewDidDisappear:(BOOL)animated {
        [super viewDidDisappear:animated];
    
        [[NSNotificationCenter defaultCenter]
                removeObserver:self name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]
                removeObserver:self name:UIKeyboardWillHideNotification object:nil];
    }
    
    - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
        [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
        self.animatingRotation = YES;
    }
    
    - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
        [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
        self.animatingRotation = NO;
    }
    
    - (void)adjustViewForKeyboardNotification:(NSNotification *)notification {
        NSDictionary *notificationInfo = [notification userInfo];
    
        // Get the end frame of the keyboard in screen coordinates.
        CGRect finalKeyboardFrame = [[notificationInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    
        // Convert the finalKeyboardFrame to view coordinates to take into account any rotation
        // factors applied to the window’s contents as a result of interface orientation changes.
        finalKeyboardFrame = [self.view convertRect:finalKeyboardFrame fromView:self.view.window];
    
        // Calculate new position of the commentBar
        CGRect commentBarFrame = self.commentBar.frame;
        commentBarFrame.origin.y = finalKeyboardFrame.origin.y - commentBarFrame.size.height;
    
        // Update tableView height.
        CGRect tableViewFrame = self.tableView.frame;
        tableViewFrame.size.height = commentBarFrame.origin.y;
    
        if (!self.animatingRotation) {
            // Get the animation curve and duration
            UIViewAnimationCurve animationCurve = (UIViewAnimationCurve) [[notificationInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
            NSTimeInterval animationDuration = [[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    
            // Animate view size synchronously with the appearance of the keyboard. 
            [UIView beginAnimations:nil context:nil];
            [UIView setAnimationDuration:animationDuration];
            [UIView setAnimationCurve:animationCurve];
            [UIView setAnimationBeginsFromCurrentState:YES];
    
            self.commentBar.frame = commentBarFrame;
            self.tableView.frame = tableViewFrame;
    
            [UIView commitAnimations];
        } else {
            self.commentBar.frame = commentBarFrame;
            self.tableView.frame = tableViewFrame;
        }
    }
    

    【讨论】:

    • 你的手机是喝醉了还是我??
    • 回家@Frade,你喝醉了
    【解决方案5】:

    我已经成功地以相当优雅的方式解决了这个问题(我认为,...)。

    代码将于下周在 Github 上发布,并在此答案中链接到。

    --

    它是如何完成的:我通过选择 inputAccessoryView 的方式来完成旋转工作。

    命名法:

    1. 'MessageInputView' 是一个包含我的 'GrowingUITextView' 的 UIView(它还包含一个“发送”按钮和背景图像)。
    2. 'ChatView' 是属于 ChatViewController 的视图,它显示所有 Chatbubbles,并且我的 'MessageInputView' 停靠在底部。
    3. 'keyboardAccessoryView' 是一个空的 UIView,大小为:CGRect(0,0,0,0)。

    我需要弄清楚当键盘被关闭时如何让 MessageInputView 留在屏幕上。那是棘手的部分。我通过创建另一个视图 (keyboardAccessoryView) 并让我的 GrowingUITextView 将其用作其 inputAccessoryView。我保留了 keyboardAccessoryView,因为我以后需要对它的引用。

    然后我想起了我在其他尝试中所做的一些事情(每当键盘通知到达时,在屏幕周围动画 MessageInputView 的帧)。

    我将我的 MessageInputView 作为子视图添加到我的 ChatView(在最底部)。每当它被激活并且键盘通知调用 willShow: 方法时,我手动将 MessageInputView 的框架设置为顶部的指定位置。当动画完成并执行完成块时,我从 ChatView 中删除子视图并将其添加到 keyboardAccessoryView。这会导致触发另一个通知,因为每次更改 inputAccessoryView 的框架/边界时都会重新加载键盘!。您需要意识到这一点并妥善处理!

    当键盘即将关闭时,我将 MessageInputView 的框架转换为 ChatView 的坐标系并将其添加为子视图。因此,它已从我的 keyboardAccessoryView 中删除。然后我将 keyboardAccessoryView 的框架重新调整为 CGRect(0,0,0,0),否则 UIViewAnimationDuration 将不匹配!然后我允许关闭键盘,让我的 MessageInputView 从上方跟随它并最终停靠在屏幕底部。

    不过,这是一项相当多的工作,却收效甚微。

    --

    保重。

    PS:如果有人想出更简单的方法(完美),请告诉我。

    【讨论】:

    • 更简单的方法是设置keyboardAccessoryView.backgroundColor = [UIColor clearColor]keyboardAccessoryView.userInteractionEnabled = NO。然后如果可能,将keyboardAccessoryView 的高度限制为MessageInputView 的高度。将MessageInputView 保留为ChatView 的子视图,并将其顶部限制在keyboardAccessoryView 的顶部。 Auto Layout: Create constraint between two views in separate windows
    • 没关系。我想我找到了一个更简单的解决方案:stackoverflow.com/a/24358901/242933
    猜你喜欢
    • 2012-05-10
    • 2017-09-03
    • 1970-01-01
    • 2019-05-04
    • 2011-12-08
    • 2022-01-19
    • 1970-01-01
    • 2017-02-24
    • 2019-12-21
    相关资源
    最近更新 更多