it-swarm.com.de

Dropdown-Liste in UITableView unter iOS

enter image description here

So erstellen Sie diese Art der Tabellenansicht in iOS?

Wenn wir hier auf die erste Zeile "Konto" tippen, wird automatisch ein Bildlauf mit einigen weiteren Zeilen durchgeführt, die in "Bild" angezeigt werden. Und wenn wir erneut auf Konto tippen, wird diese Ansicht ausgeblendet.

40
Meet Doshi

Sie können eine Zelle leicht so einrichten, dass sie wie eine Kopfzeile aussieht, und den tableView: didSelectRowAtIndexPath Einrichten, um den Abschnitt, in dem sie sich befindet, manuell zu erweitern oder zu reduzieren. Wenn ich ein Array von Booleschen Werten speichern würde, die dem "verbrauchten" Wert jedes Ihrer Abschnitte entsprechen. Sie könnten dann den tableView:didSelectRowAtIndexPath In jeder Ihrer benutzerdefinierten Kopfzeilen haben, um diesen Wert umzuschalten und dann diesen bestimmten Abschnitt neu zu laden.

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

Sie würden dann Ihre Nummer numberOfRowsInSection einrichten, um den Wert mybooleans zu überprüfen und entweder 1 zurückzugeben, wenn der Abschnitt nicht erweitert ist, oder 1+ die Anzahl der Elemente im Abschnitt, wenn er erweitert ist .

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

Sie müssten auch Ihr cellForRowAtIndexPath aktualisieren, um eine benutzerdefinierte Header-Zelle für die erste Zeile in einem beliebigen section zurückzugeben.

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section ist die bessere Möglichkeit, Ihren "eigenen benutzerdefinierten Header" bereitzustellen, da dies genau das ist, wofür er entwickelt wurde.

Weitere Informationen finden Sie unter Answer oder this PKCollapsingTableViewSections .

Sie können diese Art von Tabellenansichten auch mit setIndentationLevel abrufen. Bitte beziehen Sie sich auf diesen DemoCode für dieses Beispiel. Ich denke, dies ist die beste Lösung für Dropdown-Tabellenansichten.

Wenn Sie eine einfache Kopfzeile und eine Zellen-Dropdown-Liste erstellen möchten, lesen Sie bitte STCollapseTableView .

Hoffe, das ist was du suchst. Alle Bedenken melden sich bei mir. :)

31
Meet Doshi

Die einfachste und natürlichste Möglichkeit, dies zu implementieren, ist die Verwendung von Tabellenansichtszellen. Keine expandierenden Zellenansichten, keine Abschnittsüberschriften, einfache und einfache Zellen (wir sind schließlich in einer Tabellenansicht).

Das Design ist wie folgt:

  • erstellen Sie mithilfe eines MVVM-Ansatzes eine CollapsableViewModel -Klasse, die die Informationen enthält, die zum Konfigurieren der Zelle erforderlich sind: label, image
  • neben dem obigen Feld gibt es zwei zusätzliche Felder: children, ein Array von CollapsableViewModel Objekten, und isCollapsed, das den Status des Dropdowns enthält
  • der Ansichtscontroller enthält einen Verweis auf die Hierarchie von CollapsableViewModel sowie eine flache Liste mit den Ansichtsmodellen, die auf dem Bildschirm gerendert werden (die Eigenschaft displayedRows).
  • wenn Sie auf eine Zelle tippen, prüfen Sie, ob sie untergeordnete Elemente enthält, und fügen Sie Zeilen sowohl in displayedRows als auch in der Tabellenansicht mit den Funktionen insertRowsAtIndexPaths() und deleteRowsAtIndexPaths() hinzu oder entfernen Sie sie.

Der Code Swift sieht wie folgt aus (beachten Sie, dass der Code nur die Eigenschaft label des Ansichtsmodells verwendet, um ihn sauber zu halten):

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

Das Objective-C-Gegenstück ist einfach zu übersetzen. Ich habe die Swift) -Version nur hinzugefügt, da sie kürzer und besser lesbar ist.

Mit ein paar kleinen Änderungen kann der Code verwendet werden, um Dropdown-Listen mit mehreren Ebenen zu generieren.

Bearbeiten

Die Leute fragten mich nach den Trennzeichen. Dies kann durch Hinzufügen einer benutzerdefinierten Klasse CollapsibleTableViewCell erreicht werden, die mit einem Ansichtsmodell konfiguriert wird (verschieben Sie schließlich die Zellenkonfigurationslogik vom Controller an den Ort, zu dem sie gehört - die Zelle). Credits für die Trennzeichenlogik gehen nur für einige der Zellen an Personen, die this SO question beantworten.

