2009-12-21 3 views
103

Может ли кто-нибудь сказать мне способ выполнить UITableView расширяемые/сбрасываемые анимации в sections из UITableView, как показано ниже?Развернуть/свернуть раздел в UITableView в iOS

или

+0

Попробуйте следующее: http://stackoverflow.com/questions/33186659/how-to-create-drop-down-list-in-uitableview-in-ios –

+0

[Отметьте мой ответ так же для пользовательского заголовка ] (http://stackoverflow.com/questions/18203434/uitableview-with-open-closed-sections/40932319#40932319) http://stackoverflow.com/questions/18203434/uitableview-with-open-closed-sections/40932319 # 40932319 –

ответ

104

Я уверен, что вы должны просто сделать свой собственный ряд пользовательских заголовков и просто положить, что в качестве первой строки каждого раздела. Подклассификация UITableView или заголовков, которые сейчас существуют, вероятно, будет огромной болью, и я не уверен, что вы можете легко получить от них действия так, как они работают сейчас. Вы можете легко настроить ячейку для просмотра как заголовок и установить tableView:didSelectRowAtIndexPath, чтобы развернуть или свернуть раздел, который он находится в ручном режиме.

Если бы я был вами, я бы сохранил массив булевых, соответствующий «затраченному» значению каждого из ваших разделов. Затем вы можете установить tableView:didSelectRowAtIndexPath в каждой из ваших собственных строк заголовков, чтобы переключить это значение, а затем перезагрузить этот конкретный раздел.

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

Вы бы затем настроить ваш номер numberOfRowsInSection проверить значение mybooleans и возвращать либо 1, если раздел не расширен, или 1+ количество элементов в секции, если она расширяется.

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

Вы также должны обновить cellForRowAtIndexPath вернуть ячейку пользовательского заголовка для первой строки в любой секции.

+2

, если вы использовали приложение Beejive, вы бы знали, что их сводный заголовок раздела фактически «плавает» в верхней части таблицы, даже когда вы прокручиваете часть своего раздела, точно так же, как обычные заголовки разделов Apple , это невозможно, если вы просто добавите ячейку в начале раздела. – user102008

+0

Приятное элегантное решение!user102008 имеет точку в плавающем заголовке, но в сценарии, где вы действительно хотите прокручивать «разделы», это отличный подход. –

+0

@mjdth plz дайте мне какой-нибудь пример кода bcz Мне нужна конкретная ячейка hide/unhide ..thanks заранее – Bajaj

10

У меня есть лучшее решение, что вы должны добавить UIButton в заголовок раздела и установить размер этой кнопки, равный размеру раздела, но скрыть его прозрачным цветом фона, после чего вам будет легко проверить, какой раздел щелкнуть, чтобы развернуть или коллапс

+3

На мой взгляд, это решение лучше, чем принятый ответ, потому что семантически вы сохраняете заголовок как заголовок, и вы не используете фальшивую строку для имитации заголовка. Метод 'tableView: numberOfRowsInSection:' будет не затронут, и вы будете продолжать использовать его для того, что он на самом деле означает. То же самое касается 'tableView: cellForRowAtIndexPath:'. –

+0

Итак, вы нажимаете кнопку в заголовке раздела, но как вы собираетесь определять, какой раздел нужно перезагрузить? – memmons

+0

@Answerbot Привет, Это очень легко, установив тег для кнопки, используя то же значение с индексом раздела. –

100

Некоторые примеры кода для анимации в расширении/действие коллапса с помощью заголовка таблицы вид сечения обеспечивается Apple, здесь: Table View Animations and Gestures

ключ к этому подходу к реализации - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section и возвращать пользовательские UIView, который включает в себя (обычно того же размера, что и представление заголовка). Подклассифицируя UIView и используя это для представления заголовка (как это делает этот пример), вы можете легко сохранить дополнительные данные, такие как номер раздела.

+28

Фантастический, спасибо: http://developer.apple.com/library/ios/#samplecode/TableViewUpdates/Introduction /Intro.html – EightyEight

+0

есть ли образец кода, который будет работать на pre-iOS 4? – user102008

+0

не помню, но почему код примера не работает на pre-iOS 4? – samwize

5

Это лучший способ я нашел, чтобы создать развернутое представление таблицы ячеек

.h файл

NSMutableIndexSet *expandedSections; 

.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]; 

Таблица методов зрения делегата

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

} 
+7

Вероятно, вы должны указывать вопросы как точные дубликаты, а не просто рассылать один и тот же ответ на все из них. – casperOne

+0

Если раздел уже расширен, а другой раздел щелкнут, он дает ошибку – shivam

+0

привет сэр, выбранный указатель hight, как изменить? HeightForRowAtIndexPath, как работать с вашим кодом? –

6

