iOS中UITableView的下拉列表

43

在此输入图片描述

如何在iOS中创建这种类型的TableView?

当我们点击第一行“账户”时,它会自动滚动并显示图像中显示的更多行。如果再次点击“账户”,那么该视图将被隐藏。

12个回答

32

你可以轻松地设置一个单元格,使其看起来像是标题,并手动设置tableView:didSelectRowAtIndexPath以展开或折叠所在的部分。如果我存储与每个部分的“展开”值相对应的布尔数组。然后,您可以让自定义头行的每个tableView:didSelectRowAtIndexPath切换此值,然后重新加载该特定部分。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == 0) {
        ///it's the first row of any section so it would be your custom section header

        ///put in your code to toggle your boolean value here
        mybooleans[indexPath.section] = !mybooleans[indexPath.section];

        ///reload this section
        [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
    }
}

然后,您将设置您的数字numberOfRowsInSection来检查mybooleans值,并根据情况返回1(如果该部分未展开)或者1加上该部分中项目的数量(如果该部分已展开)。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    if (mybooleans[section]) {
        ///we want the number of people plus the header cell
        return [self numberOfPeopleInGroup:section] + 1;
    } else {
        ///we just want the header cell
        return 1;
    }
}
你还需要更新 cellForRowAtIndexPath 方法,以便为任何一个 section 中的第一行返回自定义头部单元格。 - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section 是提供自己的 "自定义标题" 的更好方法,因为它正是为此而设计的。
有关详细信息,请参考此回答或此PKCollapsingTableViewSections
此外,您可以使用 setIndentationLevel 来获得此类型的表视图。请参考此演示代码。我认为这是下拉表视图的最佳解决方案。
如果您想要创建一个简单的可折叠标题和单元格,请参考STCollapseTableView
希望这就是您正在寻找的内容。如有疑问,请随时联系我。 :)

1
这个教程http://www.appcoda.com/expandable-table-view提到了另一种使用属性列表文件的方法。该教程是用Swift编写的。完整项目在https://github.com/appcoda/expandable-table-view。 - user3354805
1
稍微变化一下,可以使用insertRowsAtIndexPaths:withRowAnimation:而不是reloadSections:,因为有时候重载整个部分的动画效果不太好看。 - chedabob

31

实现这个功能最简洁、自然的方式是使用表格视图单元格。没有展开单元格视图,也没有分区标题,只有简单的单元格(毕竟我们在一个表格视图中)。

设计如下:

  • 使用MVVM方法创建一个CollapsableViewModel类,其中包含配置单元格所需的信息:标签、图像。
  • 除了上述内容外,还有两个额外的字段:children,它是CollapsableViewModel对象数组,和isCollapsed,它保存下拉状态。
  • 视图控制器保存对CollapsableViewModel层次结构的引用,以及包含要呈现在屏幕上的视图模型的平面列表(displayedRows属性)。
  • 每当点击单元格时,检查它是否具有子项,并通过insertRowsAtIndexPaths()deleteRowsAtIndexPaths()函数在displayedRows和表格视图中添加或删除行。

以下是Swift代码(请注意,代码仅使用视图模型的label属性,以保持清晰):

import UIKit

class CollapsableViewModel {
    let label: String
    let image: UIImage?
    let children: [CollapsableViewModel]
    var isCollapsed: Bool
    
    init(label: String, image: UIImage? = nil, children: [CollapsableViewModel] = [], isCollapsed: Bool = true) {
        self.label = label
        self.image = image
        self.children = children
        self.isCollapsed = isCollapsed
    }
}

class CollapsableTableViewController: UITableViewController {
    let data = [
        CollapsableViewModel(label: "Account", image: nil, children: [
            CollapsableViewModel(label: "Profile"),
            CollapsableViewModel(label: "Activate account"),
            CollapsableViewModel(label: "Change password")]),
        CollapsableViewModel(label: "Group"),
        CollapsableViewModel(label: "Events", image: nil, children: [
            CollapsableViewModel(label: "Nearby"),
            CollapsableViewModel(label: "Global"),
            ]),
        CollapsableViewModel(label: "Deals"),
    ]
    
