web-dev-qa-db-fra.com

Liste déroulante dans UITableView dans iOS

enter image description here

Comment créer ce type de table dans iOS ??

Ici, si nous tapons sur la 1ère ligne "Compte", puis automatiquement, il défile avec quelques lignes supplémentaires qui s'affichent dans l'image. Et si nous tapons à nouveau sur Compte, cette vue sera masquée.

40
Meet Doshi

Vous pouvez facilement configurer une cellule pour qu'elle ressemble à un en-tête et configurer le tableView: didSelectRowAtIndexPath Pour développer ou réduire manuellement la section dans laquelle elle se trouve. Si je stockais un tableau de booléens correspondant à la valeur "dépensée" de chacune de vos sections. Vous pouvez alors avoir le tableView:didSelectRowAtIndexPath Sur chacune de vos lignes d'en-tête personnalisées pour basculer cette valeur puis recharger cette section spécifique.

- (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];
    }
}

Vous devez ensuite configurer votre nombre numberOfRowsInSection pour vérifier la valeur mybooleans et renvoyer soit 1 si la section n'est pas développée, soit 1+ le nombre d'éléments dans la section, si elle est développée .

- (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;
    }
}

Vous devrez également mettre à jour votre cellForRowAtIndexPath pour renvoyer une cellule d'en-tête personnalisée pour la première ligne de tout section.

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section est le meilleur moyen de fournir votre "propre en-tête personnalisé", car c'est exactement ce pour quoi il est conçu.

Pour plus de détails, reportez-vous à cette Réponse ou à cette PKCollapsingTableViewSections .

En outre, vous pouvez obtenir ce type de vues de table en utilisant setIndentationLevel. Veuillez vous référer à DemoCode pour cet exemple. Je pense que c'est la meilleure solution pour les vues de tables déroulantes.

Si vous souhaitez créer un simple en-tête et une liste déroulante de cellules, veuillez vous reporter à STCollapseTableView .

J'espère que c'est ce que vous recherchez. Toute inquiétude me revient. :)

31
Meet Doshi

Le moyen le plus simple et le plus naturel de l'implémenter via des cellules de vue tableau. Pas de vues de cellule en expansion, pas d'en-têtes de section, de cellules simples et simples (nous sommes après tout dans une vue de table).

La conception est la suivante:

  • en utilisant une approche MVVM, créez une classe CollapsableViewModel qui contient les informations nécessaires pour configurer la cellule: label, image
  • en plus de celui ci-dessus, il y a deux champs supplémentaires: children, qui est un tableau d'objets CollapsableViewModel, et isCollapsed, qui contient l'état de la liste déroulante
  • le contrôleur de vue contient une référence à la hiérarchie de CollapsableViewModel, ainsi qu'une liste plate contenant les modèles de vue qui seront rendus à l'écran (la propriété displayedRows)
  • chaque fois qu'une cellule est tapée, vérifiez si elle a des enfants et ajoutez ou supprimez des lignes dans displayedRows et dans la vue tableau, via les fonctions insertRowsAtIndexPaths() et deleteRowsAtIndexPaths().

Le code Swift est le suivant (notez que le code utilise uniquement la propriété label du modèle de vue, pour le garder propre):

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

La contrepartie d'Objective-C est facile à traduire, j'ai ajouté la version Swift uniquement car elle est plus courte et plus lisible.

Avec quelques petites modifications, le code peut être utilisé pour générer des listes déroulantes de plusieurs niveaux.

Modifier

Les gens m'ont posé des questions sur les séparateurs, cela peut être réalisé en ajoutant une classe personnalisée CollapsibleTableViewCell qui est configurée avec un modèle de vue (enfin, déplacez la logique de configuration de la cellule du contrôleur vers son emplacement - la cellule). Les crédits pour la logique de séparation uniquement pour certaines cellules vont aux personnes répondant this SO question.

Tout d'abord, mettez à jour le modèle, ajoutez une propriété needsSeparator qui indique à la cellule de vue de table de rendre ou non le séparateur:

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

Ensuite, ajoutez la classe de cellules:

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 devrait alors être modifié pour renvoyer ce type de cellules:

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
    }

Une dernière étape, supprimez les séparateurs de cellules de vue de tableau par défaut - soit de xib soit du code (tableView.separatorStyle = .none).

25
Cristik