I ende d только создав headerView, в котором была кнопка (я увидел Son Nguyen's solution выше, после этого, но heres мой код ..это выглядит как много, но это довольно просто):

объявить пару Bools для вас секций

bool customerIsCollapsed = NO; 
bool siteIsCollapsed = NO; 

... код

теперь в ваших методов делегата 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; 
} 

и, наконец, функция, вызываемая при нажатии одной из кнопок заголовка раздела:

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

Мне просто интересно, пусть раздел рухнет и расширится анимированным или без анимации. Без анимации это будет выглядеть очень плохо. как мы можем добавить к нему анимацию? – Sam

+0

@Sam, если вы используете что-то вроде '[self.tableView reloadSections: [NSIndexSet indexSetWithIndex: 0] withRowAnimation: UITableViewRowAnimationFade];' в методе коллапса/разворота он должен анимировать красиво. –

1

Таким образом, на основе решения «кнопка в заголовке», здесь чистая и минималистская реализация:

  • вам следить за рухнувшие (или расширенные) разделы в свойстве
  • Пометив кнопку с индексом
  • разделе вы установите выбранное состояние на эту кнопку, чтобы изменить направление стрелки (как △ и ▽)

Вот код:

@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 
0
// ------------------------------------------------------------------------------- 
// 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

0
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

+0

Почему два ответа? Кажется, вы не представили два разных решения проблемы. – Cristik

1

я нашел еще один относительно простой способ решить эту проблему. Используя этот метод, нам не потребуется изменять нашу ячейку, которая почти всегда связана с индексом массива данных, что потенциально вызывает беспорядок в нашем контроллере представления.

Во-первых, мы добавим эти следующие свойства класса нашего контроллера:

@property (strong, nonatomic) NSMutableArray* collapsedSections; 
@property (strong, nonatomic) NSMutableArray* sectionViews; 

collapsedSections сохранит свернутые номера раздела. sectionViews сохранит наш пользовательский раздел.

Синтезировать его:

@synthesize collapsedSections; 
@synthesize sectionViews; 

Инициализировать его:

- (void) viewDidLoad 
{ 
    [super viewDidLoad]; 

    self.collapsedSections = [NSMutableArray array]; 
    self.sectionViews  = [NSMutableArray array]; 
} 

После этого, мы должны соединить наш UITableView, так что могут быть доступны из нашего класса контроллера представления:

@property (strong, nonatomic) IBOutlet UITableView *tblMain; 

Подключите его к XIB для просмотра контроллера с помощью ctrl + drag, как обычно.

Затем мы создаем вид, как пользовательский раздел заголовок для нашего представления таблицы путем выполнения этого 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; 
} 

Далее, мы реализуем метод, чтобы сохранить наш созданный ранее пользовательский заголовок раздела в классе собственности:

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

Добавить UIGestureRecognizerDelegate в наш контроллер представления .h файл:

@interface MyViewController : UIViewController<UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate> 

Затем мы создаем метод attachTapGestureToView:

- (void) attachTapGestureToView:(UIView*) view 
{ 
    UITapGestureRecognizer* tapAction = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)]; 

    [tapAction setDelegate:self]; 

    [view addGestureRecognizer:tapAction]; 
} 

Выше метод добавит водопроводная жест распознаватель всем видом раздела мы создали ранее. Далее мы должны реализовать 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]; 
} 

Выше метод будет вызываться, когда пользователь нажмите любую из нашей точки зрения таблицы раздела. Этот метод ищет правильный номер раздела, основанный на нашем массиве sectionViews, который мы создали ранее.

Кроме того, мы реализуем метод получения раздела wihch заголовка.

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

Далее, мы должны реализовать метод toggleCollapseSection:

- (void) toggleCollapseSection:(NSInteger) section 
{ 
    if([self isCollapsedSection:section]) 
    { 
     [self removeCollapsedSection:section]; 
    } 
    else 
    { 
     [self addCollapsedSection:section]; 
    } 

    [[self tblMain] reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade]; 
} 

Этот метод будет вставить/удалить номер раздела нашего collapsedSections массива, который мы создали ранее. Когда номер раздела вставлен в этот массив, это означает, что раздел должен быть свернут и расширен, если в противном случае.

Далее мы реализуем removeCollapsedSection:, addCollapsedSection:section и 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]]; 
} 

Это три методы является только помощниками, чтобы сделать нам проще в обращении collapsedSections массива.

Наконец, реализуйте этот делегат представления таблицы, чтобы наши пользовательские виды разделов выглядели красиво.

- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section 
{ 
    return 30.f; // Same as each custom section view height 
} 

Надеюсь, это поможет.

20

У меня есть хорошее решение, вдохновленное Apple Table View Animations and Gestures. Я удалил ненужные части из образца Apple и перевел его в быстрый.

