Swift:tableView 行的高度,其 tableView 单元格具有动态行数的嵌套 tableView
Posted
技术标签:
【中文标题】Swift:tableView 行的高度,其 tableView 单元格具有动态行数的嵌套 tableView【英文标题】:Swift: height of tableView row whose tableView cell has nested tableView with dynamic number of rows 【发布时间】:2020-07-09 07:25:04 【问题描述】:我一直在寻找解决方案或最佳方法来确定heightForRowAt
中 tableView 行的高度,它具有基于数据模型中某些条件的 tableView。
当我的数据模型有一个名为MULTISELECT
的数据类型时,我需要显示一个带有tableView 的单元格。这样做没有问题。内层tableView的数据分配在外层tableView的cellForRowAt
中。
这里的问题是,在为内部 tableView 行填充数据后,如何获取MULTISELECT
类型单元格的外部 tableView 行的高度?
外部 tableView 代码(在 ViewController 内)-
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
guard let preferenceCategories = self.preferenceCategories else
return UITableViewCell()
let categoryCode = preferenceCategories[indexPath.section].code
let filteredPreferenceSet = self.preferenceSet.filter($0.categoryCode == categoryCode).filter($0.dataType == "BOOLEAN"/* || $0.dataType == "MULTISELECT"*/)
if let preferenceDataType = filteredPreferenceSet[indexPath.row].dataType
if preferenceDataType == "BOOLEAN"
let cell = self.tableView.dequeueReusableCell(withIdentifier: "CustPrefSetCell", for: indexPath) as! CustPrefSetCell
cell.preferenceName.text = filteredPreferenceSet[indexPath.row].name
cell.preferenceDescription.text = filteredPreferenceSet[indexPath.row].description
cell.switchDelegate = self
let propertyValue = ((filteredPreferenceSet[indexPath.row].value ?? "false") as NSString).boolValue
propertyValue ? cell.preferenceSwitch.setOn(true, animated: true) : cell.preferenceSwitch.setOn(false, animated: true)
cell.preferenceCode = filteredPreferenceSet[indexPath.row].code
return cell
else if preferenceDataType == "MULTISELECT"
let multiSelectCell = self.tableView.dequeueReusableCell(withIdentifier: "CustPrefMultiSelectTableViewCell", for: indexPath) as! CustPrefMultiSelectTableViewCell
multiSelectCell.preferenceValues = filteredPreferenceSet[indexPath.row].preferenceValues
// self.rowHeight = multiSelectCell.tableView.contentSize.height
return multiSelectCell
else
return UITableViewCell()
else
return UITableViewCell()
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
return UITableView.automaticDimension
内部tableView在multiSelectCell
里面,其代码如下-
class CustPrefMultiSelectTableViewCell: UITableViewCell
@IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var preferenceDescription: UILabel!
@IBOutlet weak var preferenceTitle: UILabel!
@IBOutlet weak var tableView: UITableView!
var preferenceValues: [PreferenceValue]?
override func awakeFromNib()
super.awakeFromNib()
self.tableView.delegate = self
self.tableView.dataSource = self
guard let frameworkBundle = Bundle(identifier: "com.frameworkbundle.asdf") else
fatalError("Framework bundle identifier is incorrect.")
let custPrefHeaderCell = UINib(nibName: "CustPrefMultiSelectPreferenceTableViewCell", bundle: frameworkBundle)
self.tableView.register(custPrefHeaderCell, forCellReuseIdentifier: "CustPrefMultiSelectPreferenceTableViewCell")
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 64.0
override func setSelected(_ selected: Bool, animated: Bool)
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
extension CustPrefMultiSelectTableViewCell: UITableViewDataSource, UITableViewDelegate
func numberOfSections(in tableView: UITableView) -> Int
return 1
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
guard let preferenceValues = self.preferenceValues else
return 0
return preferenceValues.count
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
guard let preferenceCategories = self.preferenceValues else
return UITableViewCell()
let cell = self.tableView.dequeueReusableCell(withIdentifier: "CustPrefMultiSelectPreferenceTableViewCell", for: indexPath) as! CustPrefMultiSelectPreferenceTableViewCell
cell.preferenceName.text = preferenceCategories[indexPath.row].name
cell.preferenceDescription.text = preferenceCategories[indexPath.row].description
return cell
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
return UITableView.automaticDimension
我想到了一种方法,对内部 tableView 设置高度约束,并在外部 tableView 准备好/重新加载数据时更新它的高度。但是我应该在哪里实现这个逻辑?使用固定高度的内部 tableView,我得到了不需要的滚动行为。这需要避免。
我该如何走得更远? 提前致谢!
【问题讨论】:
我在没有自动维度的情况下通过计算单元格的高度做了类似的行为。 酷,你能分享一下你做了什么吗?我真的很挣扎。 您真的需要在单元格中嵌套表格视图吗?也就是说,他们会做表格视图所做的事情——滚动、重用单元格、允许编辑/删除/重新排列/等等?还是您只需要“重复元素”?通常,多节表视图比嵌套表视图更易于管理。如果你真的想嵌套表格视图,这里有一种方法:***.com/a/57995132/6257435 是的,我真的需要在我的单元格中嵌套 tableView。我有可扩展和可折叠的部分。然后每个部分都有允许编辑的重复使用单元格的行。 :) @LohithKorupolu - 使用多部分表格视图和展开/折叠部分通常要容易得多。但是,如果您真的打算使用嵌套表格视图,请点击该链接 - 此链接 ***.com/a/56840758/6257435 显示了它如何在滚动视图中使用(这将直接应用于将其用作单元格中的嵌套表格视图)。 【参考方案1】:我认为使用嵌套的tableView并不是最好的解决方案,无论如何,我希望这个例子对你有所帮助。
struct Foo
let strings: [String]
class NestedViewController: UIViewController
let dataSource = [Foo(strings: ["String1", "String2"]),
Foo(strings: ["Long long long long long long long long long long long long long string"])]
let tableView: UITableView =
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(NestedCell.self, forCellReuseIdentifier: NestedCell.identifier)
tableView.tableFooterView = UIView()
return tableView
()
override func viewDidLoad()
super.viewDidLoad()
view.addSubview(tableView)
setupConstraints()
tableView.dataSource = self
tableView.delegate = self
tableView.reloadData()
func setupConstraints()
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
extension NestedViewController: UITableViewDelegate & UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
dataSource.count
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
guard let cell = tableView.dequeueReusableCell(withIdentifier: NestedCell.identifier, for: indexPath) as? NestedCell else
return UITableViewCell()
cell.setup(foo: dataSource[indexPath.row])
return cell
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
NestedCell.heightFor(foo: dataSource[indexPath.row])
class NestedCell: UITableViewCell
static let identifier = "NestedCell"
let nestedTableView: UITableView =
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(TextCell.self, forCellReuseIdentifier: TextCell.identifier)
tableView.tableFooterView = UIView()
return tableView
()
private var foo = Foo(strings: [""])
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(nestedTableView)
setConstraints()
nestedTableView.dataSource = self
nestedTableView.delegate = self
required init?(coder: NSCoder)
fatalError("init(coder:) has not been implemented")
func setup(foo: Foo)
self.foo = foo
nestedTableView.reloadData()
static func heightFor(foo: Foo) -> CGFloat
foo.strings.reduce(0) $0 + TextCell.heightFor(text: $1)
private func setConstraints()
NSLayoutConstraint.activate([
nestedTableView.topAnchor.constraint(equalTo: contentView.topAnchor),
nestedTableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
nestedTableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
nestedTableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
])
extension NestedCell: UITableViewDelegate & UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
foo.strings.count
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
guard let cell = tableView.dequeueReusableCell(withIdentifier: TextCell.identifier, for: indexPath) as? TextCell else
return UITableViewCell()
cell.setup(text: foo.strings[indexPath.row])
return cell
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
TextCell.heightFor(text: foo.strings[indexPath.row])
class TextCell: UITableViewCell
static let identifier = "TextCell"
static let labelOffset: CGFloat = 10
private let label: UILabel =
let label = UILabel()
label.numberOfLines = 0
label.font = .systemFont(ofSize: 15, weight: .medium)
label.translatesAutoresizingMaskIntoConstraints = false
return label
()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(label)
setConstraints()
required init?(coder: NSCoder)
fatalError("init(coder:) has not been implemented")
func setup(text: String)
label.text = text
static func heightFor(text: String) -> CGFloat
text.height(width: UIScreen.main.bounds.width - 2 * TextCell.labelOffset,
font: .systemFont(ofSize: 15, weight: .medium)) + 2 * TextCell.labelOffset
private func setConstraints()
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: TextCell.labelOffset),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -TextCell.labelOffset),
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: TextCell.labelOffset),
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -TextCell.labelOffset)
])
extension String
func height(width: CGFloat, font: UIFont) -> CGFloat
let rect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
return ceil(boundingBox.height)
【讨论】:
以上是关于Swift:tableView 行的高度,其 tableView 单元格具有动态行数的嵌套 tableView的主要内容,如果未能解决你的问题,请参考以下文章
如何快速更改 UISearchController 行的高度
如何在 Swift 中截取 UITableView 中选定行的屏幕截图并将其保存为 UIImage?