展开和折叠所有表格视图单元格

Posted

技术标签:

【中文标题】展开和折叠所有表格视图单元格【英文标题】:Expand and Collapse All Table View Cells 【发布时间】:2014-12-21 19:19:15 【问题描述】:

我需要一些 UI 建议。

我有一个视图需要加载如下格式的数据:

    
      "heading": "This is a header",
      "content": "This is some detailed content about the header"
    ,
    
      "heading": "This is another headline.",
      "content": " These are more details about the headline. "
    

以下是参数: 在加载时,它应该只在表格视图中显示标题。点击标题将展开该单元格并加载有关它的内容或详细信息。这是一个粗略的草图:

加载时:

标题 1 标题 2 标题 3 标题 4

点击标题 2:

标题 1 标题 2 此处显示标题 2 的内容 标题 3 标题 4

还需要一个条形按钮项,用于展开或折叠所有单元格。哪个会这样:

全部折叠:

标题 1 标题 2 标题 3 标题 4

全部展开:

标题 1 标题 1 的内容显示在此处 标题 2 此处显示标题 2 的内容 标题 3 此处显示标题 3 的内容 标题 4 此处显示标题 4 的内容

我使用了一些奇怪的父/子逻辑来扩展单个单元格,但我认为我走上了一条黑暗的道路,因为现在我正在尝试实现全部展开/折叠并且我被卡住了。

有谁知道执行这种手风琴表格视图的任何开源代码和/或关于如何设置视图控制器来执行此操作的任何建议?我已经看到一些库扩展和折叠单个单元格,但是能够完成所有这些变得越来越棘手。

【问题讨论】:

【参考方案1】:

这就是我的做法,也许稍微简单一些,但绝对是类似的方法:)

#import "ViewController.h"

//dont worry, the header is empty except for import <UIKit/UIKit.h>, this is a subclass on UIViewController

@interface ViewController ()<UITableViewDataSource, UITableViewDelegate>

@property (weak, nonatomic) UITableView *tableView;


@end

@implementation ViewController

//ivars
  BOOL sectionIsOpen[4]; //we will use this BOOL array to keep track of the open/closed state for each section.  Obviously I have the number of sections fixed at 4 here, but you could make a more dynamic array with malloc() if neccesary..




- (void)viewDidLoad 
  [super viewDidLoad];

  UITableView *tv = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
  tv.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  tv.dataSource = self;
  tv.delegate = self;

  [self.view addSubview:tv];
  self.tableView = tv;

  // Do any additional setup after loading the view, typically from a nib.


#pragma mark - UITableViewDataSource
-(NSInteger )numberOfSectionsInTableView:(UITableView *)tableView
  return 4;


-(NSInteger )tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  return ((sectionIsOpen[section]) ? [self numberOfRowsInSection:section] : 0);

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section

  //put your switch() here...

  return [NSString stringWithFormat:@"I am section %i", (int)section ];

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

  static NSString *cellId = @"cellID";

  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];

  if (!cell) 
    cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellId];
  


 //etc etc decorate your cell...
  cell.textLabel.text = [NSString stringWithFormat:@"cell %i / %i", (int)indexPath.section, (int)indexPath.row ];


  return cell;

#pragma mark - UITableViewDelegate
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section

  const CGRect fr = CGRectMake(0, 0, 320.0, 40.0 );

  UIButton *btn = [[UIButton alloc]initWithFrame:fr];
  [btn setTitle:[self tableView:tableView titleForHeaderInSection:section] forState:UIControlStateNormal ];
  [btn setTag:section];
  [btn addTarget:self action:@selector(sectionOpenToggle:) forControlEvents:UIControlEventTouchUpInside];


  // add an image, colour etc if you like

  return btn;




#pragma mark - tableViewHelpers

//the number of rows in sectionX when it is open...
-(NSInteger )numberOfRowsInSection:(NSInteger )section

  return section + 1;


//opening/closing a section
-(void )setSection:(NSInteger )section toOpen:(BOOL )open

  if (open != sectionIsOpen[section]) 

