自动调整表格视图单元格内的自动调整表格视图
Self sizing tableview inside self sizing tableview cell
假设我有这样的层次结构:
*TableViewCell
**TableView
***TableViewCell
并且所有这些都应该可以调整大小。有人遇到过这种问题吗?过去我使用了很多解决方法,例如 systemLayoutSizeFitting
或 heightForRowAt
中的高度预先计算,但它总是会打破一些约束,因为 TableViewCell
的高度约束等于估计的行高,并且出现一些各种神奇的行为。有什么办法让这个活起来吗?
当前解决方法:
class SportCenterReviewsTableCell: UITableViewCell, MVVMView {
var tableView: SelfSizedTableView = {
let view = SelfSizedTableView(frame: .zero)
view.clipsToBounds = true
view.tableFooterView = UIView()
view.separatorStyle = .none
view.isScrollEnabled = false
view.showsVerticalScrollIndicator = false
view.estimatedRowHeight = 0
if #available(iOS 11.0, *) {
view.contentInsetAdjustmentBehavior = .never
} else {
// Fallback on earlier versions
}
return view
}()
private func markup() {
contentView.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.register(ReviewsTableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.snp.makeConstraints() { make in
make.top.equalTo(seeAllButton.snp.bottom).offset(12)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
make.bottom.lessThanOrEqualTo(contentView.snp.bottom)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ReviewsTableViewCell
cell.viewModel = viewModel.cellViewModels[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ReviewsTableViewCell
cell.viewModel = viewModel.cellViewModels[indexPath.row]
cell.setNeedsLayout()
cell.layoutIfNeeded()
let size = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)
return size.height
}
}
自动调整 tableView class:
class SelfSizedTableView: UITableView {
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}
override var intrinsicContentSize: CGSize {
self.setNeedsLayout()
self.layoutIfNeeded()
return contentSize
}
}
这里是一个示例,说明如何在 table 视图单元格内制作一个 table 视图,并为单元格设置自动高度。
您应该使用 'ContentSizedTableView' class 作为内部 table 视图。
class ViewController: UIViewController {
@IBOutlet weak var outerTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
outerTableView.rowHeight = UITableView.automaticDimension
outerTableView.estimatedRowHeight = UITableView.automaticDimension
outerTableView.delegate = self
outerTableView.dataSource = self
}
}
final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
sizeToFit()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableTableViewCell
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
- 使用 xib 文件来简化层次结构。
- 在故事板上获取一个 tableView,并为您的 tableViewCell(比如 CustomTableViewCell)创建一个 nib 文件。在其中创建一个 tableView 并再次创建一个 tableViewCell xib 文件。现在,不需要在你的 xib 文件中设置标签,(如果你只想要单元格中的标签而不需要其他任何东西,如果不需要,还有另一种添加约束的方法)
- 假设你有一个文本数组,有些字符串很长,有些很短。
- 在 CustomTableViewCell 中注册 nib 文件并扩展它以使用 Delegate 和 DataSource。
- 在 ViewController 中注册此 CustomTableViewCell。
- 在 CustomTableViewCell 中声明单元格时,只需执行=
- cell.textLabel?.text = 内容
- cell.textLabel?.numberOfLines = 0
- 使用heightForRowAt设置外层tableViewCell的高度,让内层tableView向内滚动。
这实际上不是问题的答案,只是解释。
(由于评论的字符数限制写在这里)。
问题是您试图在另一个垂直滚动视图中插入一个垂直滚动视图。如果不禁用嵌套 tableview 的滚动能力,则在滚动时会出现故障,因为系统不知道将滚动事件传递给谁(传递给嵌套 tableview 或 parent tableview)。
所以在我们的例子中,你必须为嵌套的 tableviews 禁用 "scrollable" 属性,因此你必须将嵌套的 tableview 的高度设置为等于它的内容大小。但是这样你将失去 tableview 的优势(即单元格重用优势)并且它与使用实际的 UIScrollView 相同。但是,另一方面,由于您必须将高度设置为等于其内容大小,因此根本没有理由使用 UIScrollView,您可以将嵌套单元格添加到 UIStackView,并且您的 tableview 将具有此层次结构:
*TableView
**TableViewCell
***StackView
****Items
****Items
****Items
****Items
但同样,正确的解决方案是使用 multi-sectional tableview。让你的单元格成为表格视图的 headers 部分,让内部单元格成为表格视图的行。
假设我有这样的层次结构:
*TableViewCell
**TableView
***TableViewCell
并且所有这些都应该可以调整大小。有人遇到过这种问题吗?过去我使用了很多解决方法,例如 systemLayoutSizeFitting
或 heightForRowAt
中的高度预先计算,但它总是会打破一些约束,因为 TableViewCell
的高度约束等于估计的行高,并且出现一些各种神奇的行为。有什么办法让这个活起来吗?
当前解决方法:
class SportCenterReviewsTableCell: UITableViewCell, MVVMView {
var tableView: SelfSizedTableView = {
let view = SelfSizedTableView(frame: .zero)
view.clipsToBounds = true
view.tableFooterView = UIView()
view.separatorStyle = .none
view.isScrollEnabled = false
view.showsVerticalScrollIndicator = false
view.estimatedRowHeight = 0
if #available(iOS 11.0, *) {
view.contentInsetAdjustmentBehavior = .never
} else {
// Fallback on earlier versions
}
return view
}()
private func markup() {
contentView.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.register(ReviewsTableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.snp.makeConstraints() { make in
make.top.equalTo(seeAllButton.snp.bottom).offset(12)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
make.bottom.lessThanOrEqualTo(contentView.snp.bottom)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ReviewsTableViewCell
cell.viewModel = viewModel.cellViewModels[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ReviewsTableViewCell
cell.viewModel = viewModel.cellViewModels[indexPath.row]
cell.setNeedsLayout()
cell.layoutIfNeeded()
let size = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)
return size.height
}
}
自动调整 tableView class:
class SelfSizedTableView: UITableView {
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}
override var intrinsicContentSize: CGSize {
self.setNeedsLayout()
self.layoutIfNeeded()
return contentSize
}
}
这里是一个示例,说明如何在 table 视图单元格内制作一个 table 视图,并为单元格设置自动高度。
您应该使用 'ContentSizedTableView' class 作为内部 table 视图。
class ViewController: UIViewController {
@IBOutlet weak var outerTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
outerTableView.rowHeight = UITableView.automaticDimension
outerTableView.estimatedRowHeight = UITableView.automaticDimension
outerTableView.delegate = self
outerTableView.dataSource = self
}
}
final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
sizeToFit()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableTableViewCell
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
- 使用 xib 文件来简化层次结构。
- 在故事板上获取一个 tableView,并为您的 tableViewCell(比如 CustomTableViewCell)创建一个 nib 文件。在其中创建一个 tableView 并再次创建一个 tableViewCell xib 文件。现在,不需要在你的 xib 文件中设置标签,(如果你只想要单元格中的标签而不需要其他任何东西,如果不需要,还有另一种添加约束的方法)
- 假设你有一个文本数组,有些字符串很长,有些很短。
- 在 CustomTableViewCell 中注册 nib 文件并扩展它以使用 Delegate 和 DataSource。
- 在 ViewController 中注册此 CustomTableViewCell。
- 在 CustomTableViewCell 中声明单元格时,只需执行=
- cell.textLabel?.text = 内容
- cell.textLabel?.numberOfLines = 0
- 使用heightForRowAt设置外层tableViewCell的高度,让内层tableView向内滚动。
这实际上不是问题的答案,只是解释。
(由于评论的字符数限制写在这里)。
问题是您试图在另一个垂直滚动视图中插入一个垂直滚动视图。如果不禁用嵌套 tableview 的滚动能力,则在滚动时会出现故障,因为系统不知道将滚动事件传递给谁(传递给嵌套 tableview 或 parent tableview)。 所以在我们的例子中,你必须为嵌套的 tableviews 禁用 "scrollable" 属性,因此你必须将嵌套的 tableview 的高度设置为等于它的内容大小。但是这样你将失去 tableview 的优势(即单元格重用优势)并且它与使用实际的 UIScrollView 相同。但是,另一方面,由于您必须将高度设置为等于其内容大小,因此根本没有理由使用 UIScrollView,您可以将嵌套单元格添加到 UIStackView,并且您的 tableview 将具有此层次结构:
*TableView
**TableViewCell
***StackView
****Items
****Items
****Items
****Items
但同样,正确的解决方案是使用 multi-sectional tableview。让你的单元格成为表格视图的 headers 部分,让内部单元格成为表格视图的行。