点击 headers 部分时如何将数据动态添加到 UITableView

How to add data dynamically to UITableView when tapped on section headers

我在将数据动态加载到 UITableView 部分时遇到问题。我的业务需求是我有一个名为 "Courses" 的 ViewController,在这个视图中我有一个 tableView,其中包含我使用 TableViewHeaderFooterView 的不同部分,每个 header它将包含相关的课程名称、该课程的章节数和该课程的作业数,我从 API 调用中获取所有这些数据我能够填充 tableView headers 与此数据,而且我将为每门课程获得一个 'id',我将其添加为每个 header 的标签。现在,当我点击 header 中的任何一个时,我必须通过发送 header 的标记值(即 courseID)来进行另一个 API 调用,因此我将获取 tableView 的数据源它应该扩展该部分,显示来自数据源的行和行中的数据。

在点击 header 之前,我可以使用拥有数据源的静态数据来完成所有这些操作,但是如果要动态添加数据,我不知道该怎么做点击 headers.

我试过这样做,当我第一次点击任何 header 时它会显示该部分的数据,但如果我再次点击相同的 header 或任何其他 header 我遇到

崩溃

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (0) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).

我在这里发布我的模型和代码:

课程名称模型:

struct CourseNamesModel {

var courseName: String!
var courseNameLetter: String!
var numberOfChaptersAndAssignments: String!
var chapterCount: Int!
var courseId: Int!
var opened: Bool!

init(courseName: String, courseNameLetter: String, numberOfChaptersAndAssignments: String, chapterCount: Int, courseId: Int ,opened: Bool) {

    self.courseName = courseName
    self.courseNameLetter = courseNameLetter
    self.numberOfChaptersAndAssignments  = numberOfChaptersAndAssignments
    self.chapterCount = chapterCount
    self.courseId = courseId
    self.opened = opened
  }
}

点击 header 后的数据模型:

struct CourseDataModel {

var chapterName: String!
var documentAndAssignmentCount: String!

init(chapterName: String, documentAndAssignmentCount: String!) {

    self.chapterName = chapterName
    self.documentAndAssignmentCount = documentAndAssignmentCount
  }
}

我的 viewController 和 TableView

的代码
import UIKit
import Alamofire

class CoursesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, ExpandableHeaderViewDelegate {

@IBOutlet weak var tableView: UITableView!
var sectionData = [CourseNamesModel]()
var tableData = [CourseDataModel]()

var selectedIdexPath: IndexPath!
override func viewDidLoad() {
    super.viewDidLoad()

    self.setFontFamilyAndSize()
    self.title = "Courses"
    selectedIdexPath = IndexPath(row: -1, section: -1)
    tableView.register(UINib(nibName: "ExpandableHeaderView", bundle: nil), forHeaderFooterViewReuseIdentifier: "expandableHeaderView")
   getCourseNames()
}

func numberOfSections(in tableView: UITableView) -> Int {
    return sectionData.count
}

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

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return tableView.frame.size.height/8.2
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

    if sectionData[indexPath.section].opened {
        return tableView.frame.size.height/8.48275862069
    } else {
        return 0
    }
}

func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {

    return 1
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "expandableHeaderView") as! ExpandableHeaderView
    headerView.customInit(courseName: sectionData[section].courseName, letterSign: sectionData[section].courseNameLetter, numberOfChaptersAndAssignments: sectionData[section].numberOfChaptersAndAssignments, section: section, delegate: self)
    headerView.tag = sectionData[section].courseId
    return headerView
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "dataCell") as! DataCell
    cell.chapterName.text = tableData[indexPath.row].chapterName
    cell.numberOfDocumentsAndAssignments.text = tableData[indexPath.row].documentAndAssignmentCount
    return cell
}

