Quelqu'un pourrait-il me dire comment exécuter les animations UITableView
extensibles/réductibles dans sections
de UITableView
comme ci-dessous?
ou
Vous devez créer votre propre ligne d'en-tête personnalisée et la placer comme première ligne de chaque section. Sous-classer la UITableView
ou les en-têtes déjà présents sera pénible. Compte tenu de leur mode de fonctionnement actuel, je ne suis pas sûr que vous puissiez facilement en tirer des actions. Vous pouvez configurer une cellule pour ressembler à un en-tête et configurer le tableView:didSelectRowAtIndexPath
pour développer ou réduire manuellement la section dans laquelle elle se trouve.
Je stockerais un tableau de booléens correspondant à la valeur "dépensée" de chacune de vos sections. Ensuite, vous pouvez 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];
}
}
Puis définissez numberOfRowsInSection
pour vérifier la valeur mybooleans
et renvoyer 1 si la section n'est pas développée ou 1+ le nombre d'éléments de 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;
}
}
En outre, vous devrez mettre à jour cellForRowAtIndexPath
pour renvoyer une cellule d'en-tête personnalisée pour la première ligne d'une section.
Apple fournit ici un exemple de code permettant d'animer une action développer/réduire à l'aide d'un en-tête de section de vue tableau: Animations et gestes de vue tableau
La clé de cette approche consiste à implémenter - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
et à renvoyer une UIView personnalisée comprenant un bouton (généralement de la même taille que la vue d’en-tête elle-même). En sous-classant UIView et en l'utilisant pour la vue d'en-tête (comme le fait cet exemple), vous pouvez facilement stocker des données supplémentaires telles que le numéro de section.
J'ai obtenu une solution Nice inspirée des animations et des gestes de la table Table View . J'ai supprimé des parties inutiles de l'échantillon d'Apple et les ai traduites en Swift.
Je sais que la réponse est assez longue, mais tout le code est nécessaire. Heureusement, vous pouvez simplement copier et coller la plupart du code et il vous suffit de faire quelques modifications à l’étape 1 et 3.
1.créer SectionHeaderView.Swift
et SectionHeaderView.xib
import UIKit
protocol SectionHeaderViewDelegate {
func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionOpened: Int)
func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionClosed: Int)
}
class SectionHeaderView: UITableViewHeaderFooterView {
var section: Int?
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var disclosureButton: UIButton!
@IBAction func toggleOpen() {
self.toggleOpenWithUserAction(true)
}
var delegate: SectionHeaderViewDelegate?
func toggleOpenWithUserAction(userAction: Bool) {
self.disclosureButton.selected = !self.disclosureButton.selected
if userAction {
if self.disclosureButton.selected {
self.delegate?.sectionHeaderView(self, sectionClosed: self.section!)
} else {
self.delegate?.sectionHeaderView(self, sectionOpened: self.section!)
}
}
}
override func awakeFromNib() {
var tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "toggleOpen")
self.addGestureRecognizer(tapGesture)
// change the button image here, you can also set image via IB.
self.disclosureButton.setImage(UIImage(named: "arrow_up"), forState: UIControlState.Selected)
self.disclosureButton.setImage(UIImage(named: "arrow_down"), forState: UIControlState.Normal)
}
}
le SectionHeaderView.xib
(la vue avec un fond gris) devrait ressembler à ceci dans une table (vous pouvez le personnaliser en fonction de vos besoins, bien sûr):
remarque:
a) l'action toggleOpen
devrait être liée à disclosureButton
b) les actions disclosureButton
et toggleOpen
ne sont pas nécessaires. Vous pouvez supprimer ces 2 choses si vous n’avez pas besoin du bouton.
2.créer SectionInfo.Swift
import UIKit
class SectionInfo: NSObject {
var open: Bool = true
var itemsInSection: NSMutableArray = []
var sectionTitle: String?
init(itemsInSection: NSMutableArray, sectionTitle: String) {
self.itemsInSection = itemsInSection
self.sectionTitle = sectionTitle
}
}
3.dans votre tableview
import UIKit
class TableViewController: UITableViewController, SectionHeaderViewDelegate {
let SectionHeaderViewIdentifier = "SectionHeaderViewIdentifier"
var sectionInfoArray: NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
let sectionHeaderNib: UINib = UINib(nibName: "SectionHeaderView", bundle: nil)
self.tableView.registerNib(sectionHeaderNib, forHeaderFooterViewReuseIdentifier: SectionHeaderViewIdentifier)
// you can change section height based on your needs
self.tableView.sectionHeaderHeight = 30
// You should set up your SectionInfo here
var firstSection: SectionInfo = SectionInfo(itemsInSection: ["1"], sectionTitle: "firstSection")
var secondSection: SectionInfo = SectionInfo(itemsInSection: ["2"], sectionTitle: "secondSection"))
sectionInfoArray.addObjectsFromArray([firstSection, secondSection])
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionInfoArray.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.sectionInfoArray.count > 0 {
var sectionInfo: SectionInfo = sectionInfoArray[section] as! SectionInfo
if sectionInfo.open {
return sectionInfo.open ? sectionInfo.itemsInSection.count : 0
}
}
return 0
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionHeaderView: SectionHeaderView! = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier(SectionHeaderViewIdentifier) as! SectionHeaderView
var sectionInfo: SectionInfo = sectionInfoArray[section] as! SectionInfo
sectionHeaderView.titleLabel.text = sectionInfo.sectionTitle
sectionHeaderView.section = section
sectionHeaderView.delegate = self
let backGroundView = UIView()
// you can customize the background color of the header here
backGroundView.backgroundColor = UIColor(red:0.89, green:0.89, blue:0.89, alpha:1)
sectionHeaderView.backgroundView = backGroundView
return sectionHeaderView
}
func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionOpened: Int) {
var sectionInfo: SectionInfo = sectionInfoArray[sectionOpened] as! SectionInfo
var countOfRowsToInsert = sectionInfo.itemsInSection.count
sectionInfo.open = true
var indexPathToInsert: NSMutableArray = NSMutableArray()
for i in 0..<countOfRowsToInsert {
indexPathToInsert.addObject(NSIndexPath(forRow: i, inSection: sectionOpened))
}
self.tableView.insertRowsAtIndexPaths(indexPathToInsert as [AnyObject], withRowAnimation: .Top)
}
func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionClosed: Int) {
var sectionInfo: SectionInfo = sectionInfoArray[sectionClosed] as! SectionInfo
var countOfRowsToDelete = sectionInfo.itemsInSection.count
sectionInfo.open = false
if countOfRowsToDelete > 0 {
var indexPathToDelete: NSMutableArray = NSMutableArray()
for i in 0..<countOfRowsToDelete {
indexPathToDelete.addObject(NSIndexPath(forRow: i, inSection: sectionClosed))
}
self.tableView.deleteRowsAtIndexPaths(indexPathToDelete as [AnyObject], withRowAnimation: .Top)
}
}
}
Pour implémenter la section de tableau réductible dans iOS, la magie est de savoir comment contrôler le nombre de lignes pour chaque section, ou nous pouvons gérer la hauteur des lignes pour chaque section.
En outre, nous devons personnaliser l'en-tête de section afin de pouvoir écouter l'événement tap depuis la zone d'en-tête (qu'il s'agisse d'un bouton ou de l'en-tête complet).
Comment traiter l'en-tête? C'est très simple, nous étendons la classe UITableViewCell et créons une cellule d'en-tête personnalisée comme ceci:
import UIKit
class CollapsibleTableViewHeader: UITableViewCell {
@IBOutlet var titleLabel: UILabel!
@IBOutlet var toggleButton: UIButton!
}
utilisez ensuite viewForHeaderInSection pour connecter la cellule d’en-tête:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableCellWithIdentifier("header") as! CollapsibleTableViewHeader
header.titleLabel.text = sections[section].name
header.toggleButton.tag = section
header.toggleButton.addTarget(self, action: #selector(CollapsibleTableViewController.toggleCollapse), forControlEvents: .TouchUpInside)
header.toggleButton.rotate(sections[section].collapsed! ? 0.0 : CGFloat(M_PI_2))
return header.contentView
}
souvenez-vous que nous devons renvoyer le contentView car cette fonction s'attend à ce qu'un UIView soit renvoyé.
Parlons maintenant de la partie repliable, voici la fonction de basculement qui bascule l'accessoire pliable de chaque section:
func toggleCollapse(sender: UIButton) {
let section = sender.tag
let collapsed = sections[section].collapsed
// Toggle collapse
sections[section].collapsed = !collapsed
// Reload section
tableView.reloadSections(NSIndexSet(index: section), withRowAnimation: .Automatic)
}
cela dépend de la façon dont vous gérez les données de section. Dans ce cas, les données de section ressemblent à ceci:
struct Section {
var name: String!
var items: [String]!
var collapsed: Bool!
init(name: String, items: [String]) {
self.name = name
self.items = items
self.collapsed = false
}
}
var sections = [Section]()
sections = [
Section(name: "Mac", items: ["MacBook", "MacBook Air", "MacBook Pro", "iMac", "Mac Pro", "Mac mini", "Accessories", "OS X El Capitan"]),
Section(name: "iPad", items: ["iPad Pro", "iPad Air 2", "iPad mini 4", "Accessories"]),
Section(name: "iPhone", items: ["iPhone 6s", "iPhone 6", "iPhone SE", "Accessories"])
]
enfin, ce que nous devons faire est basé sur le prop pliable de chaque section, contrôler le nombre de lignes de cette section:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (sections[section].collapsed!) ? 0 : sections[section].items.count
}
J'ai une démo entièrement opérationnelle sur mon Github: https://github.com/jeantimex/ios-Swift-collapsible-table-section
Si vous souhaitez implémenter les sections réductibles dans une table de style groupé, j'ai une autre démonstration avec le code source ici: https://github.com/jeantimex/ios-Swift-collapsible-table-section-in-grouped- section
J'espère que cela pourra aider.
J'ai une meilleure solution que vous devriez ajouter un UIButton dans l'en-tête de section et définir la taille de ce bouton égale à la taille de la section, mais le masquer par la couleur d'arrière-plan clair, après que vous êtes facilement à vérifier quelle section est cliqué pour développer ou réduire
J'ai fini par créer juste un header qui contenait un bouton (j'ai vu la solution de Son Nguyen ci-dessus après le fait, mais voici mon code .. il ressemble beaucoup mais c'est assez simple):
déclarer un couple bools pour vous sections
bool customerIsCollapsed = NO;
bool siteIsCollapsed = NO;
...code
maintenant dans vos méthodes de délégué tableview ...
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, _tblSearchResults.frame.size.width, 35)];
UILabel *lblSection = [UILabel new];
[lblSection setFrame:CGRectMake(0, 0, 300, 30)];
[lblSection setFont:[UIFont fontWithName:@"Helvetica-Bold" size:17]];
[lblSection setBackgroundColor:[UIColor clearColor]];
lblSection.alpha = 0.5;
if(section == 0)
{
if(!customerIsCollapsed)
[lblSection setText:@"Customers --touch to show--"];
else
[lblSection setText:@"Customers --touch to hide--"];
}
else
{
if(!siteIsCollapsed)
[lblSection setText:@"Sites --touch to show--"];
else
[lblSection setText:@"Sites --touch to hide--"]; }
UIButton *btnCollapse = [UIButton buttonWithType:UIButtonTypeCustom];
[btnCollapse setFrame:CGRectMake(0, 0, _tblSearchResults.frame.size.width, 35)];
[btnCollapse setBackgroundColor:[UIColor clearColor]];
[btnCollapse addTarget:self action:@selector(touchedSection:) forControlEvents:UIControlEventTouchUpInside];
btnCollapse.tag = section;
[headerView addSubview:lblSection];
[headerView addSubview:btnCollapse];
return headerView;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if(section == 0)
{
if(customerIsCollapsed)
return 0;
else
return _customerArray.count;
}
else if (section == 1)
{
if(siteIsCollapsed)
return 0;
else
return _siteArray.count;
}
return 0;
}
et enfin la fonction appelée lorsque vous touchez l'un des boutons d'en-tête de section:
- (IBAction)touchedSection:(id)sender
{
UIButton *btnSection = (UIButton *)sender;
if(btnSection.tag == 0)
{
NSLog(@"Touched Customers header");
if(!customerIsCollapsed)
customerIsCollapsed = YES;
else
customerIsCollapsed = NO;
}
else if(btnSection.tag == 1)
{
NSLog(@"Touched Site header");
if(!siteIsCollapsed)
siteIsCollapsed = YES;
else
siteIsCollapsed = NO;
}
[_tblSearchResults reloadData];
}
C’est le meilleur moyen que j’ai trouvé de créer des cellules de tableau extensibles
fichier .h
NSMutableIndexSet *expandedSections;
fichier .m
if (!expandedSections)
{
expandedSections = [[NSMutableIndexSet alloc] init];
}
UITableView *masterTable = [[UITableView alloc] initWithFrame:CGRectMake(0,100,1024,648) style:UITableViewStyleGrouped];
masterTable.delegate = self;
masterTable.dataSource = self;
[self.view addSubview:masterTable];
Méthodes déléguées de la vue tableau
- (BOOL)tableView:(UITableView *)tableView canCollapseSection:(NSInteger)section
{
// if (section>0) return YES;
return YES;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 4;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([self tableView:tableView canCollapseSection:section])
{
if ([expandedSections containsIndex:section])
{
return 5; // return rows when expanded
}
return 1; // only top row showing
}
// Return the number of rows in the section.
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
}
// Configure the cell...
if ([self tableView:tableView canCollapseSection:indexPath.section])
{
if (!indexPath.row)
{
// first row
cell.textLabel.text = @"Expandable"; // only top row showing
if ([expandedSections containsIndex:indexPath.section])
{
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"UITableContract"]];
cell.accessoryView = imView;
}
else
{
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"UITableExpand"]];
cell.accessoryView = imView;
}
}
else
{
// all other rows
if (indexPath.section == 0) {
cell.textLabel.text = @"section one";
}else if (indexPath.section == 1) {
cell.textLabel.text = @"section 2";
}else if (indexPath.section == 2) {
cell.textLabel.text = @"3";
}else {
cell.textLabel.text = @"some other sections";
}
cell.accessoryView = nil;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
}
else
{
cell.accessoryView = nil;
cell.textLabel.text = @"Normal Cell";
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self tableView:tableView canCollapseSection:indexPath.section])
{
if (!indexPath.row)
{
// only first row toggles exapand/collapse
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSInteger section = indexPath.section;
BOOL currentlyExpanded = [expandedSections containsIndex:section];
NSInteger rows;
NSMutableArray *tmpArray = [NSMutableArray array];
if (currentlyExpanded)
{
rows = [self tableView:tableView numberOfRowsInSection:section];
[expandedSections removeIndex:section];
}
else
{
[expandedSections addIndex:section];
rows = [self tableView:tableView numberOfRowsInSection:section];
}
for (int i=1; i<rows; i++)
{
NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i
inSection:section];
[tmpArray addObject:tmpIndexPath];
}
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (currentlyExpanded)
{
[tableView deleteRowsAtIndexPaths:tmpArray
withRowAnimation:UITableViewRowAnimationTop];
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"UITableExpand"]];
cell.accessoryView = imView;
}
else
{
[tableView insertRowsAtIndexPaths:tmpArray
withRowAnimation:UITableViewRowAnimationTop];
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"UITableContract"]];
cell.accessoryView = imView;
}
}
}
NSLog(@"section :%d,row:%d",indexPath.section,indexPath.row);
}
Donc, basée sur la solution 'button in header', voici une implémentation propre et minimaliste:
Voici le code:
@interface MyTableViewController ()
@property (nonatomic, strong) NSMutableIndexSet *collapsedSections;
@end
...
@implementation MyTableViewController
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (!self)
return;
self.collapsedSections = [NSMutableIndexSet indexSet];
return self;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// if section is collapsed
if ([self.collapsedSections containsIndex:section])
return 0;
// if section is expanded
#warning incomplete implementation
return [super tableView:tableView numberOfRowsInSection:section];
}
- (IBAction)toggleSectionHeader:(UIView *)sender
{
UITableView *tableView = self.tableView;
NSInteger section = sender.tag;
MyTableViewHeaderFooterView *headerView = (MyTableViewHeaderFooterView *)[self tableView:tableView viewForHeaderInSection:section];
if ([self.collapsedSections containsIndex:section])
{
// section is collapsed
headerView.button.selected = YES;
[self.collapsedSections removeIndex:section];
}
else
{
// section is expanded
headerView.button.selected = NO;
[self.collapsedSections addIndex:section];
}
[tableView beginUpdates];
[tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
}
@end
J'ai trouvé un autre moyen relativement simple de résoudre ce problème. En utilisant cette méthode, nous n’avons pas besoin de modifier notre cellule, qui est presque toujours liée à l’indice de tableau de données, ce qui peut causer des dégâts dans notre contrôleur de vue.
Tout d'abord, nous ajoutons les propriétés suivantes à notre classe de contrôleur:
@property (strong, nonatomic) NSMutableArray* collapsedSections;
@property (strong, nonatomic) NSMutableArray* sectionViews;
collapsedSections
enregistrera les numéros de section réduits .sectionViews
stockera notre vue de section personnalisée.
Le synthétiser:
@synthesize collapsedSections;
@synthesize sectionViews;
Initialise le:
- (void) viewDidLoad
{
[super viewDidLoad];
self.collapsedSections = [NSMutableArray array];
self.sectionViews = [NSMutableArray array];
}
Après cela, nous devons connecter notre UITableView afin de pouvoir y accéder depuis notre classe de contrôleur de vue:
@property (strong, nonatomic) IBOutlet UITableView *tblMain;
Connectez-le à partir de XIB pour afficher le contrôleur en utilisant ctrl + drag
comme d'habitude.
Ensuite, nous créons une vue en tant qu'en-tête de section personnalisé pour notre vue table en implémentant ce délégué UITableView:
- (UIView*) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
// Create View
CGRect frame = CGRectZero;
frame.Origin = CGPointZero;
frame.size.height = 30.f;
frame.size.width = tableView.bounds.size.width;
UIView* view = [[UIView alloc] initWithFrame:frame];
[view setBackgroundColor:[UIColor blueColor]];
// Add label for title
NSArray* titles = @[@"Title 1", @"Title 2", @"Title 3"];
NSString* selectedTitle = [titles objectAtIndex:section];
CGRect labelFrame = frame;
labelFrame.size.height = 30.f;
labelFrame.size.width -= 20.f;
labelFrame.Origin.x += 10.f;
UILabel* titleLabel = [[UILabel alloc] initWithFrame:labelFrame];
[titleLabel setText:selectedTitle];
[titleLabel setTextColor:[UIColor whiteColor]];
[view addSubview:titleLabel];
// Add touch gesture
[self attachTapGestureToView:view];
// Save created view to our class property array
[self saveSectionView:view inSection:section];
return view;
}
Ensuite, nous implémentons une méthode pour enregistrer notre en-tête de section personnalisé créé précédemment dans la propriété de classe:
- (void) saveSectionView:(UIView*) view inSection:(NSInteger) section
{
NSInteger sectionCount = [self numberOfSectionsInTableView:[self tblMain]];
if(section < sectionCount)
{
if([[self sectionViews] indexOfObject:view] == NSNotFound)
{
[[self sectionViews] addObject:view];
}
}
}
Ajoutez UIGestureRecognizerDelegate
à notre fichier .h de contrôleur de vue:
@interface MyViewController : UIViewController<UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate>
Ensuite, nous créons la méthode attachTapGestureToView:
- (void) attachTapGestureToView:(UIView*) view
{
UITapGestureRecognizer* tapAction = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
[tapAction setDelegate:self];
[view addGestureRecognizer:tapAction];
}
La méthode ci-dessus ajoute la reconnaissance des gestes tactiles à toutes les vues en coupe précédemment créées. Ensuite, nous devrions implémenter le sélecteur onTap:
- (void) onTap:(UITapGestureRecognizer*) gestureRecognizer
{
// Take view who attach current recognizer
UIView* sectionView = [gestureRecognizer view];
// [self sectionViews] is Array containing our custom section views
NSInteger section = [self sectionNumberOfView:sectionView];
// [self tblMain] is our connected IBOutlet table view
NSInteger sectionCount = [self numberOfSectionsInTableView:[self tblMain]];
// If section more than section count minus one set at last
section = section > (sectionCount - 1) ? 2 : section;
[self toggleCollapseSection:section];
}
La méthode ci-dessus sera invoquée lorsque l'utilisateur tapera sur l'une de nos sections de vue tableau. Cette méthode recherche le numéro de section correct en fonction de notre tableau sectionViews
que nous avons créé auparavant.
Nous implémentons également une méthode pour obtenir la section à laquelle appartient la vue en-tête.
- (NSInteger) sectionNumberOfView:(UIView*) view
{
UILabel* label = [[view subviews] objectAtIndex:0];
NSInteger sectionNum = 0;
for(UIView* sectionView in [self sectionViews])
{
UILabel* sectionLabel = [[sectionView subviews] objectAtIndex:0];
//NSLog(@"Section: %d -> %@ vs %@", sectionNum, [label text], [sectionLabel text]);
if([[label text] isEqualToString:[sectionLabel text]])
{
return sectionNum;
}
sectionNum++;
}
return NSNotFound;
}
Ensuite, nous devons implémenter la méthode toggleCollapseSection:
- (void) toggleCollapseSection:(NSInteger) section
{
if([self isCollapsedSection:section])
{
[self removeCollapsedSection:section];
}
else
{
[self addCollapsedSection:section];
}
[[self tblMain] reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade];
}
Cette méthode va insérer/supprimer le numéro de section dans notre tableau collapsedSections
que nous avons créé auparavant. Lorsqu'un numéro de section est inséré dans ce tableau, cela signifie que la section doit être réduite et développée dans le cas contraire.
Ensuite, nous implémentons removeCollapsedSection:
, addCollapsedSection:section
et isCollapsedSection:section
- (BOOL)isCollapsedSection:(NSInteger) section
{
for(NSNumber* existing in [self collapsedSections])
{
NSInteger current = [existing integerValue];
if(current == section)
{
return YES;
}
}
return NO;
}
- (void)removeCollapsedSection:(NSInteger) section
{
[[self collapsedSections] removeObjectIdenticalTo:[NSNumber numberWithInteger:section]];
}
- (void)addCollapsedSection:(NSInteger) section
{
[[self collapsedSections] addObject:[NSNumber numberWithInteger:section]];
}
Ces trois méthodes sont juste des aides pour nous faciliter l’accès au tableau collapsedSections
.
Enfin, implémentez ce délégué de vue de table pour que nos vues de section personnalisées aient l’air agréable.
- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 30.f; // Same as each custom section view height
}
J'espère que ça aide.
J'ai utilisé un NSDictionary comme source de données, cela ressemble à beaucoup de code, mais c'est très simple et fonctionne très bien! comment ça se trouve ici
J'ai créé un enum pour les sections
typedef NS_ENUM(NSUInteger, TableViewSection) {
TableViewSection0 = 0,
TableViewSection1,
TableViewSection2,
TableViewSectionCount
};
sections de propriété:
@property (nonatomic, strong) NSMutableDictionary * sectionsDisctionary;
Une méthode retournant mes sections:
-(NSArray <NSNumber *> * )sections{
return @[@(TableViewSection0), @(TableViewSection1), @(TableViewSection2)];
}
Et puis configurer mes données soruce:
-(void)loadAndSetupData{
self.sectionsDisctionary = [NSMutableDictionary dictionary];
NSArray * sections = [self sections];
for (NSNumber * section in sections) {
NSArray * sectionObjects = [self objectsForSection:section.integerValue];
[self.sectionsDisctionary setObject:[NSMutableDictionary dictionaryWithDictionary:@{@"visible" : @YES, @"objects" : sectionObjects}] forKey:section];
}
}
-(NSArray *)objectsForSection:(NSInteger)section{
NSArray * objects;
switch (section) {
case TableViewSection0:
objects = @[] // objects for section 0;
break;
case TableViewSection1:
objects = @[] // objects for section 1;
break;
case TableViewSection2:
objects = @[] // objects for section 2;
break;
default:
break;
}
return objects;
}
Les méthodes suivantes vous aideront à savoir quand une section est ouverte et comment répondre à la source de données tableview:
Répondez la section à la source de données:
/**
* Asks the delegate for a view object to display in the header of the specified section of the table view.
*
* @param tableView The table-view object asking for the view object.
* @param section An index number identifying a section of tableView .
*
* @return A view object to be displayed in the header of section .
*/
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
NSString * headerName = [self titleForSection:section];
YourCustomSectionHeaderClass * header = (YourCustomSectionHeaderClass *)[tableView dequeueReusableHeaderFooterViewWithIdentifier:YourCustomSectionHeaderClassIdentifier];
[header setTag:section];
[header addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]];
header.title = headerName;
header.collapsed = [self sectionIsOpened:section];
return header;
}
/**
* Asks the data source to return the number of sections in the table view
*
* @param An object representing the table view requesting this information.
* @return The number of sections in tableView.
*/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
// Return the number of sections.
return self.sectionsDisctionary.count;
}
/**
* Tells the data source to return the number of rows in a given section of a table view
*
* @param tableView: The table-view object requesting this information.
* @param section: An index number identifying a section in tableView.
* @return The number of rows in section.
*/
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
BOOL sectionOpened = [self sectionIsOpened:section];
return sectionOpened ? [[self objectsForSection:section] count] : 0;
}
Outils:
/**
Return the section at the given index
@param index the index
@return The section in the given index
*/
-(NSMutableDictionary *)sectionAtIndex:(NSInteger)index{
NSString * asectionKey = [self.sectionsDisctionary.allKeys objectAtIndex:index];
return [self.sectionsDisctionary objectForKey:asectionKey];
}
/**
Check if a section is currently opened
@param section the section to check
@return YES if is opened
*/
-(BOOL)sectionIsOpened:(NSInteger)section{
NSDictionary * asection = [self sectionAtIndex:section];
BOOL sectionOpened = [[asection objectForKey:@"visible"] boolValue];
return sectionOpened;
}
/**
Handle the section tap
@param tap the UITapGestureRecognizer
*/
- (void)handleTapGesture:(UITapGestureRecognizer*)tap{
NSInteger index = tap.view.tag;
[self toggleSection:index];
}
Basculer la visibilité de la section
/**
Switch the state of the section at the given section number
@param section the section number
*/
-(void)toggleSection:(NSInteger)section{
if (index >= 0){
NSMutableDictionary * asection = [self sectionAtIndex:section];
[asection setObject:@(![self sectionIsOpened:section]) forKey:@"visible"];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade];
}
}
J'ajoute cette solution pour qu'elle soit complète et montre comment utiliser les en-têtes de section.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet var tableView: UITableView!
var headerButtons: [UIButton]!
var sections = [true, true, true]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
let section0Button = UIButton(type: .detailDisclosure)
section0Button.setTitle("Section 0", for: .normal)
section0Button.addTarget(self, action: #selector(section0Tapped), for: .touchUpInside)
let section1Button = UIButton(type: .detailDisclosure)
section1Button.setTitle("Section 1", for: .normal)
section1Button.addTarget(self, action: #selector(section1Tapped), for: .touchUpInside)
let section2Button = UIButton(type: .detailDisclosure)
section2Button.setTitle("Section 2", for: .normal)
section2Button.addTarget(self, action: #selector(section2Tapped), for: .touchUpInside)
headerButtons = [UIButton]()
headerButtons.append(section0Button)
headerButtons.append(section1Button)
headerButtons.append(section2Button)
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section] ? 3 : 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellReuseId = "cellReuseId"
let cell = UITableViewCell(style: .default, reuseIdentifier: cellReuseId)
cell.textLabel?.text = "\(indexPath.section): \(indexPath.row)"
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return headerButtons[section]
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
@objc func section0Tapped() {
sections[0] = !sections[0]
tableView.reloadSections([0], with: .fade)
}
@objc func section1Tapped() {
sections[1] = !sections[1]
tableView.reloadSections([1], with: .fade)
}
@objc func section2Tapped() {
sections[2] = !sections[2]
tableView.reloadSections([2], with: .fade)
}
}
Lien vers Gist: https://Gist.github.com/pawelkijowskizimperium/fe1e8511a7932a0d40486a2669316d2c
pour soutenir la solution @ jean.timex, utilisez le code ci-dessous si vous souhaitez ouvrir une section à tout moment. créez une variable comme: var expandSection = -1;
func toggleSection(_ header: CollapsibleTableViewHeader, section: Int) {
let collapsed = !sections[section].collapsed
// Toggle collapse
sections[section].collapsed = collapsed
header.setCollapsed(collapsed)
tableView.reloadSections(NSIndexSet(index: section) as IndexSet, with: .automatic)
if (expandedSection >= 0 && expandedSection != section){
sections[expandedSection].collapsed = true
tableView.reloadSections(NSIndexSet(index: expandedSection) as IndexSet, with: .automatic)
}
expandedSection = section;
}
// -------------------------------------------------------------------------------
// tableView:viewForHeaderInSection:
// -------------------------------------------------------------------------------
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *mView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 20, 20)];
[mView setBackgroundColor:[UIColor greenColor]];
UIImageView *logoView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 5, 20, 20)];
[logoView setImage:[UIImage imageNamed:@"carat.png"]];
[mView addSubview:logoView];
UIButton *bt = [UIButton buttonWithType:UIButtonTypeCustom];
[bt setFrame:CGRectMake(0, 0, 150, 30)];
[bt setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[bt setTag:section];
[bt.titleLabel setFont:[UIFont systemFontOfSize:20]];
[bt.titleLabel setTextAlignment:NSTextAlignmentCenter];
[bt.titleLabel setTextColor:[UIColor blackColor]];
[bt setTitle: @"More Info" forState: UIControlStateNormal];
[bt addTarget:self action:@selector(addCell:) forControlEvents:UIControlEventTouchUpInside];
[mView addSubview:bt];
return mView;
}
#pragma mark - Suppose you want to hide/show section 2... then
#pragma mark add or remove the section on toggle the section header for more info
- (void)addCell:(UIButton *)bt{
// If section of more information
if(bt.tag == 2) {
// Initially more info is close, if more info is open
if(ifOpen) {
DLog(@"close More info");
// Set height of section
heightOfSection = 0.0f;
// Reset the parameter that more info is closed now
ifOpen = NO;
}else {
// Set height of section
heightOfSection = 45.0f;
// Reset the parameter that more info is closed now
DLog(@"open more info again");
ifOpen = YES;
}
//[self.tableView reloadData];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:2] withRowAnimation:UITableViewRowAnimationFade];
}
}// end addCell
#pragma mark -
#pragma mark What will be the height of the section, Make it dynamic
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.section == 2) {
return heightOfSection;
}else {
return 45.0f;
}
// vKj
This action will happen in your didSelectRowAtIndexPath, when you will try to hide or show number of cell in a section
first of all declare a global variable numberOfSectionInMoreInfo in .h file and in your viewDidLoad set suppose to numberOfSectionInMoreInfo = 4.
Now use following logic:
// More info link
if(row == 3) {
/*Logic: We are trying to hide/show the number of row into more information section */
NSString *log= [NSString stringWithFormat:@"Number of section in more %i",numberOfSectionInMoreInfo];
[objSpineCustomProtocol showAlertMessage:log];
// Check if the number of rows are open or close in view
if(numberOfSectionInMoreInfo > 4) {
// close the more info toggle
numberOfSectionInMoreInfo = 4;
}else {
// Open more info toggle
numberOfSectionInMoreInfo = 9;
}
//reload this section
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
// vKj
En développant this réponse écrite en Objective C, j’écris ce qui suit pour ceux qui écrivent en Swift
L'idée est d'utiliser des sections dans la table et de définir le nombre de lignes de la section sur 1 (réduit) et 3(expanded) lorsque la première ligne de cette section est exploitée.
La table décide du nombre de lignes à dessiner en fonction d'un tableau de valeurs booléennes.
Vous devez créer deux lignes dans le storyboard et leur attribuer les identificateurs de réutilisation 'CollapsingRow' et 'GroupHeading'.
import UIKit
class CollapsingTVC:UITableViewController{
var sectionVisibilityArray:[Bool]!// Array index corresponds to section in table
override func viewDidLoad(){
super.viewDidLoad()
sectionVisibilityArray = [false,false,false]
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func numberOfSections(in tableView: UITableView) -> Int{
return sectionVisibilityArray.count
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat{
return 0
}
// numberOfRowsInSection - Get count of entries
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rowsToShow:Int = 0
if(sectionVisibilityArray[section]){
rowsToShow = 3 // Or however many rows should be displayed in that section
}else{
rowsToShow = 1
}
return rowsToShow
}// numberOfRowsInSection
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
if(indexPath.row == 0){
if(sectionVisibilityArray[indexPath.section]){
sectionVisibilityArray[indexPath.section] = false
}else{
sectionVisibilityArray[indexPath.section] = true
}
self.tableView.reloadSections([indexPath.section], with: .automatic)
}
}
// cellForRowAtIndexPath - Get table cell corresponding to this IndexPath
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell
if(indexPath.row == 0){
cell = tableView.dequeueReusableCell(withIdentifier: "GroupHeading", for: indexPath as IndexPath)
}else{
cell = tableView.dequeueReusableCell(withIdentifier: "CollapsingRow", for: indexPath as IndexPath)
}
return cell
}// cellForRowAtIndexPath
}
J'ai fait la même chose en utilisant plusieurs sections.
class SCTierBenefitsViewController: UIViewController {
@IBOutlet private weak var tblTierBenefits: UITableView!
private var selectedIndexPath: IndexPath?
private var isSelected:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
tblTierBenefits.register(UINib(nibName:"TierBenefitsTableViewCell", bundle: nil), forCellReuseIdentifier:"TierBenefitsTableViewCell")
tblTierBenefits.register(UINib(nibName:"TierBenefitsDetailsCell", bundle: nil), forCellReuseIdentifier:"TierBenefitsDetailsCell")
tblTierBenefits.rowHeight = UITableViewAutomaticDimension;
tblTierBenefits.estimatedRowHeight = 44.0;
tblTierBenefits.tableFooterView = UIView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
extension SCTierBenefitsViewController : UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 7
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (isSelected && section == selectedIndexPath?.section) ? 2 : 1
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0.01
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell:TierBenefitsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "TierBenefitsTableViewCell")! as! TierBenefitsTableViewCell
cell.selectionStyle = .none
cell.contentView.setNeedsLayout()
cell.contentView.layoutIfNeeded()
return cell
case 1:
let cell:TierBenefitsDetailsCell = tableView.dequeueReusableCell(withIdentifier: "TierBenefitsDetailsCell")! as! TierBenefitsDetailsCell
cell.selectionStyle = .none
return cell
default:
break
}
return UITableViewCell()
}
}
extension SCTierBenefitsViewController : UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
if let _selectedIndexPath = selectedIndexPath ,selectedIndexPath?.section == indexPath.section {
tblTierBenefits.beginUpdates()
expandCollapse(indexPath: _selectedIndexPath, isExpand: false)
selectedIndexPath = nil
}
else{
tblTierBenefits.beginUpdates()
if selectedIndexPath != nil {
tblTierBenefits.reloadSections([(selectedIndexPath?.section)!], with: .none)
}
expandCollapse(indexPath: indexPath, isExpand: true)
}
}
}
private func expandCollapse(indexPath: IndexPath?,isExpand: Bool){
isSelected = isExpand
selectedIndexPath = indexPath
tblTierBenefits.reloadSections([(indexPath?.section)!], with: .none)
tblTierBenefits.endUpdates()
}
}