点击 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 Model
的 Array
。因此,您可以轻松地归档特定 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
}
}
我在将数据动态加载到 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 Model
的 Array
。因此,您可以轻松地归档特定 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
}
}