    var displayedRows: [CollapsableViewModel] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        displayedRows = data
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return displayedRows.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier") ?? UITableViewCell()
        let viewModel = displayedRows[indexPath.row]
        cell.textLabel!.text = viewModel.label
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: false)
        let viewModel = displayedRows[indexPath.row]
        if viewModel.children.count > 0 {
            let range = indexPath.row+1...indexPath.row+viewModel.children.count
            let indexPaths = range.map { IndexPath(row: $0, section: indexPath.section) }
            tableView.beginUpdates()
            if viewModel.isCollapsed {
                displayedRows.insert(contentsOf: viewModel.children, at: indexPath.row + 1)
                tableView.insertRows(at: indexPaths, with: .automatic)
            } else {
                displayedRows.removeSubrange(range)
                tableView.deleteRows(at: indexPaths, with: .automatic)
            }
            tableView.endUpdates()
        }
        viewModel.isCollapsed = !viewModel.isCollapsed
    }
}

Objective-C可以轻松翻译,我添加了Swift版本,因为它更短更易读。

通过几个小改动,可以使用该代码生成多级下拉列表。

编辑

有人问我关于分隔符的问题,这可以通过添加自定义类CollapsibleTableViewCell来实现,该类可配置视图模型(最终,将单元格配置逻辑从控制器移到其所属的位置 - 单元格)。部分单元格的分隔符逻辑的功劳要归功于回答 SO 问题的人们。

首先,更新模型,添加一个needsSeparator属性,用于告诉表视图单元格是否呈现分隔符:

class CollapsableViewModel {
    let label: String
    let image: UIImage?
    let children: [CollapsableViewModel]
    var isCollapsed: Bool
    var needsSeparator: Bool = true
    
    init(label: String, image: UIImage? = nil, children: [CollapsableViewModel] = [], isCollapsed: Bool = true) {
        self.label = label
        self.image = image
        self.children = children
        self.isCollapsed = isCollapsed
        
        for child in self.children {
            child.needsSeparator = false
        }
        self.children.last?.needsSeparator = true
    }
}

然后,添加cell类:

class CollapsibleTableViewCell: UITableViewCell {
    let separator = UIView(frame: .zero)
    
    func configure(withViewModel viewModel: CollapsableViewModel) {
        self.textLabel?.text = viewModel.label
        if(viewModel.needsSeparator) {
            separator.backgroundColor = .gray
            contentView.addSubview(separator)
        } else {
            separator.removeFromSuperview()
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        let separatorHeight = 1 / UIScreen.main.scale
        separator.frame = CGRect(x: separatorInset.left,
                                 y: contentView.bounds.height - separatorHeight,
                                 width: contentView.bounds.width-separatorInset.left-separatorInset.right,
                                 height: separatorHeight)
    }
}

cellForRowAtIndexPath需要修改为返回这种类型的单元格:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = (tableView.dequeueReusableCell(withIdentifier: "CollapsibleTableViewCell") as? CollapsibleTableViewCell) ?? CollapsibleTableViewCell(style: .default, reuseIdentifier: "CollapsibleTableViewCell")
        cell.configure(withViewModel: displayedRows[indexPath.row])
        return cell
    }

最后一步,删除默认的表格视图单元格分隔符 - 可以从xib或代码中进行操作 (tableView.separatorStyle = .none)。


