使TextView适合内容并移动View Up - Swift

时间:2018-03-24 04:23:09

标签: ios swift uitextview

我有一个名为composeTextView的UITextView位于键盘正上方。这个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:
根据迈克尔的建议,我做了这个改变,但它不能始终如一地工作。如果我按住退格按钮并且它一次开始删除单词,它最终会以比正常的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大约是0.95或-95。有些情况下diffNumLines大概是.44并且我不想包含它,所以我改变了所有实例,我将diffNumLines值与1或-1进行了比较,因此它被比作0.75或-.75。然后我添加了几行,我检查heightChange是否大于5行的高度或小于5行的负高度。使用数字5实际上找不到5行的高度,我必须得到5行的实际高度。我发现在调试控制台和一些打印语句中,如果我一次输入一个单词,composeTextView会在高度为100时停止增加。它从33的高度开始,因此简单的减法告诉我们我希望它在高度是67.我设置了一些规则,所以如果heightChange&gt;如图67所示,heightChange将被设置回67并且如果heightChange&lt; -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
}

1 个答案:

答案 0 :(得分:2)

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

此外,我不会使用textViewDidChange,而是将contentSize的观察者添加到textview,然后只要内容大小发生变化,就会调用函数。它让你的生活变得更轻松 - 如果你正在使用Swift 4,那就是这样的事情:

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

请务必在composeTextView.removeObserver(self, forKeyPath: "contentSize")

中致电deinit

然后,覆盖observeValue函数:

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

适应高度变化的一个好方法是找出行数与行数之间的差异,因为你知道每次调用此函数时,行数都不同。要获得行数,你可以做一些事情(Swift 4),如:

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

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

希望这有帮助。

修改

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