//build an array of indexPath objects
    NSMutableArray *indxPths = [NSMutableArray array];
    for (NSInteger i = 0; i < [self numberOfRowsInSection:section]; i ++) 

      [indxPths addObject: [NSIndexPath indexPathForRow:i inSection:section ]
       ];

    


    [self.tableView beginUpdates];

    if (open) 
      [self.tableView insertRowsAtIndexPaths:indxPths withRowAnimation:UITableViewRowAnimationFade];
      //nb there is a large ENUM of tableViewRowAnimation types to experiment with..

    else
      [self.tableView deleteRowsAtIndexPaths:indxPths withRowAnimation:UITableViewRowAnimationFade];

    
    sectionIsOpen[section] = open;
    [self.tableView endUpdates];

  


-(void )sectionOpenToggle:(id )sender
  [self setSection:[sender tag] toOpen: !sectionIsOpen[[sender tag]] ];


// open/close all sections.
-(void )setAllSectionsOpen:(BOOL )open

  for (NSInteger i = 0; i < [self numberOfSectionsInTableView:self.tableView]; i ++) 
    [self setSection:i toOpen:open];
  



//these two for your convenience, hook up to navbar items etc..
-(IBAction)openAllSections:(id)sender
  [self setAllSectionsOpen:YES];

-(IBAction)closeAllSections:(id)sender
  [self setAllSectionsOpen:NO];

@end

【讨论】:

【参考方案2】:

我不是以英语为母语的人,所以如果我的某些解释不够清楚,请告诉我,我会尝试改写它们。

无论如何,可能是更好的方法,但这只是我的想法, 下面的代码可能看起来很吓人,很复杂,但我个人认为真的是 直截了当, 我刚刚加了很多cmets,解释了每一步,所以代码其实不像 乍一看可能会显得冗长/复杂/凌乱。

根据您上面的示例,在我看来,您的数据源是一个 NSArray,其中包含 NSDictionary 对象, 并且每个NSDictionary 包含一个标题(部分),并且该部分只有一个内容(行), 因此,下面的示例设置为仅处理 - 一个表格视图,具有多个部分,每个部分有一行, 其数据源为NSArray,其中包含NSDictionary对象。

由于我不知道您当前的数据源是否可变,因此在我的示例中,我将首先创建它的可变副本,并将在整个代码中使用它。

在整个代码中,我假设self.tableView 是您的表格视图,self.dataArray 是您在上面发布的字典数组, 我还假设您已经在代码或情节提要中将表格视图的delegatedataSource 设置为self

// Here we define the height of each header,  
// I've arbitrarily chosen 50 points. You can change it as you like.  
// The reason I've declare it like this, is that I'm using its  
// height to also create a UIView for the header, so this way  
// if you want to change the height, you need to only change it once.
#define kHeaderHeight 50

-(void)viewDidLoad   
    ...  
    ...  
    // The method below will be called to create an mutable copies of the  
    // dictionaries in your data source, plus add them another object  
    // which will indicate in our code if the correspond header  
    // should be expanded or collapsed  
    [self createDataSource];  

    // The below line is not mandatory, but I personally like to add it  
    // So collapsed sections won't have the row's 'bounds' under them.
    self.tableView.tableFooterView = [[UIView alloc] initWithFrame: CGRectZero];  
    ...  
    ...  
  

-(void)createDataSource   
    // Here we are basically going to create a temporary mutable array,
    // then we are going to iterate through self.dataArray array,  
    // Make a mutable copy of every dictionary in it, Add a BOOL value  
    // it that indicates if the row is expanded or not, add the new mutable  
    // dictionary to the temporary array, and then make self.dataArray  
    // point to an immutable copy of the new array we've created  

    NSMutableArray *array = [[NSMutableArray alloc] init];

    for(int i = 0; i < [self.dataArray count]; i++)   
        NSMutableDictionary *dict = [self.dataArray[i] mutableCopy];  
        [dict setObject:@(NO) forKey:@"expanded"];
        [array addObject:dict];  
      

    self.dataArray = [array copy];  
  

// Now we will set the height of each header.  
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section   
    return kHeaderHeight;  
  

