【问题标题】: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 的内容

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

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

【问题讨论】:

    标签: ios objective-c uitableview


    【解决方案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];
      }  
      

      祝你好运。

      【讨论】:

        猜你喜欢
        • 2016-08-23
        • 1970-01-01
        • 1970-01-01
        • 2018-07-31
        • 1970-01-01
        • 1970-01-01
        • 2012-04-05
        • 1970-01-01
        相关资源
        最近更新 更多