@Cristik 我使用了你的代码,但是我需要进行一些修改。我想要在子菜单(Children)中将分隔符样式从单行改为无,而在父菜单中保持单行。你能帮忙吗?!! - Balaji Gupta
1
@BalajiGupta请查看此帖子以了解如何仅对某些单元格使用分隔符的详细信息https://dev59.com/H2oy5IYBdhLWcg3wa9ff。 - Cristik
@BalajiGupta,我编写了一些关于单元格分隔符的代码,请检查更新后的答案。 - Cristik
@Cristik 嗨,你的代码运行得非常好,我只是想知道如果我想让每次点击一个新单元格时关闭其他所有打开的单元格,该怎么做。例如,如果当我点击事件单元格时账户单元格是打开的,那么账户单元格就会关闭,事件单元格就会打开。对于这个逻辑方面的任何帮助或建议都将不胜感激 :) 谢谢 - Kashish Goel
@MarcusWayne 如果你将 tableView: didSelectRowAtIndexPath: 中的逻辑应用于两个视图模型:当前扩展的模型和被点击的模型,就可以实现你需要的效果。一个小修改是只使用一组 beginUpdates + endUpdates - Cristik

7

这里提供一个基于MVC的解决方案。

创建一个名为ClsMenuGroup的模型类来表示你的章节内容。

class ClsMenuGroup: NSObject {

    // We can also add Menu group's name and other details here.
    var isSelected:Bool = false
    var arrMenus:[ClsMenu]!
}

创建一个名为ClsMenu的模型类来表示你的行。
class ClsMenu: NSObject {

    var strMenuTitle:String!
    var strImageNameSuffix:String!
    var objSelector:Selector!   // This is the selector method which will be called when this menu is selected.
    var isSelected:Bool = false

    init(pstrTitle:String, pstrImageName:String, pactionMehod:Selector) {

        strMenuTitle = pstrTitle
        strImageNameSuffix = pstrImageName
        objSelector = pactionMehod
    }
}

在您的ViewController中创建组数组。
 class YourViewController: UIViewController, UITableViewDelegate {

    @IBOutlet var tblMenu: UITableView!
    var objTableDataSource:HDTableDataSource!
    var arrMenuGroups:[AnyObject]!

    // MARK: - View Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        if arrMenuGroups == nil {
            arrMenuGroups = Array()
        }

        let objMenuGroup = ClsMenuGroup()
        objMenuGroup.arrMenus = Array()

        var objMenu = ClsMenu(pstrTitle: "Manu1", pstrImageName: "Manu1.png", pactionMehod: "menuAction1")
        objMenuGroup.arrMenus.append(objMenu)

        objMenu = ClsMenu(pstrTitle: "Menu2", pstrImageName: "Menu2.png", pactionMehod: "menuAction2")
        objMenuGroup.arrMenus.append(objMenu)

        arrMenuGroups.append(objMenuGroup)
        configureTable()
    }


    func configureTable(){

        objTableDataSource = HDTableDataSource(items: nil, cellIdentifier: "SideMenuCell", configureCellBlock: { (cell, item, indexPath) -> Void in

            let objTmpGroup = self.arrMenuGroups[indexPath.section] as! ClsMenuGroup
            let objTmpMenu = objTmpGroup.arrMenus[indexPath.row]
            let objCell:YourCell = cell as! YourCell

            objCell.configureCell(objTmpMenu)  // This method sets the IBOutlets of cell in YourCell.m file.
        })

        objTableDataSource.sectionItemBlock = {(objSection:AnyObject!) -> [AnyObject]! in

            let objMenuGroup = objSection as! ClsMenuGroup
            return (objMenuGroup.isSelected == true) ? objMenuGroup.arrMenus : 0
        }

        objTableDataSource.arrSections = self.arrMenuGroups
        tblMenu.dataSource = objTableDataSource
        tblMenu.reloadData()
    }

    // MARK: - Tableview Delegate

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

        let objTmpGroup = self.arrMenuGroups[indexPath.section] as! ClsMenuGroup
        let objTmpMenu = objTmpGroup.arrMenus[indexPath.row]

        if objTmpMenu.objSelector != nil && self.respondsToSelector(objTmpMenu.objSelector) == true {
            self.performSelector(objTmpMenu.objSelector)  // Call the method for the selected menu.
        }

        tableView.reloadData()
    }

    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

        let arrViews:[AnyObject] = NSBundle.mainBundle().loadNibNamed("YourCustomSectionView", owner: self, options: nil)
        let objHeaderView = arrViews[0] as! UIView
        objHeaderView.sectionToggleBlock = {(objSection:AnyObject!) -> Void in

            let objMenuGroup = objSection as! ClsMenuGroup
            objMenuGroup.isSelected = !objMenuGroup.isSelected
            tableView.reloadData()
        }
        return objHeaderView
    }

    // MARK: - Menu methods

    func menuAction1(){

    }

    func menuAction2(){

    }
}