Я знаю, что ответ довольно длинный, но необходим весь код. К счастью, вы можете просто скопировать & мимо большей части кода и просто нужно сделать небольшую модификацию на шагах 1 и 3

1.создать SectionHeaderView.swift и 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) 
    } 

} 

SectionHeaderView.xib (вид с серым фоном) должно выглядеть примерно так в Tableview (вы можете настроить его в соответствии с вашими потребностями, конечно): enter image description here

примечание:

а) toggleOpen действие должно быть связано с disclosureButton

б) disclosureButton и toggleOpen действие не требуется. Вы можете удалить эти две вещи, если вам не нужна кнопка.

2.Create

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.in вашего 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) 
     } 
    } 
} 
+1

спасибо за то, что вы сделали это! С небольшим примером проекта на github было бы еще лучше ответить –

+0

Спасибо за предоставление подробного ответа. Пример проекта будет лучше. –

8

Для осуществления раздела складных таблиц в прошивке, магия, как контролировать количество строк для каждой секции, или мы можем управлять высотой строк для каждого раздела.

Кроме того, нам нужно настроить заголовок раздела, чтобы мы могли прослушивать событие касания из области заголовка (будь то кнопка или весь заголовок).

Как бороться с заголовком? Это очень просто, мы расширим класс UITableViewCell и сделать пользовательскую ячейку заголовка следующим образом:

import UIKit 

class CollapsibleTableViewHeader: UITableViewCell { 

    @IBOutlet var titleLabel: UILabel! 
    @IBOutlet var toggleButton: UIButton! 

} 

затем использовать viewForHeaderInSection подключить ячейку заголовка:

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 
} 

помнить, что мы должны вернуть contentView потому что эта функция ожидает возвращения UIView.

Теперь давайте разбираться с разборной части, здесь есть функция переключения, которая переключает складную опору каждой секции:

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

зависит от того, как управлять данными раздела, в этом случае, у меня есть раздел что-то данные, как это:

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"]) 
] 

наконец, что нам нужно сделать, это на основе разборной опоры каждой секции, контролировать количество строк этого раздела:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
    return (sections[section].collapsed!) ? 0 : sections[section].items.count 
} 

У меня есть полностью рабочий пример на моем Github: https://github.com/jeantimex/ios-swift-collapsible-table-section

demo

Если вы хотите реализовать сборно-разборные секции в таблице сгруппированы в стиле, у меня есть еще один демо с исходным кодом здесь: https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section

Надеюсь, что это поможет.

+0

Привет, Я сделал свой собственный раздел заголовка в файле xib и зарегистрировал nib для моего Table View Controller. Когда я удаляю раздел и пытаюсь развернуть/свернуть снова, я получаю фатальную ошибку, указав, что индекс выходит за пределы диапазона. Есть ли способ исправить это? Благодаря! – iamhx

+0

Очень хорошее и чистое решение! – Joel

0

Некоторые примеры кода для анимации в расширении/действие коллапса с помощью заголовка вид раздела таблицы обеспечивается Apple, здесь: Table View анимаций и Жесты

Ключ к этому подходу к реализации - (UIView *) Tableview: (UITableView *) tableView viewForHeaderInSection: (NSInteger) и возвращает пользовательский UIView, который включает в себя кнопку (обычно такой же размер, как и заголовок). Подклассифицируя UIView и используя это для представления заголовка (как это делает этот пример), вы можете легко сохранить дополнительные данные, такие как номер раздела.

1

Я использовал NSDictionary как источник данных, это выглядит как много кода, но это очень просто и работает очень хорошо! how looks here

Я создал перечисление для секций

typedef NS_ENUM(NSUInteger, TableViewSection) { 

    TableViewSection0 = 0, 
    TableViewSection1, 
    TableViewSection2, 
    TableViewSectionCount 
}; 

участки недвижимость:

@property (nonatomic, strong) NSMutableDictionary * sectionsDisctionary; 

Метод, возвращающий мои разделы:

-(NSArray <NSNumber *> *)sections{ 

    return @[@(TableViewSection0), @(TableViewSection1), @(TableViewSection2)]; 
} 

А затем настроить мой 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; 
} 

Следующие методы помогут вам знать, когда открыт раздел, и как реагировать на Tableview источника данных:

Реагируйте раздел источника данных:

/** 
* 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; 
} 

Инструменты:

/** 
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]; 
} 

Точка обзора видимости

/** 
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]; 
    } 
} 
0

Расширяя this ответ написан в Objective C, я писал для тех, кто пишет в Swift

Идея заключается в том, чтобы использовать секции в таблице и установите количество строк в разделе 1 (развалилась) и 3 (расширенный), когда первая строка в этой секции сливают

таблица решает, сколько строк рисовать на основе массив булевых значений

Вам нужно создать две строки в раскадровке и дать им идентификаторы повторного использования «CollapsingRow» и «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 

} 
Смежные вопросы