使 TextView 适合内容并向上移动视图 - Swift

Fit TextView to Content and move View Up - Swift

我有一个 UITextView 叫做 composeTextView,它就在键盘的正上方。这个 UITextView 在名为 composeUIView 的视图中。当用户输入文本时,TextView 会调整大小以显示所有文本。我的问题是,当我尝试调整 composeUIView 的底部约束时,composeTextView 的大小停止更改以适合其内容。我不明白这里发生了什么或如何解决它。如果有人知道修复方法,我可以使用帮助。

长话短说,我试图在用户增加他们使用的行数时显示更多 UITextView 行,然后移动 UITextView 所在的 UIView这样显示的额外行就不会被键盘覆盖。

class ChatViewController: UIViewController, UITextViewDelegate {

@IBOutlet weak var composeTextView: UITextView!
@IBOutlet weak var composeViewBottomConstraint: NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()

    composeTextView.delegate = self
}

func textViewDidChange(_ textView: UITextView) {

    let oldHeight = composeTextView.frame.height

    let fixedWidth = composeTextView.frame.size.width
    composeTextView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
    let newSize = composeTextView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
    var newFrame = composeTextView.frame
    newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
    composeTextView.frame = newFrame

    let newHeight = composeTextView.frame.height

    let changeHeight = newHeight - oldHeight

    if changeHeight != 0 {
        self.composeViewBottomConstraint.constant += changeHeight
    }
}

编辑 1:
根据 Michael 的建议,我进行了此更改,但效果并不一致。如果我按住退格键,它开始一次删除单词,最后它会比正常的 TextView 小。此外,如果我粘贴一段文本或删除一段文本,它不会正确响应:

var oldNumLines = CGFloat()

override func viewDidLoad() {
    super.viewDidLoad()

    composeTextView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)

    self.oldNumLines = composeTextView.contentSize.height / (composeTextView.font?.lineHeight)!

}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {

    let newNumLines = composeTextView.contentSize.height / (composeTextView.font?.lineHeight)!

    print("oldNumLines: \(oldNumLines)")
    print("newNumLines: \(newNumLines)")

    let diffNumLines = newNumLines - oldNumLines
    print("diffNumLines: \(diffNumLines)")

    let heightChange = (diffNumLines * (composeTextView.font?.lineHeight)!)
    print("heightChange: \(heightChange)")

    if diffNumLines > 1 {
        if composeTextView.frame.size.height < (5 * (composeTextView.font?.lineHeight)!) {
    composeTextView.frame.size.height += heightChange
    composeViewHeightConstraint.constant += heightChange
    }
    }

    if diffNumLines < -1 {

// the number 1.975216273089 is the first value of newNumLines
        if newNumLines > 1.975216273089 && composeTextView.frame.size.height < (5 * (composeTextView.font?.lineHeight)!) {
            composeTextView.frame.size.height += heightChange
            composeViewHeightConstraint.constant += heightChange
        }
    }

    if composeTextView.frame.size.height >= (5 * (composeTextView.font?.lineHeight)!) && composeTextView.frame.size.height < (6 * (composeTextView.font?.lineHeight)!) && diffNumLines < -1 {

        composeTextView.frame.size.height += heightChange
        composeViewHeightConstraint.constant += heightChange
    }

    self.oldNumLines = newNumLines
}

编辑 2:
我想出了如何解决最后的问题。我正在检查新旧行数 (diffNumLines) 的差异是否大于 1 或小于 -1。我没有意识到有时 diffNumLines 大约是 .95 或 -.95。有些情况下 diffNumLines 约为 .44,但我不想包括它,所以我更改了所有将 diffNumLines 值与 1 或 -1 进行比较的实例,因此将其与 .75 或 -.75 进行比较。然后我添加了几行,检查 heightChange 是大于 5 行的高度还是小于 5 行的负高度。 5行的高度实际上不是用数字5求出来的,我只好求出5行的实际高度。我在调试控制台和一些打印语句中发现,如果我一次输入一个单词,composeTextView 会在 100 的高度停止增加。它从 33 的高度开始,所以简单的减法告诉我们我最希望它改变的是高度是 67。我设置了一些规则,如果 heightChange > 67,heightChange 将被设置回 67,如果 heightChange < -67,它将被设置为 -67。这最终修复了所有问题,因此它很顺利并且没有显示任何错误。