Voici une solution basée sur [~ # ~] mvc [~ # ~] .

Créer une classe de modèle ClsMenuGroup pour vos sections

class ClsMenuGroup: NSObject {

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

Créer une classe de modèle ClsMenu pour vos lignes

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

Créer un tableau de groupes dans votre 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(){

    }
}

J'ai utilisé HDTableDataSource à la place des méthodes de source de données de Tableview. Vous pouvez trouver un exemple de HDTableDataSource de Github.

Les avantages du code ci-dessus sont

  1. Vous pouvez à tout moment modifier l'ordre de n'importe quel menu ou section ou changer de menu et de section, sans changer d'autres fonctions.
  2. Vous n'aurez pas besoin d'ajouter un long code else if ladder dans les méthodes de délégué de votre tableview
  3. Vous pouvez spécifier séparément l'icône, le titre ou un autre attribut pour votre élément de menu, comme l'ajout du nombre de badges, la modification de la couleur du menu sélectionné, etc.
  4. Vous pouvez également utiliser plusieurs cellules ou sections en appliquant des modifications mineures au code existant
7
HarshIT

le moyen le plus simple est d'utiliser l'en-tête de section UITableView comme cellule-> et le nombre de lignes défini est 0 et section.count pour réduire et développer l'état.

  • Il s'agit de l'en-tête TableViewSection, isExpand -> section.count sinon retourne 0.

    -Cellule normale

    -Cellule normale

    -Cellule normale

  • Il s'agit de l'en-tête TableViewSection, isExpand -> section.count sinon retourne 0.

    -Cellule normale

    -Cellule normale

    -Cellule normale

5
Trung Phan

Habituellement, je le fais en définissant la hauteur de la ligne. Par exemple, vous disposez de deux éléments de menu avec des listes déroulantes:

  • Menu 1
    • Point 1.1
    • Point 1.2
    • Point 1.3
  • Menu 2
    • Point 2.1
    • Point 2.2

Vous devez donc créer une vue de table avec 2 sections. La première section contient 4 lignes (Menu 1 et ses éléments) et la section seconde contient 3 lignes (Menu 2 et ses éléments).

Vous définissez toujours la hauteur uniquement pour la première ligne de la section. Et si l'utilisateur clique sur la première ligne, vous développez les lignes de cette section en définissant la hauteur et rechargez cette section.

5

Il n'y a pas de contrôle intégré pour les vues arborescentes comme dans le cadre iOS - UIKit . Comme cela a été souligné par d'autres utilisateurs, la solution la plus simple (sans utiliser de bibliothèques externes) est probablement d'ajouter une logique personnalisée au délégué et à la source de données de UITableView pour imiter le comportement souhaité.

Heureusement, il existe des bibliothèques open source qui vous permettent d'implémenter l'arborescence souhaitée comme la vue sans vous soucier des détails des opérations de développement/réduction. Il y a deux d'entre eux disponibles pour la plate-forme iOS. Dans la plupart des cas, ces bibliothèques enveloppent simplement UITableView et vous fournissent une interface conviviale pour le programmeur qui vous permet de vous concentrer sur votre problème et non sur les détails d'implémentation de l'arborescence.

Personnellement, je suis l'auteur de la bibliothèque RATreeView qui vise à minimiser le coût nécessaire pour créer une arborescence comme des vues sur iOS. Vous pouvez consulter des exemples de projets (disponibles dans Objective-c et Swift ) pour vérifier comment ce contrôle fonctionne et se comporte. En utilisant mon contrôle, il est vraiment simple de créer la vue que vous souhaitez:

  1. DataObject struct sera utilisé pour conserver les informations sur le nœud de l'arborescence - il sera responsable de conserver les informations sur le titre de la cellule, son image (si la cellule a une image) et ses enfants (si la cellule a des enfants) .
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. Nous déclarerons le protocole TreeTableViewCell et implémenterons deux cellules conformes à ce protocole. Une de ces cellules sera utilisée pour afficher les éléments racine et une autre sera utilisée pour afficher les enfants des éléments racine.
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. Dans le contrôleur de vue (MVC) ou le modèle de vue (MVVM), nous définissons la structure de données responsable de la sauvegarde de notre arborescence.
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. Ensuite, nous devrons implémenter quelques méthodes à partir de la source de données de 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
}

Notez que l'utilisation de ma bibliothèque vous n'avez pas à vous soucier de développer et de réduire la cellule - elle est gérée par le RATreeView. Vous n'êtes responsable que des données utilisées pour configurer les cellules - le reste est géré par le contrôle lui-même.

5
Rafał Augustyniak
@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];
    }
}

J'espère que cela vous aidera :)

4
Vũ Ngọc Giang

Vous pouvez avoir un compte en tant que cellule qui se développe au toucher pour révéler trois boutons ("Profil", "Activer le compte", "Changer le mot de passe"), mais cela crée un problème: tapoter autour de chacun des trois boutons comptera comme "sélectionné par l'utilisateur" la cellule Compte "et déclenchez -tableView:didSelectRowAtIndexPath: avec l'extension/l'effondrement de la cellule qui en résulte.

Ou vous pouvez faire de chacune des options masquées ("Profil", "Activer le compte", "Changer le mot de passe") une cellule de vue de table distincte. Mais je ne sais pas comment vous pourriez animer les trois cellules dans son ensemble en expansion et en contraction (au lieu de chaque expansion séparément de la hauteur zéro à la pleine expansion).

