IOS 中 UITableView 的展开/折叠部分

谁能告诉我如何在 UITableViewsections中执行如下的 UITableView可展开/可展开动画?

or

151547 次浏览

您必须创建自己的自定义标题行,并将其作为每个部分的第一行。子类化 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;
}
}

Also, you will need to update cellForRowAtIndexPath to return a custom header cell for the first row in any section.

我有一个更好的解决方案,你应该添加一个 UIButton 到部分标题,并设置这个按钮的大小等于部分大小,但使它隐藏的明确背景颜色,之后,你很容易检查哪个部分被点击展开或折叠

苹果在这里提供了一些示例代码,用于使用表视图部分标题动画展开/折叠操作

这种方法的关键是实现 - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section并返回一个自定义 UIView,其中包含一个按钮(通常与头视图本身的大小相同)。通过子类化 UIView 并将其用于头视图(如本示例所示) ,您可以轻松地存储其他数据,如部分编号。

这是我发现的创建可扩展表视图单元格的最佳方法

.h file

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


}

最后我只是创建了一个包含按钮的 headerView (事后我看到了上面的 Son Nguyen's solution,但这里是我的代码。.它看起来很多,但其实很简单) :

为你们的部分申报几个帐篷

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

因此,基于“页眉中的按钮”解决方案,这里有一个简洁和极简的实现:

  • 跟踪属性中的折叠(或展开)部分
  • 你用节索引标记按钮
  • 你可以在按钮上设置一个选定的状态来改变箭头方向(比如△和 )

密码如下:

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

I found another relatively simple way to solve that problem. By using this method we will not required to alter our cell which is almost always related to data array index, potentially causing mess in our view controller.

首先,我们将以下属性添加到控制器类中:

@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;

像通常一样,使用 ctrl + drag将它从 XIB 连接到视图控制器。

Then we create view as custom section header for our table view by implementing this UITableView delegate:

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

接下来,我们实现一个方法,将先前创建的自定义节头保存在 class 属性中:

- (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添加到我们的 view controller. 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];
}

Above method will invoked when user tap any of our table view section. This method search correct section number based on our sectionViews array we created before.

Also, we implement method to get wihch section of header view belongs to.

- (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:sectionisCollapsedSection: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
}

希望能有帮助。

I got a nice solution inspired by Apple's 表格视图动画和手势. I deleted unnecessary parts from Apple's sample and translated it into swift.

I know the answer is quite long, but all the code is necessary. Fortunately, you can just copy and paste most of the code and just need to do a bit modification on step 1 and 3

1. 创建 SectionHeaderView.swiftSectionHeaderView.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(背景为灰色的视图)在表格视图中应该是这样的(当然,你可以根据自己的需要定制它) : enter image description here

注:

A) toggleOpen动作应与 disclosureButton连结

B)不需要采取 disclosureButtontoggleOpen行动。如果你不需要按钮,你可以删除这两样东西。

2.create 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
}
}

在你的桌面上

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

要在 iOS 中实现可折叠表部分,关键在于如何控制每个部分的行数,或者我们可以管理每个部分的行高。

Also, we need to customize the section header so that we can listen to the tap event from the header area (whether it's a button or the whole header).

如何处理标题?这非常简单,我们扩展 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
}

I have a fully working demo on my Github: https://github.com/jeantimex/ios-swift-collapsible-table-section

demo

如果您想在一个分组样式的表中实现可折叠的部分,这里有另一个带源代码的演示: https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section

希望能帮上忙。

Apple 在 表格视图动画和手势提供了一些示例代码,用于使用表视图部分标头来动画展开/折叠操作。

这种方法的关键是实现

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section

并返回一个自定义 UIView,其中包含一个按钮(通常与头视图本身的大小相同)。通过子类化 UIView 并将其用于头视图(如本示例所示) ,您可以轻松地存储其他数据,如部分编号。

我使用了一个 NSDictionary 作为数据源,这看起来像很多代码,但它真的很简单,工作得很好! 这里看起来怎么样

我为这些部分创建了一个枚举

typedef NS_ENUM(NSUInteger, TableViewSection) {


TableViewSection0 = 0,
TableViewSection1,
TableViewSection2,
TableViewSectionCount
};

物业:

@property (nonatomic, strong) NSMutableDictionary * sectionsDisctionary;

返回我的部分的方法:

-(NSArray <NSNumber *> * )sections{


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

然后设置我的数据源:

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

在目标 C 编写的 这个答案的基础上,我为那些用 Swift 编写的人编写了以下内容

其思想是使用表中的部分,并在点击该部分中的第一行时,将该部分中的行数设置为1(折叠)和3(展开)

该表根据布尔值数组决定要绘制的行数

您需要在故事板中创建两行,并给它们重用标识符‘ CollapsingRow’和‘ GroupHeding’

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


}

我已经使用多个部分做了同样的事情。

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


}

我添加这个解决方案是为了完整性,并展示了如何使用节标题。

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


}

链接到要点: https://gist.github.com/pawelkijowskizimperium/fe1e8511a7932a0d40486a2669316d2c

支持@jean。Timex 解决方案,如果您想在任何时候打开一个部分,请使用以下代码。创建一个变量,如: var expdedSection = -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;
}