Swift中无需第三方库实现可展开UITableView的方案咨询
Hey there! Let's break down how to convert your existing UITableView into an expandable one. I'll cover two practical approaches so you can pick what fits your menu structure best:
This works great if you want to group your menu items into collapsible sections.
Step 1: Update Your Data Structure
First, replace your flat arrays with a structured model that tracks each section's name, sub-items, and expansion state:
struct MenuSection { let sectionName: String let subMenuItems: [String] var isExpanded: Bool // Tracks if the section is open/closed } // Initialize your grouped menu data (replace with your actual content) var menuSections: [MenuSection] = [ MenuSection(sectionName: "Main Menu", subMenuItems: ["Profile", "Settings"], isExpanded: false), MenuSection(sectionName: "Actions", subMenuItems: ["Share", "Delete"], isExpanded: false) ]
Step 2: Update TableView DataSource & Delegate Methods
Modify your existing methods to work with sections, and add a custom header for toggling expansion:
// MARK: - TableView Delegate and Datasource func numberOfSections(in tableView: UITableView) -> Int { return menuSections.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // Show sub-items only if the section is expanded return menuSections[section].isExpanded ? menuSections[section].subMenuItems.count : 0 } // Custom header to display section name and handle taps func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 50)) headerView.backgroundColor = .systemGray5 let titleLabel = UILabel(frame: CGRect(x: 16, y: 0, width: tableView.frame.width - 40, height: 50)) titleLabel.text = menuSections[section].sectionName titleLabel.font = .systemFont(ofSize: 16, weight: .medium) headerView.addSubview(titleLabel) // Add tap gesture to toggle expansion let tapGesture = UITapGestureRecognizer(target: self, action: #selector(toggleSection(_:))) headerView.addGestureRecognizer(tapGesture) headerView.tag = section // Tag to identify which section was tapped return headerView } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 50 // Adjust header height as needed } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "MenuTableViewCell", for: indexPath) as! MenuTableViewCell // Load the sub-item for the current section/row cell.menuNameLabel.text = menuSections[indexPath.section].subMenuItems[indexPath.row] return cell }
Step 3: Add the Expansion Toggle Logic
Implement the method to flip the section's expanded state and refresh the table:
@objc private func toggleSection(_ gesture: UITapGestureRecognizer) { guard let section = gesture.view?.tag else { return } // Toggle the expanded state menuSections[section].isExpanded.toggle() // Animate the section refresh tableView.reloadSections(IndexSet(integer: section), with: .automatic) }
Use this if you want individual parent rows to reveal sub-items when tapped (no sections).
Step 1: Update Your Data Structure
Create a model for parent items that includes sub-items and an expansion state:
struct MenuItem { let name: String let subItems: [String] var isExpanded: Bool // Tracks if the parent row is open/closed } // Initialize your menu data (replace with your actual content) var menuItems: [MenuItem] = [ MenuItem(name: "Profile", subItems: ["Edit Profile", "Change Password"], isExpanded: false), MenuItem(name: "Settings", subItems: ["Notifications", "Privacy"], isExpanded: false) ]
Step 2: Add Helper Methods
We need helpers to calculate total rows and map table index paths to our menu model:
// Calculate total rows (parent rows + expanded sub-items) private func totalMenuRows() -> Int { var count = 0 for item in menuItems { count += 1 // Count the parent row if item.isExpanded { count += item.subItems.count // Add sub-items if expanded } } return count } // Map table index path to the corresponding menu item/child private func getItemForIndexPath(_ indexPath: IndexPath) -> (parent: MenuItem, isParentRow: Bool, childIndex: Int?) { var currentRow = 0 for (parentIndex, item) in menuItems.enumerated() { // Check if current row is the parent if currentRow == indexPath.row { return (item, true, nil) } currentRow += 1 // Check sub-items if parent is expanded if item.isExpanded { for childIndex in 0..<item.subItems.count { if currentRow == indexPath.row { return (item, false, childIndex) } currentRow += 1 } } } fatalError("IndexPath out of bounds for menu data") }
Step 3: Update TableView DataSource & Delegate
Modify your existing methods to handle the dynamic row count and styling:
// MARK: - TableView Delegate and Datasource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return totalMenuRows() } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "MenuTableViewCell", for: indexPath) as! MenuTableViewCell let (item, isParent, childIndex) = getItemForIndexPath(indexPath) if isParent { cell.menuNameLabel.text = item.name // Add a disclosure indicator to show it's expandable cell.accessoryType = item.isExpanded ? .disclosureIndicator : .none cell.indentationLevel = 0 } else { cell.menuNameLabel.text = item.subItems[childIndex!] cell.accessoryType = .none // Indent sub-items for visual hierarchy cell.indentationLevel = 1 cell.indentationWidth = 24 } return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let (item, isParent, _) = getItemForIndexPath(indexPath) guard isParent else { return } // Only parent rows trigger expansion // Find the parent item's index and toggle its state if let parentIndex = menuItems.firstIndex(where: { $0.name == item.name }) { menuItems[parentIndex].isExpanded.toggle() tableView.reloadData() // Refresh the table to show/hide sub-items } tableView.deselectRow(at: indexPath, animated: true) }
Both approaches are lightweight and play nicely with your existing MenuTableViewCell. Pick the one that aligns with your menu design!
内容的提问来源于stack exchange,提问作者B.Saravana Kumar

