You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:


Approach 1: Section-Based Expandable TableView

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)
}

Approach 2: Row-Based Expandable TableView

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:18:11