// Here we create a custom view for the header, so we can make its title clickable,  
// That will expand/collapse each section
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section   
    // Here we are creating a view for the header, and use our defined header  
    // height to set its height appropriately.  
    UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kHeaderHeight)];  
    // Then create a button  
    // I've arbitrarily chosen a size of 100x20 and created a frame to be placed in the  
    // middle of the above header
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(header.frame.size.width/2 - 50, header.frame.size.height/2 - 10, 100, 20)];  

    NSString *headerTitle = [self.dataArray[section] objectForKey:@"heading"];
    [button setTitle:headerTitle forState:UIControlStateNormal];  

    // We set the button tag to correspond to its section for future use
    button.tag = section;  

    // I've arbitrarily chose to set the button colour to gray colour  
    button.titleLabel.textColor = [UIColor grayColor];

    // Then we need to actually add an action to the button  
    [button addTarget:self action:@selector(updateTableView:) forControlEvents:UIControlEventTouchUpInside];  
    // Then we need to add the button to the header view itself  
    [header addSubview:button];  

    return header;  
  

// Here we are just setting the number of sections to correspond to the  
// number of items we have in our data array
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
    return [self.dataArray count];
  

// Here we are finally using the BOOl value we've added to our dictionary at the  
// beginning of our code. If the "expanded" BOOL value is YES, return 1 row,  
// else, the section is collapsed, so return 0 rows
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
    if([[self.dataArray[section] objectForKey:@"expanded"] boolValue]) 
        return 1;
     else 
        return 0;
    
  

// Here is just a simple method to create the cells that will use our dataArray  
// as their source for their title  
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

    NSString *identifier = @"cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    if(!cell) 
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    

    cell.textLabel.text = [self.dataArray[indexPath.row] objectForKey:@"content"];

    return cell;
  

// This method being called when clicking on the table view's headers  
// it uses the button tag we've set earlier, to determine which section we're trying  
// to collape/expand, it retrieves the current "expanded" bool value, and then store  
// the opposite value back in our dictionary (so if table was collapsed, meaning  
// its "expanded" value is NO, after this will run, it will be "YES", which  
// will be evaluated after our next line, which tells the table view to reload  
// this specific section).  
// NOTE that while our dataArray is immutable array, meaning we can't modify it,  
// it points to an mutable dictionaries, so we have no problem modifying them.
-(void)updateTableView:(UIButton *)sender 
    BOOL expanded = [[self.dataArray[sender.tag] objectForKey:@"expanded"] boolValue];
    [self.dataArray[sender.tag] setObject:@(!expanded) forKey:@"expanded"];

    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:sender.tag] withRowAnimation:UITableViewRowAnimationFade];


// Connect the below methods to your collapse/expand all buttons.  
// What the below method actually does, is iterating thru all of the dictionaries  
// in dataArray, changes their value to be corresponding to what we are trying to  
// do (expand or collapse all) and then calling reloadSections:withRowAnimation:  
// on our table view.  
// You might wonder why I just don't call reloadData.  
// Although it will do the job, I like the following more, because I personally  
// feel it gives more 'smooth' update of the UI
-(void)expandAll:(UIButton *)sender 

    for(int i = 0; i < [self.dataArray count]; i++) 
        [self.dataArray[i] setObject:@(YES) forKey:@"expanded"];
    

    [self.tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.dataArray.count)] withRowAnimation:UITableViewRowAnimationFade];
  

-(void)collapseAll:(UIButton *)sender 

    for(int i = 0; i < [self.dataArray count]; i++) 
        [self.dataArray[i] setObject:@(NO) forKey:@"expanded"];
    

    [self.tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.dataArray.count)] withRowAnimation:UITableViewRowAnimationFade];
  

祝你好运。

【讨论】:

以上是关于展开和折叠所有表格视图单元格的主要内容,如果未能解决你的问题,请参考以下文章

展开和折叠表格视图单元格

展开和折叠表格视图中的单元格

我们如何快速在表格视图底部添加展开和折叠单元格

通过使用约束自行调整大小来展开和折叠 TableView 单元格

Accordion 表格单元格 - 用于展开和折叠 ios

UITableviewCell 高度未在滚动时重置