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 行的高度

StackView 高度 Swift

Swift - 根据其内容自动布局 UILabel 高度

如何在 Swift 中截取 UITableView 中选定行的屏幕截图并将其保存为 UIImage?

swift 使用“自动布局”将UITableView的高度设置为其内容的高度

如何在Swift中的UITableView中截取所选行的屏幕截图并将其保存为UIImage?