Aktualisieren Sie zunächst das Modell, und fügen Sie eine needsSeparator -Eigenschaft hinzu, mit der die Zelle der Tabellenansicht angewiesen wird, das Trennzeichen zu rendern oder nicht:

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

Fügen Sie dann die Zellklasse hinzu:

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 müsste dann geändert werden, um diese Art von Zellen zurückzugeben:

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
    }

In einem letzten Schritt entfernen Sie die standardmäßigen Tabellentrennzeichen - entweder aus xib oder aus dem Code (tableView.separatorStyle = .none).

25
Cristik

Hier ist eine [~ # ~] mvc [~ # ~] basierte Lösung.

Erstellen Sie eine Modellklasse ClsMenuGroup für Ihre Abschnitte

class ClsMenuGroup: NSObject {

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

Erstellen Sie eine Modellklasse ClsMenu für Ihre Zeilen

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

Erstellen Sie ein Gruppenarray in Ihrem 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(){

    }
}

Ich habe HDTableDataSource anstelle der Datenquellenmethoden von Tableview verwendet. Sie können ein Beispiel für HDTableDataSource von Github finden.

Vorteile des obigen Codes ist

  1. Sie können jederzeit die Reihenfolge von Menüs oder Abschnitten ändern oder Menüs und Abschnitte austauschen, ohne andere Funktionen zu ändern.
  2. Sie müssen in den Delegatmethoden Ihrer Tabellenansicht keinen langen Code für else if ladder hinzufügen
  3. Sie können das Symbol, den Titel oder ein anderes Attribut für Ihren Menüpunkt separat festlegen, z. B. die Anzahl der Ausweise hinzufügen, die Farbe des ausgewählten Menüs ändern usw.
  4. Sie können auch mehrere Zellen oder Abschnitte verwenden, indem Sie geringfügige Änderungen an vorhandenem Code vornehmen
7
HarshIT

verwenden Sie dazu einfach den UITableView-Abschnittsheader als cell-> und setzen Sie die Zeilennummer auf 0 und section.count, um den Status zu reduzieren und zu erweitern.

  • Dies ist TableViewSection Header, isExpand -> section.count, sonst wird 0 zurückgegeben.

    -Normale Zelle

    -Normale Zelle

    -Normale Zelle

  • Dies ist TableViewSection Header, isExpand -> section.count, sonst wird 0 zurückgegeben.

    -Normale Zelle

    -Normale Zelle

    -Normale Zelle

5
Trung Phan

Normalerweise mache ich das durch Einstellen der Zeilenhöhe. Sie haben beispielsweise zwei Menüelemente mit Dropdown-Listen:

  • Menü 1
    • Punkt 1.1
    • Punkt 1.2
    • Punkt 1.3
  • Menü 2
    • Punkt 2.1
    • Punkt 2.2

Sie müssen also eine Tabellenansicht mit 2 Abschnitten erstellen. Der erste Abschnitt enthält 4 Zeilen (Menü 1 und seine Elemente) und der zweite Abschnitt enthält 3 Zeilen (Menü 2 und seine Elemente).

Sie legen die Höhe immer nur für die erste Reihe im Abschnitt fest. Wenn der Benutzer auf die erste Zeile klickt, erweitern Sie diese Abschnittszeilen, indem Sie die Höhe einstellen und diesen Abschnitt neu laden.

5

In iOS-Framework gibt es keine integrierte Steuerung für Ansichten in der Strukturansicht - UIKit . Wie andere Benutzer bereits betont haben, besteht die wahrscheinlich einfachste Lösung (ohne Verwendung externer Bibliotheken) darin, dem Delegaten und der Datenquelle des UITableView eine benutzerdefinierte Logik hinzuzufügen, um das gewünschte Verhalten zu imitieren.

Glücklicherweise gibt es einige Open-Source-Bibliotheken, mit denen Sie die gewünschte Strukturansicht wie die Ansicht implementieren können, ohne sich um die Details der Expand/Collapse-Operationen kümmern zu müssen. Es gibt ein einige davon für die iOS-Plattform. In den meisten Fällen schließen diese Bibliotheken nur UITableView um und bieten Ihnen eine programmiererfreundliche Oberfläche, mit der Sie sich auf Ihr Problem und nicht auf die Implementierungsdetails der Baumansicht konzentrieren können.

Persönlich bin ich der Autor der Bibliothek RATreeView , die dazu dient, die Kosten für die Erstellung von Baumansichten wie Ansichten unter iOS zu minimieren. Sie können Beispielprojekte (verfügbar in Objective-c und Swift ) auschecken, um zu überprüfen, wie dieses Steuerelement funktioniert und sich verhält. Mit meiner Steuerung ist es ganz einfach, die gewünschte Ansicht zu erstellen:

  1. DataObject struct wird verwendet, um Informationen über den Strukturansichtsknoten zu speichern. Er ist dafür verantwortlich, Informationen über den Titel der Zelle, ihr Bild (wenn die Zelle ein Bild enthält) und ihre untergeordneten Elemente (wenn die Zelle untergeordnete Elemente enthält) zu speichern. .
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. Wir werden das Protokoll TreeTableViewCell deklarieren und zwei Zellen implementieren, die diesem Protokoll entsprechen. In einer dieser Zellen werden Stammelemente und in einer anderen Zelle die untergeordneten Elemente der Stammelemente angezeigt.
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. In unserem View Controller (MVC) oder View Model (MVVM) definieren wir die Datenstruktur, die für die Sicherung unserer Baumansicht verantwortlich ist.
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. Als nächstes müssen wir einige Methoden aus der Datenquelle von RATreeView implementieren.
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
}

