【问题标题】:Swift: height of tableView row whose tableView cell has nested tableView with dynamic number of rowsSwift:tableView 行的高度,其 tableView 单元格具有动态行数的嵌套 tableView
【发布时间】: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,我得到了不需要的滚动行为。这需要避免。

我该如何走得更远? 提前致谢!

【问题讨论】:

  • 我在没有自动维度的情况下通过计算单元格的高度做了类似的行为。
  • 酷,你能分享一下你做了什么吗?我真的很挣扎。
  • 真的需要在单元格中嵌套表格视图吗?也就是说,他们会做表格视图所做的事情——滚动、重用单元格、允许编辑/删除/重新排列/等等?还是您只需要“重复元素”?通常,多节表视图比嵌套表视图更易于管理。如果你真的想嵌套表格视图,这里有一种方法:stackoverflow.com/a/57995132/6257435
  • 是的,我真的需要在我的单元格中嵌套 tableView。我有可扩展和可折叠的部分。然后每个部分都有允许编辑的重复使用单元格的行。 :)
  • @LohithKorupolu - 使用多部分表格视图和展开/折叠部分通常要容易得多。但是,如果您真的打算使用嵌套表格视图,请点击该链接 - 此链接 stackoverflow.com/a/56840758/6257435 显示了它如何在滚动视图中使用(这将直接应用于将其用作单元格中的嵌套表格视图)。

标签: ios swift uitableview


【解决方案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)
}
}

【讨论】:

    猜你喜欢
    • 2017-11-14
    • 1970-01-01
    • 2016-10-19
    • 2019-05-23
    • 2023-04-09
    • 1970-01-01
    • 2012-08-09
    • 1970-01-01
    • 2015-08-01
    相关资源
    最近更新 更多