Donc, la meilleure solution est peut-être de:

  1. Avoir les cellules paires (indices: 0, 2, 4 ...) pour remplir à la fois le rôle de "Titre de menu" et de "Toggle menu open/close "(vers les cellules impaires associées décrites ci-dessous).
  2. Entrelacer les cellules (initialement réduites) du "corps du menu", chacune avec un bouton par option (par exemple "Profil", "Activer le compte", "Changer le mot de passe"), disposées verticalement, dans les indices impairs (1, 3, 5. ..). Utilisez l'action-cible pour répondre à l'utilisateur sélectionnant chaque option/bouton.
  3. Implémentez la méthode déléguée de la vue de table afin que seules les cellules paires (en-têtes de menu) soient sélectionnables, et implémentez la logique de sélection pour développer/réduire la cellule impaire correspondante (à l'intérieur de -tableView: didSelectRowAtIndexPath :). Par exemple, la sélection de la cellule à l'index 0 ("Compte") entraîne l'expansion/la réduction de la cellule à l'index 1 (menu avec les options "Profil", "Activer le compte", "Changer le mot de passe").

Ce n'est pas l'utilisation la plus élégante d'UITableView, mais cela fera le travail.

3
Nicolas Miari

Vous avez besoin d'une TableView réductible. Pour y parvenir, dans votre TableView, vous devez suivre les sections qui sont réduites (contractées) et celles qui sont développées. Pour cela, vous devez conserver un ensemble d'index de sections développées ou un tableau booléen où la valeur de chaque index indique si la section correspondante est développée ou non. Vérifiez les valeurs à l'index spécifique lors de l'attribution de la hauteur à une certaine ligne. Vérifiez ce lien pour plus d'aide.

Vous pouvez en apprendre davantage sur les vues de table sectionnelles ici .

Il existe des bibliothèques tierces disponibles sur Github qui peuvent vous sauver de l'hustel. Jetez un œil à CollapsableTableView ou CollapsableTable-Swift

3
luckyShubhra

Si vous n'aimez pas utiliser de bibliothèque externe, vous pouvez créer 2 cellules personnalisées. Un qui s'affiche avant l'expansion et l'autre après l'expansion (avec différents identifiants). Et lorsque vous cliquez sur la cellule, vérifiez si la cellule est développée ou non. Si ce n'est pas le cas, utilisez l'identifiant de cellule développé, sinon l'identifiant de cellule non développé.

C'est le moyen le plus efficace et le plus propre de créer une cellule de vue de tableau étendue.

3
Bhavuk Jain

Selon la réponse de @sticker, vous pouvez lier l'exécution

objc_setAssociatedObject

pour l'index de section, et utilisez sa logique. Et tout en utilisant tapgesture sur la vue d'en-tête, vous pouvez obtenir l'index de section comme

objc_getAssociatedObject.

UITapGestureRecognizer *singleTapRecogniser = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(gestureHandler:)] autorelease];
[singleTapRecogniser setDelegate:self];
singleTapRecogniser.numberOfTouchesRequired = 1;
singleTapRecogniser.numberOfTapsRequired = 1;   
[sectionHeaderView addGestureRecognizer:singleTapRecogniser];

Si vous voulez une bibliothèque tierce, vous pouvez essayer la solution this .

2
Ankit Thakur

J'aime la solution @Cristik, il y a quelque temps, j'ai eu le même problème et ma solution suit en quelque sorte les mêmes principes; c'est donc ce que je propose en fonction des exigences que j'avais:

  1. Pour le rendre plus général, les éléments de la table ne devraient pas hériter d'une classe spécialisée pour la fonctionnalité d'extension, mais il devrait y avoir un protocole qui définit les propriétés nécessaires

  2. Il ne devrait pas y avoir de restriction sur le nombre de niveaux que nous pouvons étendre. Ainsi, la table peut avoir une option, une sous-option, une sous-option, etc.

  3. La vue du tableau doit afficher ou masquer les cellules en utilisant l'une des animations habituelles (pas de reloadData)

  4. L'action d'expansion ne doit pas nécessairement être attachée à l'utilisateur qui sélectionne la cellule, la cellule peut avoir un UISwitch par exemple

La version simplifiée de l'implémentation ( https://github.com/JuanjoArreola/ExpandableCells ) est la suivante:

D'abord le protocole:

protocol CellDescriptor: class {
    var count: Int { get }
    var identifier: String! { get }
}

Une cellule non extensible a toujours un nombre de 1:

extension CellDescriptor {
    var count: Int { return 1 }
}

Ensuite, le protocole de cellule extensible:

protocol ExpandableCellDescriptor: CellDescriptor {
    var active: Bool { get set }
    var children: [CellDescriptor] { get set }