我已经使用HDTableDataSource代替了TableView的数据源方法。您可以在Github上找到HDTableDataSource的示例。

以上代码的优点是:

  1. 您可以随时更改任何菜单或部分的顺序或交换菜单和部分,而不必更改其他功能。
  2. 您将不需要在tableview的委托方法中添加长代码的if else梯子。
  3. 您可以单独指定菜单项的图标、标题或其他属性,例如添加徽章计数、更改所选菜单的颜色等。
  4. 您还可以通过对现有代码进行微小更改来使用多个单元格或部分。

2
为什么要使用匈牙利命名法? - Cristik
@Cristik,这是我们公司的标准编码规范。我们会添加前缀,如“str”、“int”等,以便识别数据类型,而不是每次都要使用“Cmd + 单击”,因此这被认为是长时间编码的良好实践。 - HarshIT
3
你应该查看这个链接:为什么不应该使用“匈牙利命名法”? - Cristik
@Cristik,谢谢,我读了Joel的博客。他说得很对。但是,如果你看他关于C++的例子,我们的匈牙利命名法是必要的。此外,页面上其他答案“为什么不应该使用‘匈牙利命名法’?”也正确地指出了使用匈牙利命名法的原因。我认为,我们应该通过添加变量的行为而不仅仅是数据类型(如可选绑定等)来改进编码约定。 - HarshIT
2
匈牙利命名法在其时是很好的,对于某些语言可能仍然适用。但Swift不是其中之一,在这里每个变量都携带着明确定义的类型,因此不需要在其名称前加前缀。而且Xcode使得查看该类型非常容易(检查检视器侧视图)。 - Cristik

5
通常我会通过设置行高来实现。例如,您有两个带有下拉列表的菜单项:
  • 菜单 1
    • 项目 1.1
    • 项目 1.2
    • 项目 1.3
  • 菜单 2
    • 项目 2.1
    • 项目 2.2
因此,您需要创建一个包含2个部分的表格视图。第一部分包含4行(菜单1及其项目),第二部分包含3行(菜单2及其项目)。
您始终只为每个部分的第一行设置高度。如果用户点击了第一行,则通过设置高度并重新加载该部分来展开该部分的所有行。

5

实现这个功能的简单方法是使用UITableView的section header作为cell,设置行数为0和section.count以实现折叠和展开状态。

  • 这是TableView的section header,isExpand->section.count,否则返回0。

    -普通cell

    -普通cell

    -普通cell

  • 这是TableView的section header,isExpand->section.count,否则返回0。

    -普通cell

    -普通cell

    -普通cell


1
您能否像在单元格上一样检测到对节标题的轻拍(使用-tableView:didSelectRowAtIndexPath:)? - Nicolas Miari
能否分享一个链接或者方法名称?在苹果的文档中找不到。 - Nicolas Miari
抱歉,我很久以前做过这个,所以我忘记了,你可以使用按钮或类似于这个的轻拍手势:https://dev59.com/omsz5IYBdhLWcg3wpZny - Trung Phan

5
iOS框架中没有类似树形视图的内置控件 - UIKit。正如其他用户所指出的,可能最简单的解决方案(不使用任何外部库)是向UITableView的委托和数据源添加一些自定义逻辑,以模拟所需的行为。
幸运的是,有一些开源库可以让您实现所需的树形视图,而无需担心展开/折叠操作的细节。在iOS平台上有一些可用的库。在大多数情况下,这些库只是包装了UITableView,并为您提供了编程友好的接口,使您可以专注于问题而不是树形视图的实现细节。
就我个人而言,我是RATreeView库的作者,其目的是将创建iOS上的树形视图的成本最小化。您可以查看示例项目(在Objective-cSwift中可用)以查看此控件的工作和行为方式。使用我的控件,创建您想要的视图非常简单:
  1. DataObject结构将用于保存有关树形视图节点的信息 - 它将负责保留有关单元格标题、其图像(如果单元格具有图像)和其子项(如果单元格具有子项)的信息。
