iOS中UITableView中的下拉列表

Mee*_*shi 40 objective-c uitableview ios swift dropdown

在此输入图像描述

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

在这里,如果我们点击第一行"帐户",然后自动滚动显示在图像中的更多行.如果我们再次点击"帐户",那么该视图将被隐藏.

Mee*_*shi 31

您可以轻松地将单元格设置为像标题一样LOOK,并设置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];
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您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;
    }
}
Run Code Online (Sandbox Code Playgroud)

您还必须更新cellForRowAtIndexPath以返回任何第一行的自定义标题单元格section.

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section 是提供"自定义标题"的更好方法,因为它正是它的设计目标.

有关更多详细信息,请参阅此答案或此PKCollapsingTableViewSections.

此外,您可以使用此类表格视图setIndentationLevel.有关此示例,请参阅此DemoCode.我认为这是Drop-Down tableviews的最佳解决方案.

如果你想制作一个简单的标题和单元格下拉,那么请参考STCollapseTableView.

希望,这是你正在寻找的.任何关注都会回复给我.:)


Cri*_*tik 26

如果通过表格视图单元格,则可以更轻松,最自然地实现此方法.没有扩展的单元格视图,没有节标题,简单和简单的单元格(毕竟我们在表格视图中).

设计如下:

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,添加单元格类:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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
    }
Run Code Online (Sandbox Code Playgroud)

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


Har*_*hIT 7

这是一个基于MVC的解决方案.

为您的Sections创建一个Model类ClsMenuGroup

class ClsMenuGroup: NSObject {

    // We can also add Menu group's name and other details here.
    var isSelected:Bool = false
    var arrMenus:[ClsMenu]!
}
Run Code Online (Sandbox Code Playgroud)

为您的行创建一个Model类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
    }
}
Run Code Online (Sandbox Code Playgroud)

在ViewController中创建groups数组

 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(){

    }
}
Run Code Online (Sandbox Code Playgroud)

我使用HDTableDataSource代替Tableview的数据源方法.您可以从Github 找到HDTableDataSource的示例.

上面代码的优点是

  1. 您可以随时更改任何菜单或部分或交换菜单和部分的顺序,而无需更改其他功能.
  2. 如果在tableview的委托方法中使用梯形图,则无需添加其他长代码
  3. 您可以单独为菜单项指定图标,标题或其他属性,例如添加徽章计数,更改所选菜单的颜色等.
  4. 您还可以通过对现有代码应用细微更改来使用多个单元格或节

  • 您应该检查一下:[为什么我不应该使用"匈牙利表示法"?](http://stackoverflow.com/questions/111933/why-shouldnt-i-use-hungarian-notation) (3认同)
  • 为什么是匈牙利符号? (2认同)
  • 匈牙利语表示法在当时很不错,而且可能对某些语言仍然有用。Swift不是其中之一,这里每个变量都带有定义明确的类型,从而无需在其名称前添加前缀。使用xcode可以很容易地分辨出该类型是什么(检查检查器侧视图)。 (2认同)

Ale*_*nev 5

通常我会通过设置行高来实现.例如,您有两个带有下拉列表的菜单项:

  • 菜单1
    • 项目1.1
    • 项目1.2
    • 项目1.3
  • 菜单2
    • 项目2.1
    • 项目2.2

因此,您必须创建一个包含2个部分的表视图.第一部分包含4行(菜单1及其项目),秒部分包含3行(菜单2及其项目).

您始终只为第一行设置高度.如果用户单击第一行,则通过设置高度展开此部分行并重新加载此部分.


Tru*_*han 5

这样做的简单方法是使用UITableView节头作为cell->,并将行数设置为0,将section.count用于折叠和展开状态.

  • .这是TableViewSection Header,isExpand - > section.count else返回0.

    - 正常细胞

    - 正常细胞

    - 正常细胞

  • .这是TableViewSection Header,isExpand - > section.count else返回0.

    - 正常细胞

    - 正常细胞

    - 正常细胞


Raf*_*iak 5

在iOS框架中没有用于树视图的内置控件 - UIKit.正如其他用户所指出的那样,最简单的解决方案(不使用任何外部库)可能是为UITableView代理和数据源添加一些自定义逻辑来模仿所需的行为.

幸运的是,有一些开源库允许您实现所需的树视图,而不必担心展开/折叠操作的细节.其中有几个适用于iOS平台.在大多数情况下,这些库只是包装UITableView并为您提供程序员友好的界面,使您可以专注于您的问题而不是树视图的实现细节.

就个人而言,我是RATreeView库的作者,它的目的是最小化在iOS上创建树视图所需的成本.您可以查看示例项目(在Objective-cSwift中可用)以检查此控件的工作方式和行为.使用我的控件,创建所需的视图非常简单:

  1. DataObject struct将用于保存有关树视图节点的信息 - 它将负责保存有关单元格标题,图像(如果单元格有图像)及其子项(如果单元格有子项)的信息.
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]())
    }
}
Run Code Online (Sandbox Code Playgroud)
  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
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 在out视图控制器(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]
Run Code Online (Sandbox Code Playgroud)
  1. 接下来,我们需要从数据源中实现几个方法RATreeView.
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” : “TreeTableViewCellRootCell”
    let 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
}
Run Code Online (Sandbox Code Playgroud)

请注意,使用我的库,您不必关心单元格的扩展和折叠 - 它由RATreeView.您只负责用于配置单元格的数据 - 其余部分由控件本身处理.


Nic*_*ari 3

您可以将帐户作为一个单元格,点击后展开以显示三个按钮(“个人资料”、“激活帐户”、“更改密码”),但这会产生一个问题:点击这三个按钮中的每一个都将被视为“用户选择”帐户单元格”并触发-tableView:didSelectRowAtIndexPath:单元格的展开/折叠。

或者,您可以将每个隐藏选项(“个人资料”、“激活帐户”、“更改密码”)设置为单独的表格视图单元格。但我不知道如何将三个单元格作为一个整体进行扩展和收缩(而不是每个单元格分别从零高度扩展到完全扩展)。

因此,也许最好的解决方案是:

  1. 偶数单元格(索引:0、2、4...)同时履行“菜单标题”和“切换菜单打开/关闭”的角色(朝向下面描述的相关奇数单元格)。
  2. 交错排列(最初折叠的)“菜单主体”单元格,每个单元格每个选项都有一个按钮(例如“个人资料”、“激活帐户”、“更改密码”),垂直排列在奇数索引(1、3、5. ..)。使用 target-action 来响应用户选择每个选项/按钮。
  3. 实现表视图委托方法,以便仅选择偶数单元格(菜单标题),并实现选择逻辑来展开/折叠相应的奇数单元格(在 -tableView:didSelectRowAtIndexPath: 内部)。例如,选择索引 0 处的单元格(“帐户”)会导致展开/折叠索引 1 处的单元格(带有选项“个人资料”、“激活帐户”、“更改密码”的菜单)。

这不是 UITableView 最优雅的用法,但可以完成工作。