28

长期潜伏者 - 第一次海报!

我在使用UITextViewWhatsApp 重新创建酒吧时遇到问题。

我正在使用一个自定义UIView子类,并懒惰地实例化它:

- (UIView *)inputAccessoryView

并返回 YES:

- (BOOL)canBecomeFirstResponder

现在,我想在大小增加inputAccessoryView时更改UITextView大小。在 iOS 7 上,我只需更改所述视图框架的大小 - 而不是它的原点 - 然后调用reloadInputViews它就会起作用:视图将向上移动,使其在键盘上方完全可见。

但是,在 iOS 8 上,这不起作用。使其工作的唯一方法是将框架的原点也更改为负值。这很好,除了它会产生一些奇怪的错误:例如,UIView输入任何文本时返回到“原始”框架。

有什么我想念的吗?我很确定 WhatsApp 会使用inputAccessoryView,因为它们在拖动时会关闭键盘 - 仅在最新版本的应用程序中。

如果你能帮助我,请告诉我!或者,如果您希望我进行任何测试!

谢谢!:)

顺便说一句,这是我用来更新自定义 UIView 高度的代码composeBar

// ComposeBar frame size
CGRect frame = self.composeBar.frame;
frame.size.height += heightDifference;
frame.origin.y -= heightDifference;
self.composeBar.frame = frame;
[self.composeBar.textView reloadInputViews]; // Tried with this
[self reloadInputViews];                     // and this

编辑:完整的源代码可用@ https://github.com/manuelmenzella/SocketChat-iOS

4

9 回答 9

33

随着行为从 iOS 7 到 iOS 8 的变化,我一直在这个问题上撞墙已经有一段时间了。我尝试了一切,直到最明显的解决方案对我有用:

inputAccessoryView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

呃!

于 2014-09-15T16:14:40.997 回答
17

总结 JohnnyC 的答案:将您的 inpitAccessoryView 设置autoresizingMask.flexibleHeight,计算它intrinsicContentSize并让框架完成其余的工作。

完整代码,针对 Swift 3 更新:

class InputAccessoryView: UIView, UITextViewDelegate {

    let textView = UITextView()

    override var intrinsicContentSize: CGSize {
        // Calculate intrinsicContentSize that will fit all the text
        let textSize = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: CGFloat.greatestFiniteMagnitude))
        return CGSize(width: bounds.width, height: textSize.height)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        // This is required to make the view grow vertically
        autoresizingMask = .flexibleHeight

        // Setup textView as needed
        addSubview(textView)
        textView.translatesAutoresizingMaskIntoConstraints = false
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[textView]|", options: [], metrics: nil, views: ["textView": textView]))
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[textView]|", options: [], metrics: nil, views: ["textView": textView]))

        textView.delegate = self

        // Disabling textView scrolling prevents some undesired effects,
        // like incorrect contentOffset when adding new line,
        // and makes the textView behave similar to Apple's Messages app
        textView.isScrollEnabled = false
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: UITextViewDelegate

    func textViewDidChange(_ textView: UITextView) {
        // Re-calculate intrinsicContentSize when text changes
        invalidateIntrinsicContentSize()
    }

}
于 2015-09-18T08:59:04.487 回答
5

问题在于,在 iOS 8 中,会自动安装一个 NSLayoutConstraint,它将 inputAccessoryView 的高度设置为其初始帧高度。为了解决布局问题,您需要将该约束更新为所需的高度,然后指示您的 inputAccessoryView 自行布局。

- (void)changeInputAccessoryView:(UIView *)inputAccessoryView toHeight:(CGFloat)height {
    for (NSLayoutConstraint *constraint in [inputAccessoryView constraints]) {
        if (constraint.firstAttribute == NSLayoutAttributeHeight) {
            constraint.constant = height;
            [inputAccessoryView layoutIfNeeded];
            break;
        }
    }
}
于 2015-06-11T18:35:07.333 回答
5

这是一个完整的、独立的解决方案(感谢@JohnnyC@JoãoNunes为我指明了正确的方向, @ stigi解释如何为intrinsicContent更改设置动画):

class InputAccessoryView: UIView {

    // InputAccessoryView is instantiated from nib, but it's not a requirement
    override func awakeFromNib() {
        super.awakeFromNib()        
        autoresizingMask = .FlexibleHeight
    }

    override func intrinsicContentSize() -> CGSize {
        let exactHeight = // calculate exact height of your view here
        return CGSize(width: UIViewNoIntrinsicMetric, height: exactHeight)
    }

    func somethingDidHappen() {
        // invalidate intrinsic content size, animate superview layout        
        UIView.animateWithDuration(0.2) {
            self.invalidateIntrinsicContentSize()
            self.superview?.setNeedsLayout()
            self.superview?.layoutIfNeeded()
        }
    }
}
于 2015-07-16T13:31:22.113 回答
4

100% 有效且非常简单的解决方案是枚举所有约束并设置新的高度值。这是一些 C# 代码(xamarin):

foreach (var constraint in inputAccessoryView.Constraints)
{
    if (constraint.FirstAttribute == NSLayoutAttribute.Height)
    {
        constraint.Constant = newHeight;
    }
}
于 2015-02-03T21:19:56.490 回答
1

不幸的是,iOS8 给 inputAccessoryView 添加了一个私有的高度约束,而这个约束是公开的。

我建议在其框架应该更改时重新创建附件视图,并调用 reloadInputViews 以便安装新的。

这就是我所做的,它按预期工作。

于 2014-10-06T13:23:57.123 回答
1

是的,iOS8 为 inputAccessoryView 添加了一个私有高度约束。

考虑到重新创建整个 inputAccessoryView 并替换旧的可能是非常昂贵的操作,您可以在重新加载输入视图之前删除约束

[inputAccessoryView removeConstraints:[inputAccessoryView constraints]];
[textView reloadInputViews];

只是另一种解决方法

于 2014-12-15T21:14:59.097 回答
0

为了解决这个问题,我使用了 inputAccessoryView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

但这当然导致我的文本视图崩溃。因此,向工具栏添加约束并在需要时对其进行更新,或者将约束添加到文本视图本身并对其进行更新对我有用。

于 2014-10-11T23:44:59.197 回答
0

首先,获取 inputAccessoryView 并设置 nil

UIView *inputAccessoryView = yourTextView.inputAccessoryView;
yourTextView.inputAccessoryView = nil;
[yourTextView reloadInputViews];

然后设置框架和布局

inputAccessoryView.frame = XXX
[inputAccessoryView setNeedsLayout];
[inputAccessoryView layoutIfNeeded];

最后再次设置新的 inputAccessoryView 并重新加载

yourTextView.inputAccessoryView = inputAccessoryView;
[yourTextView reloadInputViews];
于 2015-07-07T09:50:39.107 回答