Swift:根据服务器响应动态显示/隐藏UITableView Section
问题
开发一款包含多Section、多Header View的UITableView应用,需求是根据服务器响应动态随机显示或隐藏Section(默认有6个Section,服务器返回需显示2、4或6个),但当前实现未成功,代码如下:
var allPermissionSection = ["A","B","C","D","E","F"] // default permission override func numberOfSections(in tableView: UITableView) -> Int { return totalSectionTable // total count of showing cell } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let modelPermission = mainArrayCheckValidations[0] as! MyAcountModel if section == 0 { return 1 } // arraySectionName got from server check index if default section or server reponse index is equal to same then true this continue if allPermissionSection[0] == arraySectionName[0] { if arrayTblItems.count > 0 { return arrayTblItems.count }else if modelPermission.phoneNumbersEnabled == true { return 1 } } if allPermissionSection[1] == arraySectionName[1] { if arrayTblItemsForElectronic.count > 0 { return arrayTblItemsForElectronic.count }else if modelPermission.econsentEnabled == true { return 1 } } if allPermissionSection[2] == arraySectionName[2] { if arrayTblItemsForEmergnceContact.count > 0 { return arrayTblItemsForEmergnceContact.count }else if modelPermission.emergencyContactsEnabled == true { return 1 } } return 0 } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let modelPermission = mainArrayCheckValidations[0] as! MyAcountModel if indexPath.section == 0 { return UITableView.automaticDimension } if allPermissionSection[0] == arraySectionName[0] { // if indexPath.section == 1 { if arrayTblItems.count <= 0 { if modelPermission.phoneNumbersEnabled == true{ return 40 }else{ return 0 } } // } } if allPermissionSection[1] == arraySectionName[1] { // if indexPath.section == 2 { if arrayTblItemsForElectronic.count <= 0 { if modelPermission.econsentEnabled == true{ return 40 }else{ return 0 } } // } } if allPermissionSection[2] == arraySectionName[2] { // if indexPath.section == 3 { if arrayTblItemsForEmergnceContact.count <= 0 { if modelPermission.emergencyContactsEnabled == true{ return 40 }else{ return 0 } } // } } return UITableView.automaticDimension } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.section == 0 { let cell = tableView.dequeueReusableCell(withIdentifier: USER_DETAIL_CELL, for: indexPath) as! UserDetail_Cell cell.updateWith(mainArrayCheckValidations[indexPath.row], indexPath.row, mainArrayCheckValidations.count, tblCellType) return cell } if allPermissionSection[0] == arraySectionName[0] { // if indexPath.section == 1 { if arrayTblItems.count > 0 { let cell = tableView.dequeueReusableCell(withIdentifier: PHONE_CELL, for: indexPath) as! PhoneNumberDetailCell cell.updateWith(arrayTblItems[indexPath.row], indexPath.row, arrayTblItems.count, tblCellType) return cell }else{ let cell = tableView.dequeueReusableCell(withIdentifier: DEFAULT_CELL, for: indexPath) as! Default_Custom_Cell cell.lbl_Name.text = "" return cell } //} } if allPermissionSection[1] == arraySectionName[1] { // if indexPath.section == 2 { if arrayTblItemsForElectronic.count > 0 { let cell = tableView.dequeueReusableCell(withIdentifier: CONSTENT_CELL, for: indexPath) as! Constent_Cell cell.updateWith(arrayTblItemsForElectronic[indexPath.row], indexPath.row, arrayTblItemsForElectronic.count, tblCellType) return cell }else{ let cell = tableView.dequeueReusableCell(withIdentifier: DEFAULT_CELL, for: indexPath) as! Default_Custom_Cell cell.lbl_Name.text = "" return cell } // } } if allPermissionSection[2] == arraySectionName[2] { // if indexPath.section == 3 { if arrayTblItemsForEmergnceContact.count > 0 { let cell = tableView.dequeueReusableCell(withIdentifier: EMERGENCY_CONTACT_CELL, for: indexPath) as! EmergencyCnctCell cell.updateWith(arrayTblItemsForEmergnceContact[indexPath.row], indexPath.row, arrayTblItemsForEmergnceContact.count, tblCellType) return cell }else{ let cell = tableView.dequeueReusableCell(withIdentifier: DEFAULT_CELL, for: indexPath) as! Default_Custom_Cell cell.lbl_Name.text = "" return cell } // } } return UITableViewCell() } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if section == 0 { let headerView = setupHeaderView(textHeading: " Details") return headerView } if allPermissionSection[0] == arraySectionName[0] { //if section == 1 { let headerView = setupHeaderView(textHeading: "bers") return headerView // } } if allPermissionSection[1] == arraySectionName[1] { // if section == 2 { let headerView = setupHeaderView(textHeading: "sent") return headerView // } } if allPermissionSection[2] == arraySectionName[2] { // if section == 3 { let headerView = setupHeaderView(textHeading: "ontacts") return headerView // } } return UIView() } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if section == 0 { return 50 } if allPermissionSection[0] == arraySectionName[0] { // if section == 1 { return 50 // } } if allPermissionSection[1] == arraySectionName[1] { // if section == 2 { return 50 // } } if allPermissionSection[1] == arraySectionName[1] { // if section == 3 { return 50 // } } return 0 } func setupHeaderView(textHeading:String) -> UIView? { let headerView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: tblVw.frame.width, height: 50)) let headingLbl = UILabel() let topLbl = UILabel() let botomLbl = UILabel() topLbl.translatesAutoresizingMaskIntoConstraints = false botomLbl.translatesAutoresizingMaskIntoConstraints = false headerView.addSubview(botomLbl) headerView.addSubview(topLbl) botomLbl.leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true botomLbl.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true botomLbl.heightAnchor.constraint(equalToConstant:0.5).isActive = true botomLbl.bottomAnchor.constraint(equalTo: headerView.bottomAnchor).isActive = true topLbl.leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true topLbl.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true topLbl.heightAnchor.constraint(equalToConstant:0.5).isActive = true topLbl.topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true botomLbl.text = NULL_STRING topLbl.text = NULL_STRING topLbl.backgroundColor = .lightGray botomLbl.backgroundColor = .lightGray headingLbl.frame = CGRect.init(x: 0, y: 0, width: headerView.frame.width, height: headerView.frame.height) headingLbl.text = textHeading headingLbl.textColor = UIColor(hexString: "587358") headerView.backgroundColor = UIColor(displayP3Red: 245/256, green: 245/256, blue: 245/256, alpha: 0.6) headingLbl.textAlignment = .center headerView.addSubview(headingLbl) return headerView }
问题分析
当前代码存在以下核心问题:
- Section映射逻辑混乱:直接通过数组下标硬比对
allPermissionSection和arraySectionName,未建立「当前显示Section」与「原始权限Section」的对应关系,导致Section索引错位,服务器返回部分Section时,tableView的Section索引和数据数组无法正确匹配。 - 数据源管理缺失:未维护过滤后的有效Section列表,靠零散条件判断控制行数、高度,逻辑冗余且易出错。
- Header/Row逻辑不匹配:
viewForHeaderInSection、heightForHeaderInSection等方法未根据当前Section索引对应正确权限配置,导致Header显示错误或与Section内容脱节。 - 强制解包风险:
mainArrayCheckValidations[0] as! MyAcountModel未做空值判断,存在崩溃风险。
修正方案与实现思路
核心思路:维护一个过滤后的activeSections数组,只保留服务器允许显示的Section,所有tableView代理方法均基于该数组处理,彻底解决索引错位问题。
步骤1:定义Section模型(统一管理配置)
创建Section模型类,封装每个Section的标识、标题、数据、权限开关等信息,避免零散数组管理:
enum SectionIdentifier: String { case userDetails = "userDetails" case phoneNumbers = "phoneNumbers" case econsent = "econsent" case emergencyContacts = "emergencyContacts" // 补充剩余2个默认Section,如case D, E, F } struct TableSection { let identifier: SectionIdentifier let title: String var isEnabled: Bool // 由服务器响应控制 var items: [Any] // 对应Section的行数据 let permissionKey: KeyPath<MyAcountModel, Bool> // 权限判断的KeyPath let cellIdentifier: String // 对应Cell的复用ID }
步骤2:初始化与更新数据源
在ViewController中维护原始Section列表和过滤后的活跃Section列表:
// 原始默认6个Section的配置 private var allSections: [TableSection] = [ TableSection(identifier: .userDetails, title: "Details", isEnabled: true, items: [], permissionKey: \.userDetailsEnabled, cellIdentifier: USER_DETAIL_CELL), TableSection(identifier: .phoneNumbers, title: "Phone Numbers", isEnabled: false, items: [], permissionKey: \.phoneNumbersEnabled, cellIdentifier: PHONE_CELL), TableSection(identifier: .econsent, title: "E-Consent", isEnabled: false, items: [], permissionKey: \.econsentEnabled, cellIdentifier: CONSTENT_CELL), TableSection(identifier: .emergencyContacts, title: "Emergency Contacts", isEnabled: false, items: [], permissionKey: \.emergencyContactsEnabled, cellIdentifier: EMERGENCY_CONTACT_CELL), // 补充剩余2个默认Section ] // 过滤后的活跃Section(只显示isEnabled为true的) private var activeSections: [TableSection] = [] // 用户权限模型 private var accountModel: MyAcountModel?
服务器返回数据后,更新allSections的isEnabled状态,重新生成activeSections:
// 模拟服务器响应:返回需要显示的Section标识数组,如["phoneNumbers", "emergencyContacts"] func updateSectionsFromServer(enabledSectionIds: [String]) { // 更新每个Section的启用状态 allSections = allSections.map { section in var updatedSection = section updatedSection.isEnabled = enabledSectionIds.contains(section.identifier.rawValue) return updatedSection } // 过滤出活跃Section activeSections = allSections.filter { $0.isEnabled } // 刷新tableView tableView.reloadData() }
步骤3:修正TableView代理方法
所有代理方法基于activeSections处理,彻底避免索引错位:
override func numberOfSections(in tableView: UITableView) -> Int { return activeSections.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let currentSection = activeSections[section] guard let model = accountModel else { return 0 } // 有数据则返回数据行数,无数据则根据权限判断是否显示默认行 return !currentSection.items.isEmpty ? currentSection.items.count : (model[keyPath: currentSection.permissionKey] ? 1 : 0) } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let currentSection = activeSections[indexPath.section] guard let model = accountModel else { return 0 } if currentSection.items.isEmpty { return model[keyPath: currentSection.permissionKey] ? 40 : 0 } return UITableView.automaticDimension } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let currentSection = activeSections[indexPath.section] if !currentSection.items.isEmpty { let cell = tableView.dequeueReusableCell(withIdentifier: currentSection.cellIdentifier, for: indexPath) // 根据Section类型更新Cell数据 switch currentSection.identifier { case .userDetails: (cell as? UserDetail_Cell)?.updateWith(currentSection.items[indexPath.row], indexPath.row, currentSection.items.count, tblCellType) case .phoneNumbers: (cell as? PhoneNumberDetailCell)?.updateWith(currentSection.items[indexPath.row], indexPath.row, currentSection.items.count, tblCellType) case .econsent: (cell as? Constent_Cell)?.updateWith(currentSection.items[indexPath.row], indexPath.row, currentSection.items.count, tblCellType) case .emergencyContacts: (cell as? EmergencyCnctCell)?.updateWith(currentSection.items[indexPath.row], indexPath.row, currentSection.items.count, tblCellType) // 处理其他Section } return cell } else { // 无数据时返回默认Cell let cell = tableView.dequeueReusableCell(withIdentifier: DEFAULT_CELL, for: indexPath) as! Default_Custom_Cell cell.lbl_Name.text = "" return cell } } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let currentSection = activeSections[section] return setupHeaderView(textHeading: currentSection.title) } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 50 // 所有活跃Section的Header高度统一为50,如需特殊处理可在模型中添加height属性 } // 修正setupHeaderView方法,改用AutoLayout适配不同屏幕 private func setupHeaderView(textHeading: String) -> UIView { let headerView = UIView() headerView.backgroundColor = UIColor(displayP3Red: 245/256, green: 245/256, blue: 245/256, alpha: 0.6) let topLine = UILabel() topLine.backgroundColor = .lightGray topLine.translatesAutoresizingMaskIntoConstraints = false let bottomLine = UILabel() bottomLine.backgroundColor = .lightGray bottomLine.translatesAutoresizingMaskIntoConstraints = false let headingLbl = UILabel() headingLbl.text = textHeading headingLbl.textColor = UIColor(hexString: "587358") headingLbl.textAlignment = .center headingLbl.translatesAutoresizingMaskIntoConstraints = false headerView.addSubview(topLine) headerView.addSubview(b
相关产品推荐
相关产品推荐