class DataObject
{
    let name : String
    let imageURL : NSURL?
    private(set) var children : [DataObject]

    init(name : String, imageURL : NSURL?, children: [DataObject]) {
        self.name = name
        self.imageURL = imageURL
        self.children = children
    }

    convenience init(name : String) {
        self.init(name: name, imageURL: nil, children: [DataObject]())
    }
}
  1. 我们将声明协议TreeTableViewCell并实现两个符合该协议的单元格。其中一个单元格将用于显示根项,另一个单元格将用于显示根项的子项。
protocol TreeTableViewCell {
    func setup(withTitle title: String, imageURL: NSURL?, isExpanded: Bool)
}

class ChildTreeTableViewCell : UITableViewCell, TreeTableViewCell {
    func setup(withTitle title: String, imageURL: NSURL?, isExpanded: Bool) {
       //implementation goes here 
    }
}

class RootTreeTableViewCell : UITableViewCell, TreeTableViewCell {
    func setup(withTitle title: String, imageURL: NSURL?, isExpanded: Bool) {
       //implementation goes here
    }
}

在我们的视图控制器(MVC)或视图模型(MVVM)中,我们定义负责备份我们树形视图的数据结构。
let profileDataObject = DataObject(name: "Profile")
let privateAccountDataObject = DataObject(name: "Private Account")
let changePasswordDataObject = DataObject(name: "Change Password")
let accountDataObject = DataObject(name: "Account", imageURL: NSURL(string: "AccountImage"), children: [profileDataObject, privateAccountDataObject, changePasswordDataObject])

let groupDataObject = DataObject(name: "Group", imageURL: NSURL(string: "GroupImage"), children: [])
let eventDataObject = DataObject(name: "Event", imageURL: NSURL(string: "EventImage"), children: [])
let dealsDataObject = DataObject(name: "Deals", imageURL: NSURL(string: "DealsImage"), children: [])

data = [accountDataObject, groupDataObject, eventDataObject, dealsDataObject]

接下来我们需要实现数据源中的几个方法。
func treeView(treeView: RATreeView, numberOfChildrenOfItem item: AnyObject?) -> Int {
    if let item = item as? DataObject {
        return item.children.count //return number of children of specified item
    } else {
        return self.data.count //return number of top level items here
    }
}

func treeView(treeView: RATreeView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
    if let item = item as? DataObject {
        return item.children[index] //we return child of specified item here (using provided `index` variable)
    } else {
        return data[index] as AnyObject //we return root item here (using provided `index` variable)
    }
}

func treeView(treeView: RATreeView, cellForItem item: AnyObject?) -> UITableViewCell {
    let cellIdentifier = item ?TreeTableViewChildCell” : “TreeTableViewCellRootCelllet cell = treeView.dequeueReusableCellWithIdentifier(cellIdentifier) as! TreeTableViewCell

    //TreeTableViewCell is a protocol which is implemented by two kinds of
    //cells - the one responsible for root items in the tree view and another 
    //one responsible for children. As we use protocol we don't care
    //which one is truly being used here. Both of them can be
    //configured using data from `DataItem` object.

    let item = item as! DataObject
    let isExpanded = treeView.isCellForItemExpanded(item) //this variable can be used to adjust look of the cell by determining whether cell is expanded or not

    cell.setup(withTitle: item.name, imageURL: item.imageURL, expanded: isExpanded)

    return cell
}

请注意,使用我的库时,您无需关心单元格的展开和折叠 - 这由RATreeView控件处理。 您只需要负责配置单元格所使用的数据 - 其余部分由控件本身处理。