Beachten Sie, dass Sie sich bei der Verwendung meiner Bibliothek nicht um das Erweitern und Reduzieren der Zelle kümmern müssen. Sie wird von RATreeView verwaltet. Sie sind nur für die Daten verantwortlich, die zur Konfiguration der Zellen verwendet werden - der Rest wird von der Steuerung selbst erledigt.

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

Hoffe es hilft :)

4

Sie könnten Account als eine Zelle haben, die durch Tippen auf drei Schaltflächen erweitert wird ("Profil", "Account aktivieren", "Passwort ändern"), aber das schafft ein Problem: Wenn Sie auf jede der drei Schaltflächen tippen, wird dies als "Benutzer ausgewählt" gewertet die Kontozelle "und triggern -tableView:didSelectRowAtIndexPath: mit der daraus resultierenden Vergrößerung/Verkleinerung der Zelle.

Sie können auch für jede der ausgeblendeten Optionen ("Profil", "Konto aktivieren", "Kennwort ändern") eine eigene Tabellenzelle festlegen. Aber ich weiß nicht, wie Sie die drei Zellen animieren könnten als Ganzes erweitern und verkleinern (anstatt jede von null auf vollständig erweitert zu erweitern).

Also, vielleicht ist die beste Lösung:

  1. Haben Sie die geraden Zellen (Indizes: 0, 2, 4 ...), um sowohl die Rolle "Menütitel" als auch "Menü öffnen/wechseln" zu erfüllen? close "(in Richtung der unten beschriebenen zugehörigen ungeraden Zellen).
  2. Verschachteln Sie die (ursprünglich ausgeblendeten) "Menükörper" -Zellen mit jeweils einer Schaltfläche pro Option (z. B. "Profil", "Konto aktivieren", "Passwort ändern"), die vertikal angeordnet sind, in den ungeraden Indizes (1, 3, 5). ..). Verwenden Sie die Zielaktion, um dem Benutzer zu antworten, der die einzelnen Optionen/Schaltflächen auswählt.
  3. Implementieren Sie die Delegierungsmethode für die Tabellenansicht, sodass nur die geraden Zellen (Menüüberschriften) ausgewählt werden können, und implementieren Sie die Auswahllogik, um die entsprechende ungerade Zelle zu erweitern/zu reduzieren (innerhalb von -tableView: didSelectRowAtIndexPath :). Wenn Sie beispielsweise die Zelle im Index 0 ("Konto") auswählen, wird die Zelle im Index 1 vergrößert/verkleinert (Menü mit den Optionen "Profil", "Konto aktivieren", "Passwort ändern").

Es ist nicht die eleganteste Verwendung von UITableView, wird aber die Arbeit erledigen.

3
Nicolas Miari

Sie benötigen eine reduzierbare TableView. Um dies zu erreichen, müssen Sie in Ihrer TableView nachverfolgen, welche Abschnitte reduziert (verkleinert) und welche erweitert werden. Zu diesem Zweck müssen Sie eine Reihe von Indizes für Abschnitte verwalten, die erweitert werden, oder ein boolesches Array, in dem der Wert jedes Index angibt, ob der entsprechende Abschnitt erweitert wird oder nicht. Überprüfen Sie die Werte am jeweiligen Index, während Sie einer bestimmten Zeile die Höhe zuweisen. Überprüfen Sie diesen Link für weitere Hilfe.

