43

ここに画像の説明を入力

iOSでこのタイプのテーブルビューを作成するには??

ここで、1行目の「アカウント」をタップすると、画像に表示されているいくつかの行が自動的にスクロールされます。もう一度アカウントをタップすると、そのビューは非表示になります。

4

12 に答える 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この例については、このDemoCodeを参照してください。これがドロップダウン テーブルビューの最適なソリューションだと思います。

単純なヘッダーとセルのドロップダウンを作成する場合は、 STCollapseTableViewを参照してください。

希望、これはあなたが探しているものです。ご不明な点がございましたら、ご連絡ください。:)

于 2016-01-04T07:07:16.893 に答える
31

これを実装する最も簡単で自然な方法は、テーブル ビュー セルを使用する場合です。拡張セル ビューも、セクション ヘッダーもありません。プレーンで単純なセルです (結局、テーブル ビューにいます)。

設計は次のとおりです。

  • MVVM アプローチを使用してCollapsableViewModel、セルの構成に必要な情報を保持するクラスを作成します: ラベル、イメージ
  • 上記のフィールドに加えて、オブジェクトchildrenの配列であると、ドロップダウンの状態を保持する の2 つの追加フィールドがあります。CollapsableViewModelisCollapsed
  • ビュー コントローラは、 の階層への参照と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
    }
}

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) から、デフォルトのテーブル ビューのセル セパレータを削除します。

于 2016-01-05T20:28:29.673 に答える
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 に 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(){

    }
}

Tableview のデータ ソース メソッドの代わりに HDTableDataSource を使用しました。Github からHDTableDataSourceの例を見つけることができます。

上記のコードの利点は

  1. 他の機能を変更することなく、メニューまたはセクションの順序をいつでも変更したり、メニューとセクションを交換したりできます。
  2. テーブルビューのデリゲートメソッドにelse ifの長いコードを追加する必要はありません
  3. バッジ数の追加、選択したメニューの色の変更など、メニュー項目のアイコン、タイトル、またはその他の属性を個別に指定できます。
  4. 既存のコードに小さな変更を加えることで、複数のセルまたはセクションを使用することもできます
于 2016-01-06T13:49:46.240 に答える
5

iOS フレームワークのビューのようなツリー ビューの組み込みコントロールはありません - UIKit。他のユーザーが指摘したように、おそらく最も簡単な解決策 (外部ライブラリを使用しない) は、UITableViewのデリゲートとデータ ソースにカスタム ロジックを追加して、目的の動作を模倣することです。

幸いなことに、展開/折りたたみ操作の詳細を気にせずに、目的のツリー ビューのようなビューを実装できるオープン ソース ライブラリがいくつかあります。iOS プラットフォームで利用できるものがいくつかあります。ほとんどの場合、これらのライブラリは単にラップUITableViewされ、ツリー ビューの実装の詳細ではなく、問題に集中できるプログラマー フレンドリなインターフェイスを提供します。

個人的には、私はRATreeViewライブラリの作成者であり、iOS でビューのようなツリー ビューを作成するために必要なコストを最小限に抑えることを目的としています。サンプル プロジェクト ( Objective-cおよびSwiftで利用可能) をチェックアウトして、このコントロールがどのように機能し、動作するかを確認できます。私のコントロールを使用すると、必要なビューを作成するのは非常に簡単です。

  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し、そのプロトコルに準拠する 2 つのセルを実装します。これらのセルの 1 つはルート アイテムを表示するために使用され、別のセルはルート アイテムの子を表示するために使用されます。
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
    }
}
  1. ビュー コントローラー (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]
  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
}

私のライブラリを使用すると、セルの展開と折りたたみを気にする必要がないことに注意してくださいRATreeView。セルの構成に使用されるデータのみを担当し、残りはコントロール自体によって処理されます。

于 2016-01-09T12:45:00.420 に答える
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];
    }
}

それが役立つことを願っています:)

于 2016-01-08T04:45:07.557 に答える
3

折りたたみ可能な TableView が必要です。これを実現するには、TableView で、どのセクションが折りたたまれている (縮小されている) か、どのセクションが展開されているかを追跡する必要があります。このためには、展開されたセクションの一連のインデックス、または各インデックスの値が対応するセクションが展開されているかどうかを示すブール配列を維持する必要があります。特定の行に高さを割り当てながら、特定のインデックスの値を確認します。詳細については、このリンクを確認してください。

Sectional TableView については、こちらで学習できます。

Github にはサードパーティのライブラリがあり、煩わしさから解放されます。CollapsableTableViewまたはCollapsableTable-Swiftを ご覧ください

于 2016-01-04T06:41:12.500 に答える