如何使用 begin-endUpdates() 和多个自定义单元格展开/折叠 UITableViewCells?

Posted

技术标签:

【中文标题】如何使用 begin-endUpdates() 和多个自定义单元格展开/折叠 UITableViewCells?【英文标题】:How to expand/collapse UITableViewCells using begin-endUpdates() and multiple custom cells? 【发布时间】:2016-10-28 14:22:30 【问题描述】:

背景:单个 ViewController 包含 4 个自定义原型单元。第一个始终可见,并具有UITextField。 原型单元 2、3 和 4 分别包含图像、标签和标签。但是,原型单元 4 是基于数组的,可以有 1 行或多行,具体取决于第一个单元中的输入。

当在第一个单元格中输入某个字符串时,会检查是否存在一个对象,该对象的某个属性中包含该字符串。如果该值不正确,则第一个单元格会更改高度和布局。如果值正确,第一个单元格会改变高度,最重要的是其他 3 个原型单元格会展开以显示对应于字符串输入的对象的详细信息。如果用户随后输入了另一个不正确的字符串,则单元格会崩溃。

问题: 为其他 3 个单元格的展开/折叠设置动画。我无法弄清楚如何在 beginUpdates() 和 endUpdates() 之间定义 numberOfRowsInSection() 方法和代码块(下面我的代码中的第 2 步)。即使没有 arraycells 实现,在 insertRows() 之后调用 reloadRows() 似乎也不起作用。

我尝试了什么:

reloadData() = 正确显示数据,但我不能使用它,因为它不会提供必要的动画。 beginUpdates() 和 endUpdates() 之间没有任何内容 = 给出以下错误:

无效更新:第 0 节中的行数无效。更新后现有节中包含的行数 (6) 必须等于更新前该节中包含的行数 (1),加上或减去从该节插入或删除的行数(0 插入,0 删除)加上或减去移入或移出该节的行数(0 移入,0 移出)。'

我相信这与最后一个原型单元格基于数组的事实有关,我没有重新加载单元格的实际数据,只是视图。

与 insertRowsAtIndexPath、reloadRowsAtIndexPath、...的其他组合 = 给出与行数相关的类似错误。

任何帮助将不胜感激!

简化代码:

class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, updateUITableView 

    var people: [Person] = [
        Person(name: "name", image: "imagename1", description: "description1", children: ["name1","name2","name3"]),
        Person(name: "name2", image: "imagename3", description: "description2", children: ["name4", "name5", "name6"])
    ]

    enum Flag 
        case start
        case match
        case noMatch
    
    var flag = Flag.start

    var globalObject: Person?

    @IBOutlet var tableView: UITableView!

    func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        switch (flag) 
        case .start :
            return 1
        case .noMatch:
            return 1
        case .match:
            return 3 + (globalObject?.children.count)! //PROBLEM!
        
    

    //START - Input received from tableViewCell
    func checkName(senderCell: SearchInputTableViewCell, name: String) 
        // STEP 1: Check if person exists
        let person = checkPersonNameMatch(nameInput: name)
        globalObject = person //save globally for cellForRowsAtIndexPath

        // STEP 2: Show/hide cells
        toggleCellsVisibility(person: person)
    

    //STEP 1:
    func checkPersonNameMatch(nameInput: String) -> Person? 
        guard let index = people.index(where: $0.name == nameInput) else 
            flag = .noMatch
            return nil
        
        let personFound = people[index]
        flag = .match
        globalObject = personFound
        return personFound
    

    //STEP 2 = PROBLEM!
        func toggleCellsVisibility(person: Person?) 
    if person != nil  //Cells appear
        UIView.animate(withDuration: 0.7, animations: 

            self.tableView.beginUpdates()

            let indexPath: IndexPath = IndexPath(row: 1, section: 0) //for Image cell
            let indexPath2: IndexPath = IndexPath(row: 2, section: 0) //for Description cell
            //let indexPath3: IndexPath = IndexPath[....] //for array in Children cell

            self.tableView.insertRows(at: [indexPath], with: .bottom)
            self.tableView.insertRows(at: [indexPath2], with: .bottom)

            self.tableView.reloadRows(at: [indexPath], with: .bottom)
            self.tableView.reloadRows(at: [indexPath2], with: .bottom)
            //... a for-loop to reload the array cells here?

            self.tableView.endUpdates()
        )
     else  //Cells disappear
        UIView.animate(withDuration: 0.4, animations: 
            self.tableView.beginUpdates()
            //... same code as above?
            self.tableView.endUpdates()
        )
    


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        switch indexPath.row 
        case 0:
            let cellIdentifier = "InputCell"
            let inputCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTableViewCell

            inputCell.delegate = self

            switch (flag) 
            case .start:
                self.tableView.rowHeight = 187
            case .match:
                self.tableView.rowHeight = 170
            case .noMatch:
                self.tableView.rowHeight = 200
            

            return inputCell

        case 1:
            let cellIdentifier = "ImageCell"
            let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ImageTableViewCell

            cell.image?.image = UIImage(named: (globalObject?.image)!)

            switch (flag) 
            case .start:
                self.tableView.rowHeight = 0
            case .match:
                self.tableView.rowHeight = 170
            case .noMatch:
                self.tableView.rowHeight = 0
            

            return cell

        case 2:
            let cellIdentifier = "DescriptionCell"
            let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! DescriptionTableViewCell

            cell.description?.text = globalObject?.description

            switch (flag) 
            case .start:
                self.tableView.rowHeight = 0
            case .match:
                self.tableView.rowHeight = 54
            case .noMatch:
                self.tableView.rowHeight = 0
            

            return cell

        default:
            let cellIdentifier = "ChildrenCell"
            let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ChildrenTableViewCell

            cell.childName?.text = globalObject?.children[(indexPath as IndexPath).row - 3]

            switch (flag) 
            case .start:
                self.tableView.rowHeight = 0
            case .match:
                self.tableView.rowHeight = 44
            case .noMatch:
                self.tableView.rowHeight = 0
            

            return cell
        
    