Sie können mehr über Sectional TableViews erfahren hier .

Es gibt auf Github Bibliotheken von Drittanbietern, die Sie vor dem Rummel bewahren können. Schauen Sie sich CollapsableTableView oder CollapsableTable-Swift an

3
luckyShubhra

Wenn Sie keine externe Bibliothek verwenden möchten, können Sie zwei benutzerdefinierte Zellen erstellen. Eine wird vor dem Erweitern und die andere nach dem Erweitern angezeigt (mit unterschiedlichen Bezeichnern). Und wenn Sie auf die Zelle klicken, prüfen Sie, ob die Zelle erweitert ist oder nicht. Wenn nicht, verwenden Sie den erweiterten Zellenbezeichner, andernfalls den nicht erweiterten Zellenbezeichner.

Dies ist die beste und übersichtlichste Methode, um eine erweiterte Tabellenzelle zu erstellen.

3
Bhavuk Jain

Laut @sticker-Antwort können Sie die Laufzeit binden

objc_setAssociatedObject

für Abschnittsindex, und verwenden Sie seine Logik. Und während Sie tapgesture in der Kopfzeilenansicht verwenden, können Sie den Abschnittsindex als abrufen

objc_getAssociatedObject.

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

Wenn Sie eine Bibliothek eines Drittanbieters benötigen, können Sie this versuchen.

2
Ankit Thakur

Ich mag die @Cristik-Lösung, vor einiger Zeit hatte ich das gleiche Problem und meine Lösung folgt den gleichen Grundsätzen. Das ist also, was ich basierend auf den Anforderungen vorschlage, die ich hatte:

  1. Zur Verallgemeinerung sollten die Elemente der Tabelle nicht von einer Klasse erben, die auf die Erweiterungsfunktionalität spezialisiert ist, sondern es sollte ein Protokoll vorhanden sein, das die erforderlichen Eigenschaften definiert

  2. Die Anzahl der Ebenen, die wir erweitern können, sollte nicht beschränkt sein. Die Tabelle kann also Optionen, Unteroptionen, Unteroptionen usw. enthalten.

  3. Die Tabellenansicht sollte die Zellen mit einer der üblichen Animationen anzeigen oder verbergen (no reloadData)

  4. Die Erweiterungsaktion sollte nicht unbedingt an den Benutzer gebunden sein, der die Zelle auswählt. Die Zelle könnte beispielsweise einen UISwitch haben

Die vereinfachte Version der Implementierung ( https://github.com/JuanjoArreola/ExpandableCells ) lautet wie folgt:

Zuerst das Protokoll:

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

Eine nicht erweiterbare Zelle zählt immer 1:

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

Dann das erweiterbare Zellenprotokoll:

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

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

Eine coole Sache an Swift ist, dass wir einen Teil der Implementierung in eine Protokollerweiterung schreiben können und alle konformen Klassen die Standardimplementierung verwenden können, so dass wir die Implementierung von countsubscript und indexOf und zusätzlich a schreiben können Einige andere nützliche Funktionen wie diese:

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

Die vollständige Implementierung befindet sich in der Datei CellDescriptor.Swift

Darüber hinaus gibt es in derselben Datei eine Klasse mit dem Namen CellDescriptionArray, die ExpandableCellDescriptor implementiert und selbst keine Zelle anzeigt

Jetzt kann jede Klasse den vorherigen Protokollen entsprechen, ohne von einer bestimmten Klasse erben zu müssen. Für den Beispielcode in github habe ich ein paar Klassen erstellt: Option und ExpandableOption, so sieht ExpandableOption aus:

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

Und dies ist eine der UITableViewCell-Unterklassen:

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

Beachten Sie, dass Sie die Zelle und ihre Klasse nach Ihren Wünschen konfigurieren und Bilder, Beschriftungen, Schalter usw. hinzufügen können. Keine Einschränkungen und keine Änderung der Protokolle erforderlich.

Schließlich erstellen wir im TableViewController den Optionsbaum:

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

Der Rest der Implementierung ist jetzt sehr einfach:

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

Möglicherweise sieht es nach viel Code aus, aber der Großteil ist nur einmal geschrieben. Die meisten Informationen, die zum korrekten Zeichnen der Tabellenansicht benötigt werden, sind in der CellDescriptor.Swift-Datei vorhanden. Der Zellenkonfigurationscode befindet sich in den UITableViewCell-Unterklassen und ist relativ vorhanden wenig Code im TableViewController selbst.

Ich hoffe es hilft.

2
juanjo