将UITextView嵌入UITableViewCell中
在Workable,我们有一个称为评估的功能。 部分原因是可以填写面试套件,如下所示。
当我们开始研究如何实现此功能时,出现的一个想法是使用堆栈视图,但是用户创建了采访工具包,因此它们的大小可能会有所不同。 因此,我们选择使用UITableView作为此屏幕的主干。 毕竟它只是UITableViewCell中的UITextView,对吗?
在本文中,我将尝试列出我们面临的所有问题以及如何解决这些问题。 在演练结束时,将附上完整的解决方案。
在单元格内添加UITextView
我们创建了一个原型单元,并添加了UITextView,如下所示。
另外,不要忘记在UITextView的属性检查器中关闭 Scrolling Enabled属性。 这使UITextView具有取决于文本的固有内容大小,或者接近于我们稍后发现的文本大小。
现在,我们希望能够编辑文本。 简单,只需启用UITextView的`Editable`属性即可。
但是我们有一个小问题,我们希望UITableViewCell跟随UITextView的高度。
我们可以从单元格创建一个回调,以通知我们任何文本更改,如下所示:
在TextTableViewCell.swift中
@IBOutlet弱var textView:UITextView!
var textChanged:(((String)-> Void)?
覆盖func awakeFromNib(){
super.awakeFromNib()
textView.delegate =自我
}
func textChanged(action:@escaping(String)-> Void){
self.textChanged =操作
}
func textViewDidChange(_ textView:UITextView){
textChanged?(textView.text)
}
在TableTableViewController.swift-> cellForRow中
cell.textChanged {[weak tableView](newText:String)在
}
注意:不要忘了在此闭包的捕获列表中使tableView变弱,因为您将得到一个保留周期。 tableView->单元格(UI),单元格-> tableView(关闭)
我们唯一要做的就是重新加载单元。
我们有多种方法可以做到这一点,因此我们将尝试最常见的一种方法tableView.reloadData()。
cell.textChanged {[weak tableView](_)in
//可能的解决方案:1
//不,单元格发生一次更改然后失去焦点
tableView?.reloadData()
}
使用reload数据,您只能对文本视图进行一次更改,因为reloadData()是异步工作的。 所以我们不能使用它。
好的,那么我们将尝试重新加载单元。
cell.textChanged {[weak tableView](_)in
//可能的解决方案:2
//不,重新加载单元格,文本甚至都没有变化
tableView?.reloadRows(at:[indexPath],带有:.none)
}
使用reloadRows,由于它具有同步特性,我们甚至都无法进行任何更改,只是失去了UITextView的关注。 所以我们也不能使用它。
我们可以尝试做的另一件事就是告诉tableView仅重新布局自身。
我们可以通过调用beginUpdates()和endUpdates()来实现。
cell.textChanged {[weak tableView](_)in
//可能的解决方案:3
//似乎可以工作
tableView?.beginUpdates()
tableView?.endUpdates()
}
该解决方案似乎确实有效 ! 具有使布局更改具有动画效果的附加好处!
注意:如果您不希望对布局更改进行动画处理,则可以在UIView.performWithoutAnimation()中调用beginUpdates()endUpdates()。
结果:
但是这个解决方案还没有结束。
UIScrollView和UITextView / UITextField
当UIScrollView内有UITextView或UITextField时,scrollview调整其contentOffset以使UITextView的光标可见。 在我们的情况下,当用户开始键入并且光标移到键盘下方时,您会注意到一种奇怪的滚动行为。
例:
解
当用户键入字母时,光标的焦点和tableView的布局会发生冲突。 我们唯一需要做的就是像这样将下一个运行循环分派到主队列,从而推迟其布局:
cell.textChanged {[weak tableView](_)in
DispatchQueue.main.async {
tableView?.beginUpdates()
tableView?.endUpdates()
}
}
现在就可以了!
是吗 取决于应用程序的部署目标。 如果您是从iOS 11或更高版本进行定位的,那就可以了! 如果没有,请继续阅读。
奇怪的滚动和布局问题
当您向下滚动UITableView并尝试编辑文本时,您会注意到奇怪的跳跃行为和布局错误的单元格。
经过一些实验,我们发现,奇怪的滚动行为具有更高的RowHeight estimatedRowHeight
值“更奇怪”。 因此,我们怀疑这与UITableView如何计算行的高度有关。
所以我们决定去上学。
手动计算像元高度
首先关闭自动高度计算,并将estimatedRowHeight
高度设置为0。
然后,我们将手动计算每个单元格的高度,以便UITableView可以正确布局单元格,并且高度会随着用户的输入而变化。
我们将需要此扩展名,该扩展名将为您提供String的高度,并提供将要呈现的可用宽度和字体。
扩展字符串{
func heightWithConstrainedWidth(width:CGFloat,字体:UIFont)-> CGFloat {
让constraintRect = CGSize(宽度:宽度,高度:.greatestFiniteMagnitude)
让boundingBox = self.boundingRect(带有:constraintRect,选项:[。usesFontLeading,.usesLineFragmentOrigin],属性:[NSAttributedStringKey.font:font],上下文:nil)
返回boundingBox.height
}
}
现在我们可以使用它来计算每个单元的高度。
公共重写func tableView(_ tableView:UITableView,heightForRowAt indexPath:IndexPath)-> CGFloat {
返回模型[indexPath.row]
.heightWithConstrainedWidth(width:tableView.frame.width,字体:UIFont.systemFont(ofSize:14))
}
结果:
如您所见,该行的确改变了高度,但是UITextView被裁剪了,我们也没有奇怪的滚动行为! 我们越来越近了!
如果您仔细查看UITextViews,尽管与超级视图之间的距离为0,但仍会注意到某种填充。
为了使这一点更加清晰,我们使用了一个红色背景的半透明标签 ,我们将UITextView的背景设置为灰色,并且为了清晰起见,将单元格的高度增加了100点。 UILabel和UITextView具有相同的字体和字体大小的文本。
结果:
因此,我们的下一步是从UITextView中删除该填充。 为此,我们将UITextView子类化,删除了填充,并将其用作替代品。
@IBDesignable类ZeroPaddingTextView:UITextView {
覆盖func layoutSubviews(){
super.layoutSubviews()
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
}
}
现在,UILabel和UITextView中的文本位置完全相同!
重要!
现在,我们手动计算了行的高度,我们不需要UITableViewCell内容视图和UITextView之间的底部约束,因为我们知道这两个在设计上具有相同的高度。
最后结果:
太好了,现在可以正常使用了! 🎉
示例项目在这里。
希望对您有所帮助! 另外, 欢迎反馈 !