【问题讨论】:

【参考方案1】:

我的建议是:不要插入或删除这些行。只需将行一直保留在那里 - 高度为零(并将clipsToBounds 设置为true)。因此,在您需要它们之前,它们对用户是不可见的。当您需要它们时,只需更改高度并致电beginUpdatesendUpdates

【讨论】:

我尝试了这种方法,但它给了我如上所述的“无效行数”错误。我的情况与您链接的情况之间的区别在于我的数据的依赖性。出现/展开的单元格的数据(和数量)基于第一个单元格中的输入。所以我不清楚如何在一开始就定义它们,因为我事先不知道会有多少以及它们将包含什么数据。 "当我事先不知道会有多少以及它们将包含什么数据时" 好的,这很公平。但是在调用beginUpdates 之前,您必须始终更新您的模型。基本上,这与您添加行时的 any 没有什么不同。在你的情况下,据我所知,仍然不需要做任何 row 插入。 所以我会改变你的模型,然后重新加载所有高度为 0 的数据。现在你已经完成了第一步。现在改变高度并调用beginUpdatesendUpdates,就像我之前说的那样。那是第二步。完成! 谢谢!我想我现在更清楚地理解了我的问题。但是仅使用 begin/endUpdates() 我仍然会收到“无效的行数”错误。我相信我已经有了 cellForRowAtIndexPaths() 中的 switch 语句所涵盖的高度调整。因此,正如您提到的,它必须与更新模型有关。我认为 numberOfRowsInSection() 中的开关与 toggleCellsVisibility 中的 if/else 语句相结合会处理模型更新吗?标志或 globalObject 变量是否以某种方式使用错误?【参考方案2】:

我建议您使用枚举更改数据布局,这样您就不必担心行数,当您拥有数据时可以计算出来。示例代码如下:

enum CellType 
    case textField
    case labelCell
    case array

在你的tableView中包含一些相关的东西

class TableViewController: UITableViewController 
    var order = [CellType]()

    func calculateRowOrder() 
        // You can also customize which cells you want here, or if they're dynamic
        order = [.textField, .labelCell]

        // Add as many .array as child cells
        for _ in globalObject?.children ?? [] 
            order.append(.array)
        
    

然后您可以更轻松地为数据源执行方法:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    return order.count

以及单元格定制:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let row = indexPath.row

    let cellType = order[row]

    switch cellType 
    case .textField:
        <Customize textField>
    case .label:
        <Customize label>
    case .array:
        // You can figure out which child element you're customizing
        <Customize array>
    

每当您更改数据或需要重新加载数据、重新计算订单并重新加载数据时。祝你好运。

【讨论】:

当我在我的代码中实现这一点时,我得到了一些奇怪的结果......在第一个单元格中输入一个值并使用 reloadData() 重新加载 tableView 后,我只看到最后几个单元格。然后当我在任何地方触摸屏幕并滚动时,屏幕突然立即正确显示所有单元格。什么可能导致此故障?也许它与标志和我需要调用calculateRows()的时刻有关?我将 calculateRows() 放在 viewDidLoad() 中(以在打开屏幕时获取第一个单元格)和 toggleCellsVisibilty() 以重新计算并获取新行。 此外,虽然使代码更具可读性和简洁性,但它仍然无法修复我在原始帖子中遇到的 beginUpdates()/endUpdates() 崩溃。我仍然收到有关“无效行数”的相同错误消息。 :// 你不需要使用beginUpdates()/endUpdates(),只需要tableView.reloadData()

以上是关于如何使用 begin-endUpdates() 和多个自定义单元格展开/折叠 UITableViewCells?的主要内容,如果未能解决你的问题,请参考以下文章

如何查看手机内存和CPU使用情况

如何接收和使用 NSErrorPointer

如何使用 SVG 进行缩放和平移

如何在WTL和MFC中使用duilib及如何静态使用duilib库

你好,请问swiperjs如何安装和使用?

GeoLite City如何使用,如何获取startIpNum和endIpNum