Augustyniak,我有类似的需求,并且我已经使用RATreeView来实现。但是,我想让父行在用户开始滚动时像普通UITableView标题一样固定在顶部。你有什么想法吗? - iAmd
目前的RATreeView实现方式不支持此操作。 - Rafał Augustyniak

4
@interface TestTableViewController ()
{
    BOOL showMenu;
}

@implementation TestTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"accountMenu"];
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"accountSubMenu"];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 2;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section == 0) {
        // Account Menu
        return 1;
    }
    if (showMenu) {
        // Profile/Private Account/Change Password
        return 3;
    }
    // Hidden Account Menu
    return 0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell;

    if (indexPath.section == 0) {
        cell = [tableView dequeueReusableCellWithIdentifier:@"accountMenu" forIndexPath:indexPath];
        cell.textLabel.text = @"Account";
    }
    else
    {
        cell = [tableView dequeueReusableCellWithIdentifier:@"accountSubMenu" forIndexPath:indexPath];
        switch (indexPath.row) {
            case 0:
                cell.textLabel.text = @"Profile";
                break;
            case 1:
                cell.textLabel.text = @"Private Account";
                break;
            case 2:
                cell.textLabel.text = @"Change Password";
                break;

            default:
                break;
        }
    }


    return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 0) {
        // Click on Account Menu
        showMenu = !showMenu;
        [tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
    }
}

希望这有所帮助 :)

如何在Objective-C中对可展开菜单项执行操作@Vũ Ngọc Giang - Sagar Rathode

3

如果您不想使用任何外部库,那么可以制作2个自定义单元格。一个显示在展开之前,另一个显示在展开后(具有不同的标识符)。当您点击单元格时,请检查单元格是否已展开。如果没有展开,则使用展开的单元格标识符,否则使用未展开的单元格标识符。

这是制作扩展表视图单元格的最佳和清洁的方法。


3
您需要一个可折叠的TableView。为了实现这一点,在您的TableView中,您必须跟踪哪些部分被折叠(收缩),哪些部分被展开。为此,您需要维护一个已展开部分索引的集合,或者一个布尔数组,其中每个索引的值表示相应的部分是否已展开。在为特定行分配高度时,请检查特定索引处的值。请查看此链接以获取更多帮助。
您可以在此处了解有关分组TableView的信息。
Github上有第三方库可供使用,可以节省您的时间。请查看CollapsableTableViewCollapsableTable-Swift

3
您可以将“账户”作为一个单元格,点击后展开以显示三个按钮(“个人资料”,“激活帐户”,“更改密码”),但这会产生一个问题:在每个按钮周围轻触将被视为“用户选择了账户单元格”,并触发 -tableView:didSelectRowAtIndexPath: 导致单元格的展开/折叠。
或者您可以将每个隐藏选项(“个人资料”,“激活帐户”,“更改密码”)作为单独的表视图单元格。但我不知道如何将三个单元格作为一个整体进行动画展开和收缩(而不是分别从零高度展开到完全展开)。
因此,最好的解决方案可能是:
1. 让偶数单元格(索引:0、2、4...)同时扮演“菜单标题”和“切换菜单打开/关闭”的角色(朝向下面描述的相关奇数单元格)。 2. 将(最初折叠的)“菜单主体”单元格交错,每个单元格都有一个垂直布局的选项按钮(例如“个人资料”,“激活帐户”,“更改密码”)。使用目标-动作来响应用户选择每个选项/按钮。 3. 实现表视图委托方法,使只有偶数单元格(菜单标题)可选择,并实现选择逻辑以展开/折叠相应的奇数单元格(在 -tableView:didSelectRowAtIndexPath: 中)。例如,选择索引为0的单元格(“账户”)会导致展开/折叠索引为1的单元格(带有“个人资料”,“激活帐户”,“更改密码”选项的菜单)。
这并不是 UITableView 的最优雅的使用方式,但可以完成工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接