更改高度时,UITextView 的 textContainer 在错误的帧处呈现

Posted

技术标签:

【中文标题】更改高度时,UITextView 的 textContainer 在错误的帧处呈现【英文标题】:UITextView's textContainer renders at the wrong frame when the height is changed 【发布时间】:2018-11-08 11:05:22 【问题描述】:

我想要的只是在您输入/粘贴时调整UITextView 的大小。它应该是可滚动的,因为我不希望 UITextView 填满屏幕。用这几行代码就可以轻松实现。

@IBOutlet weak var mainTextView: UITextView!
@IBOutlet weak var mainTextViewHeightConstraint: NSLayoutConstraint!

override func viewDidLoad() 
    super.viewDidLoad()
    self.mainTextView.delegate = self


// This method is used for calculating the string's height in a UITextField
func heightOf(text: String, for textView: UITextView) -> CGFloat 
    let nsstring: NSString = text as NSString
    let boundingSize = nsstring.boundingRect(
        with: CGSize(width: textView.frame.size.width, height: .greatestFiniteMagnitude),
        options: .usesLineFragmentOrigin,
        attributes: [.font: textView.font!],
        context: nil).size
    return ceil(boundingSize.height + textView.textContainerInset.top + textView.textContainerInset.bottom)


// This method calculates the UITextView's new height
func calculateHeightFor(text: String, in textView: UITextView) -> CGFloat 
    let newString: String = textView.text ?? ""
    let minHeight = self.heightOf(text: "", for: textView) // 1 line height
    let maxHeight = self.heightOf(text: "\n\n\n\n\n", for: textView) // 6 lines height
    let contentHeight = self.heightOf(text: newString, for: textView) // height of the new string
    return min(max(minHeight, contentHeight), maxHeight)


// The lines inside will change the UITextView's height
func textViewDidChange(_ textView: UITextView) 
    self.mainTextViewHeightConstraint.constant = calculateHeightFor(
        text: textView.text ?? "", 
        in: textView)

这非常有效,除非您粘贴多行文本,或按 enter(换行符)。


为了解决粘贴问题,各种 SO 问题建议我要么创建 UITextView 的子类来覆盖 paste(_:) 方法,要么使用 textView(_: shouldChangeTextIn....) 委托方法。经过几次实验,最好的答案是使用shouldChangeTextIn 方法,这导致我的代码变成了这个

// I replaced `textViewDidChange(_ textView: UITextView)` with this
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool 
    self.mainTextViewHeightConstraint.constant = calculateHeightFor(
        text: ((textView.text ?? "") as NSString).replacingCharacters(in: range, with: text), 
        in: textView)
    return true


现在应该发生的是,当您粘贴文本时,UITextView 应该如下所示。 (粘贴的字符串是“A\nA\nA\nA\nA”或 5 行只是 A)

可是变成了这个样子

从截图中可以看出,textContainer 的框架在错误的位置。现在奇怪的是,它似乎只在您粘贴到空的UITextView 时才会发生。

我尝试过设置和重置UITextView 的frame.size,我尝试过手动设置NSTextContainer 的大小以及其他一些棘手的解决方案,但我似乎无法修复它。我该如何解决这个问题?

附加信息:

查看视图调试器后,输入 enter(换行符)时的外观如下所示

如我们所见,文本容器的位置错误。


毕竟,添加UIView.animate 块似乎可以解决一些问题,但不是全部。为什么会这样?

// These snippet kind of fixes the problem
self.mainTextViewHeightConstraint.constant = calculateHeightFor(
    text: textView.text ?? "", 
    in: textView)
UIView.animate(withDuration: 0.1) 
    self.view.layoutIfNeeded()

注意:我没有放置UIView.animate 解决方案,因为它没有涵盖所有的错误。

注意:

操作系统支持:ios 8.xx - iOS 12.xx Xcode:v10.10 Swift:3 和 4(我都试过了)

PS:是的,我已经处理过其他一些 SO 问题,例如底部列出的问题和其他一些问题

TextContainer isn't resizing while changing bounds of UITextView UITextView's text going beyond bounds UITextView stange animation glitch on paste action (iOS11) How to calculate TextView height base on text

【问题讨论】:

【参考方案1】:

可以忽略所有计算高度的代码,使用设置textview属性scrolling Enabled = false textview 将自行调整以适应文本内容 image1

然后根据需要更改代码

@IBOutlet weak var textView: UITextView!

override func viewDidLoad() 
    super.viewDidLoad()
    self.textView.text = "A\nA\nA\nA\nA"

结果会是这样 image2

【讨论】:

不,这并不能解决我的问题,让我编辑我的问题,以便更清楚。

以上是关于更改高度时,UITextView 的 textContainer 在错误的帧处呈现的主要内容,如果未能解决你的问题,请参考以下文章

粘贴长文本时UITextView动态高度滚动不起作用

SWIFT - 改变 UITextView 的高度,xcode 6 GM

SWIFT - 改变 UITextView 的高度,xcode 6 GM

如何动态调整 UITextView 的高度

iPad 上的 UIModalPresentationFormSheet。键盘出现时如何调整UITextView的高度

当键盘出现时,我应该如何正确设置 UITextView 的 contentInset