Swift: высота строки tableView, ячейка tableView которой имеет вложенный tableView с динамикой c количество строк - PullRequest
0 голосов
/ 09 июля 2020

Я искал решение или лучший способ определить высоту строки tableView в heightForRowAt, которая имеет tableView на основе некоторых условий в модели данных.

Когда мои данные модель имеет тип данных под названием MULTISELECT, мне нужно отобразить ячейку с tableView внутри нее. В этом нет никаких проблем. Внутренние данные tableView назначаются во внешнем tableView cellForRowAt.

Вопрос в том, как получить высоту моей внешней строки tableView для ячеек типа MULTISELECT после того, как данные будут заполнены для внутреннего tableView rows?

Внешний код 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, когда оно будет готово / перезагружено данными. Но где мне реализовать этот logi c? При фиксированной высоте внутреннего tableView я получаю нежелательное поведение при прокрутке. Этого следует избегать.

Как мне go дальше с этим? Заранее спасибо!

1 Ответ

0 голосов
/ 09 июля 2020

Я думаю, что использование вложенного 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)
}
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...