    subscript(index: Int) -> CellDescriptor? { get }
    func indexOf(cellDescriptor: CellDescriptor) -> Int?
}

Une chose intéressante à propos de Swift est que nous pouvons écrire une partie de l'implémentation dans une extension de protocole et que toutes les classes conformes peuvent utiliser l'implémentation par défaut, nous pouvons donc écrire le countsubscript et indexOf implémentation et en plus quelques autres fonctions utiles comme ceci:

extension ExpandableCellDescriptor {
    var count: Int {
        var total = 1
        if active {
            children.forEach({ total += $0.count })
        }
        return total
    }

    var countIfActive: Int {
        ...
    }

    subscript(index: Int) -> CellDescriptor? {
        ...
    }

    func indexOf(cellDescriptor: CellDescriptor) -> Int? {
        ...
    }

    func append(cellDescriptor: CellDescriptor) {
        children.append(cellDescriptor)
    }
}

L'implémentation complète se trouve dans le fichier CellDescriptor.Swift

De plus, dans le même fichier, il existe une classe nommée CellDescriptionArray qui implémente ExpandableCellDescriptor et n'affiche pas une cellule seule

Maintenant, n'importe quelle classe peut se conformer aux protocoles précédents sans avoir besoin d'hériter d'une classe spécifique, pour l'exemple de code dans github j'ai créé quelques classes: Option et ExpandableOption, voici comment ExpandableOption ressemble à:

class ExpandableOption: ExpandableCellDescriptor {

    var delegate: ExpandableCellDelegate?

    var identifier: String!
    var active: Bool = false {
        didSet {
            delegate?.expandableCell(self, didChangeActive: active)
        }
    }

    var children: [CellDescriptor] = []
    var title: String?
}

Et c'est l'une des sous-classes UITableViewCell:

class SwitchTableViewCell: UITableViewCell, CellDescrptionConfigurable {

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var switchControl: UISwitch!

    var cellDescription: CellDescriptor! {
        didSet {
            if let option = cellDescription as? ExpandableOption {
                titleLabel.text = option.title
                switchControl.on = option.active
            }
        }
    }

    @IBAction func activeChanged(sender: UISwitch) {
       let expandableCellDescriptor = cellDescription as! ExpandableCellDescriptor
       expandableCellDescriptor.active = sender.on
    }
}

Notez que vous pouvez configurer la cellule et sa classe comme vous le souhaitez, vous pouvez ajouter des images, des étiquettes, des commutateurs, etc.; aucune restriction et aucun changement aux protocoles nécessaires.

Enfin, dans TableViewController, nous créons l'arborescence des options:

var options = CellDescriptionArray()

override func viewDidLoad() {
   super.viewDidLoad()

   let account = ExpandableOption(identifier: "ExpandableCell", title: "Account")
   let profile = Option(identifier: "SimpleCell", title: "Profile")
   let isPublic = ExpandableOption(identifier: "SwitchCell", title: "Public")
   let caption = Option(identifier: "SimpleCell", title: "Anyone can see this account")
   isPublic.append(caption)
   account.append(profile)
   account.append(isPublic)
   options.append(account)

   let group = ExpandableOption(identifier: "ExpandableCell", title: "Group")
   group.append(Option(identifier: "SimpleCell", title: "Group Settings"))
   options.append(group)
   ...
}

Le reste de l'implémentation est désormais très simple:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   return options.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
   let option = options[indexPath.row]!
   let cell = tableView.dequeueReusableCellWithIdentifier(option.identifier, forIndexPath: indexPath)

   (cell as! CellDescrptionConfigurable).cellDescription = option
   (option as? ExpandCellInformer)?.delegate = self
   return cell
}

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    guard let option = options[indexPath.row] else { return }
    guard let expandableOption = option as? ExpandableOption else { return }
    if expandableOption.identifier == "ExpandableCell" {
        expandableOption.active = !expandableOption.active
    }
}

func expandableCell(expandableCell: ExpandableCellDescriptor, didChangeActive active: Bool) {
    guard let index = options.indexOf(expandableCell) else { return }
    var indexPaths = [NSIndexPath]()
    for row in 1..<expandableCell.countIfActive {
        indexPaths.append(NSIndexPath(forRow: index + row, inSection: 0))
    }
    if active {
        tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Fade)
    } else {
        tableView.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Fade)
    }
}

Cela peut ressembler à beaucoup de code, mais la plupart ne sont écrits qu'une seule fois, la plupart des informations nécessaires pour dessiner correctement la vue de table existent dans le fichier CellDescriptor.Swift, le code de configuration de cellule existe à l'intérieur des sous-classes UITableViewCell et il y a relativement peu de code dans le TableViewController lui-même.

J'espère que ça aide.

2
juanjo