func getCourseNames() {

    sectionData = []

    let courseNamesURL = "\(WebAPI.baseURL2 + WebAPI.coursesAPI)"

    Alamofire.request(courseNamesURL, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON { response in

        switch response.result {
        case .success:

            let responseData = response.result.value as? [[String: Any]]
            guard let courseNamesData = responseData else {return}

            for courseDetail in courseNamesData {
                let courseName = courseDetail["CourseName"] as! String
                let courseNameLetter = String(courseName.first!)
                let chaptersCount = courseDetail["Chapterscount"] as! Int
                let assignmentsCount = courseDetail["AssignmentCount"] as! Int
                let chaptersAndAssignemntsCount = "\(chaptersCount) Chapters, \(assignmentsCount) Assignments"
                let courseId = courseDetail["CourseId"] as! Int
                self.sectionData.append(CourseNamesModel(courseName: courseName, courseNameLetter: courseNameLetter, numberOfChaptersAndAssignments: chaptersAndAssignemntsCount, chapterCount: chaptersCount, courseId: courseId, opened: false))
            }
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        case .failure(let error):
            print(error.localizedDescription)
        }
     }
   }
}

toggleSection(expand/Collapse) 委托函数的代码:

func toggleSection(header: ExpandableHeaderView, section: Int) {
    sectionData[section].opened = !sectionData[section].opened
    tableData = []
    let courseChaptersURL = "\(WebAPI.baseURL2 + WebAPI.courseChaptersAPI)\(header.tag)"
    Alamofire.request(courseChaptersURL, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON {response in

        switch response.result {
        case .success:

            let responseData = response.result.value as? [[String : Any]]
            guard let courseChaptersData = responseData else {return}

            for chapterDetail in courseChaptersData {
                let chapterName = chapterDetail["ChapterName"] as! String
                let documentsCount = chapterDetail["Documentscount"] as! Int
                let assignmentsCount = chapterDetail["AssignmentCount"] as! Int
                let documentsAndAssignmentsCount = "\(documentsCount) Documents, \(assignmentsCount) Assignments"
                //                        let isMaterialPathDelete = chapterDetail["IsDeleteMaterialPath"] as! Bool
                self.tableData.append(CourseDataModel(chapterName: chapterName, documentAndAssignmentCount: documentsAndAssignmentsCount))
            }
            print(self.tableData.count)
        case .failure(let error):
            print(error.localizedDescription)
        }
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
    tableView.beginUpdates()
    tableView.endUpdates()
    print("Selected Section Index is : \(section)")
}

这就是我所有的,过去 2 天我一直在尝试这个,我无法弄清楚。

您的 table 视图的数据源不一致。 每个部分都应该有自己的 var tableData = [CourseDataModel](),因此在 nubmerOfRowsInSection 中您应该有:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return sectionData[section].tableData.count
}

始终在主队列上更新您的数据源。在调用 reloadData 之前执行此操作,因此它应该如下所示:

DispatchQueue.main.async {
    self.sectionData.append...
    // or self.tableData.append...
    self.tableView.reloadData()
    // or reload section
}

这是所有部分都相同的行数。

试试下面的代码:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if sectionData[section].opened {
        return tableData.count
    }
    return 0
}

而且不需要:

tableView.beginUpdates()
tableView.endUpdates()

toggleSection 我猜。

编辑

尝试另一种方法:

fun toggleSection中:

for courseNamesModel in sectionData {
    courseNamesModel.opened = false
}
sectionData[section].opened = !sectionData[section].opened

因为您必须将 false 设置为之前打开的 headers。

您应该重新设计您的模型。 模特必须能反映出您 UI。由于部分包含 N 行,因此您的部分模型应具有 Row ModelArray。因此,您可以轻松地归档特定 Section 的行列表。在两个不同的 Array 中管理 Section & Row 是一件令人头疼的事情。

例如。

struct SectionModel {
    var opened: Bool!
    var yourTableRowModels = [RowModel]()
}

struct RowModel {
   var someAttribute: String!
}

现在在您的 TableViewDataSource 方法中使用以下方法。

class YourViewController: UIViewController {
    var sections = [SectionModel]()

    func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sections[section].yourTableRowModels.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let rowModel = sections[indexPath.section].yourTableRowModels[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "dataCell") as! DataCell
        cell.chapterName.text = rowModel.someAttribute
        cell.numberOfDocumentsAndAssignments.text = rowModel.someAttribute
        return cell
    }
}