我只有最后一个顾虑,它并不真正需要解决,但可能会被证明是有用的。如果我尝试对高度变化进行动画处理,它会显示 composeTextView 弹出到其新的 y 位置,然后对底部进行动画处理以填充其高度。这看起来真的很难看,所以我决定不使用动画。我希望的是 composeView 的底部会保持原样,而顶部会向上动画以填充其高度。我认为这需要改变它的顶部约束而不是它的高度,我不想再这样做了。

我对它的现状很满意,但仍有改进的空间。

这是我的最终代码:

@IBOutlet weak var composeTextItem: UIBarButtonItem!
@IBOutlet weak var composeUIView: UIView!
var oldNumLines = CGFloat()

override func viewDidLoad() {
    super.viewDidLoad()

    composeTextView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
    // the number 1.975216273089 is the first value of newNumLines
    self.oldNumLines = 1.97521627308861

}

deinit {
    composeTextView.removeObserver(self, forKeyPath: "contentSize")
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {

    let newNumLines = composeTextView.contentSize.height / (composeTextView.font?.lineHeight)!

    let diffNumLines = newNumLines - oldNumLines

    var heightChange = (diffNumLines * (composeTextView.font?.lineHeight)!)

    if heightChange > 67 {
        heightChange = 67
        }
    if heightChange < -67 {
        heightChange = -67
    }

    if diffNumLines > 0.75 {
        if composeTextView.frame.size.height < (5 * (composeTextView.font?.lineHeight)!) {
    composeTextView.frame.size.height += heightChange
    composeViewHeightConstraint.constant += heightChange
    }
    }

    if composeTextView.frame.size.height > 33 {
    if diffNumLines < -0.75 {
        // the number 1.975216273089 is the first value of newNumLines
        if newNumLines > 1.97521627308861 && composeTextView.frame.size.height < (5 * (composeTextView.font?.lineHeight)!) {
            composeTextView.frame.size.height += heightChange
            composeViewHeightConstraint.constant += heightChange
        }
    }

    if composeTextView.frame.size.height >= (5 * (composeTextView.font?.lineHeight)!) && composeTextView.frame.size.height < (6 * (composeTextView.font?.lineHeight)!) && diffNumLines < -0.75 {

        composeTextView.frame.size.height += heightChange
        composeViewHeightConstraint.constant += heightChange
    }
    }

    self.oldNumLines = newNumLines
}

我不确定您为什么要更改包含文本视图的视图的底部约束...如果您要更改 composeTextView 的高度,那么您也应该更改高度composeView 的数量相同。

此外,我不使用 textViewDidChange,而是将 contentSize 的观察者添加到 textview,然后它会在内容大小发生变化时调用一个函数。它让你的生活更轻松 - 如果你使用 Swift 4:

composeTextView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)

记得在deinit中调用composeTextView.removeObserver(self, forKeyPath: "contentSize")

然后,覆盖 observeValue 函数:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    // Change height of composeTextView and composeView here
}

适应高度变化的一个好方法是找到 old 行数与 new 行数之间的差异行数,因为你知道每次调用这个函数时,行数都是不同的。要获取行数,您可以执行 (Swift 4) 之类的操作:

let numLines = composeTextView.contentSize.height / (composeTextView.font?.lineHeight)!

计算线的差异,乘以composeTextView.font?.lineHeight,然后这就是你改变一切的高度。

希望这对您有所帮助。

编辑

如果您正确设置了约束条件,则不需要提升 composeView,这会比需要的工作量更大。当 composeView 的高度发生变化时,底部不应移动 - 只有顶部应该移动。 当你显示键盘时,我建议然后更改composeView位置,然后。当您更改高度并开始键入多行时,我建议仅在需要时更改高度。