使 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
位置,然后仅。当您更改高度并开始键入多行时,我建议仅在需要时更改高度。
我有一个 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
位置,然后仅。当您更改高度并开始键入多行时,我建议仅在需要时更改高度。