iOS 中 UITableView 中的展开/折叠部分
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1938921/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Expand/collapse section in UITableView in iOS
提问by vinnitu
Could somebody tell me the way to perform UITableView
expandable/collapsible animations in sections
of UITableView
as below?
有人能告诉我在下面执行UITableView
可展开/可折叠动画sections
的UITableView
方法吗?
or
或者
采纳答案by mjdth
You have to make your own custom header row and put that as the first row of each section. Subclassing the UITableView
or the headers that are already there will be a pain. Based on the way they work now, I am not sure you can easily get actions out of them. You could set up a cell to LOOK like a header, and setup the tableView:didSelectRowAtIndexPath
to manually expand or collapse the section it is in.
您必须制作自己的自定义标题行并将其作为每个部分的第一行。子类化UITableView
已经存在的或 头文件会很痛苦。根据它们现在的工作方式,我不确定您是否可以轻松地从它们中获取操作。您可以将单元格设置为看起来像标题,并设置tableView:didSelectRowAtIndexPath
为手动展开或折叠它所在的部分。
I'd store an array of booleans corresponding the the "expended" value of each of your sections. Then you could have the tableView:didSelectRowAtIndexPath
on each of your custom header rows toggle this value and then reload that specific section.
我会存储一个布尔数组,对应于每个部分的“消耗”值。然后,您可以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];
}
}
Then set numberOfRowsInSection
to check the mybooleans
value and return 1 if the section isn't expanded, or 1+ the number of items in the section if it is expanded.
然后设置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.
此外,您需要更新cellForRowAtIndexPath
以返回任何部分中第一行的自定义标题单元格。
回答by samwize
Some sample code for animating an expand/collapse action using a table view section header is provided by Apple here: Table View Animations and Gestures
Apple 在此处提供了一些用于使用表视图部分标题为展开/折叠动作设置动画的示例代码:Table View Animations and Gestures
The key to this approach is to implement - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
and return a custom UIView which includes a button (typically the same size as the header view itself). By subclassing UIView and using that for the header view (as this sample does), you can easily store additional data such as the section number.
这种方法的关键是实现- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
并返回一个自定义 UIView,其中包含一个按钮(通常与标题视图本身的大小相同)。通过继承 UIView 并将其用于标题视图(如本示例所示),您可以轻松存储其他数据,例如部分编号。
回答by Brian
I got a nice solution inspired by Apple's Table View Animations and Gestures. I deleted unnecessary parts from Apple's sample and translated it into swift.
我得到了一个很好的解决方案,灵感来自 Apple 的Table View Animations and Gestures。我从 Apple 的样本中删除了不必要的部分并将其翻译成 swift。
I know the answer is quite long, but all the code is necessary. Fortunately, you can just copy&past most of the code and just need to do a bit modification on the step 1 and 3
我知道答案很长,但所有代码都是必需的。幸运的是,您可以复制并粘贴大部分代码,只需对第 1 步和第 3 步进行一些修改
1.create SectionHeaderView.swift
and SectionHeaderView.xib
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)
}
}
the SectionHeaderView.xib
(the view with gray background) should look something like this in a tableview(you can customize it according to your needs, of course):
在SectionHeaderView.xib
(与灰色背景视图)应该是这个样子的实现代码如下(你可以根据自己的需要定制它,当然):
note:
笔记:
a) the toggleOpen
action should be linked to disclosureButton
a) 该toggleOpen
行动应与disclosureButton
b) the disclosureButton
and toggleOpen
action are not necessary. You can delete these 2 things if you don't need the button.
b)disclosureButton
和toggleOpen
动作不是必需的。如果你不需要按钮,你可以删除这两个东西。
2.create SectionInfo.swift
2.创建 SectionInfo.swift
import UIKit
class SectionInfo: NSObject {
var open: Bool = true
var itemsInSection: NSMutableArray = []
var sectionTitle: String?
init(itemsInSection: NSMutableArray, sectionTitle: String) {
self.itemsInSection = itemsInSection
self.sectionTitle = sectionTitle
}
}
3.in your tableview
3.在你的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)
}
}
}
回答by jeantimex
To implement the collapsible table section in iOS, the magic is how to control the number of rows for each section, or we can manage the height of rows for each section.
在iOS中实现可折叠表格section,神奇之处在于如何控制每个section的行数,或者我们可以管理每个section的行高。
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).
此外,我们需要自定义部分标题,以便我们可以从标题区域(无论是按钮还是整个标题)监听点击事件。
How to deal with the header? It's very simple, we extend the UITableViewCell class and make a custom header cell like so:
头条怎么处理?很简单,我们扩展 UITableViewCell 类并制作一个自定义标题单元格,如下所示:
import UIKit
class CollapsibleTableViewHeader: UITableViewCell {
@IBOutlet var titleLabel: UILabel!
@IBOutlet var toggleButton: UIButton!
}
then use the viewForHeaderInSection to hook up the header cell:
然后使用 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
}
remember we have to return the contentView because this function expects a UIView to be returned.
记住我们必须返回 contentView 因为这个函数需要返回一个 UIView 。
Now let's deal with the collapsible part, here is the toggle function that toggle the collapsible prop of each section:
现在让我们处理可折叠部分,这里是切换每个部分的可折叠道具的切换功能:
func toggleCollapse(sender: UIButton) {
let section = sender.tag
let collapsed = sections[section].collapsed
// Toggle collapse
sections[section].collapsed = !collapsed
// Reload section
tableView.reloadSections(NSIndexSet(index: section), withRowAnimation: .Automatic)
}
depends on how you manage the section data, in this case, I have the section data something like this:
取决于您如何管理部分数据,在这种情况下,我有这样的部分数据:
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"])
]
at last, what we need to do is based on the collapsible prop of each section, control the number of rows of that section:
最后,我们需要做的是根据每个section的可折叠属性,控制那个section的行数:
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
我在我的 Github 上有一个完整的演示:https: //github.com/jeantimex/ios-swift-collapsible-table-section
If you want to implement the collapsible sections in a grouped-style table, I have another demo with source code here: https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section
如果要在分组样式表中实现可折叠部分,我还有另一个带有源代码的演示:https: //github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section
Hope that helps.
希望有帮助。
回答by Son Nguyen
I have a better solution that you should add a UIButton into section header and set this button's size equal to section size, but make it hidden by clear background color, after that you are easily to check which section is clicked to expand or collapse
我有一个更好的解决方案,您应该在节标题中添加一个 UIButton 并将此按钮的大小设置为与节大小相等,但通过清晰的背景颜色将其隐藏,之后您可以轻松检查单击哪个部分以展开或折叠
回答by RyanG
I ended up just creating a headerView that contained a button ( i saw Son Nguyen's solutionabove after the fact, but heres my code.. it looks like a lot but it's pretty simple):
我最终只是创建了一个包含按钮的 headerView(事后我看到了上面的Son Nguyen 的解决方案,但这是我的代码.. 看起来很多,但很简单):
declare a couple bools for you sections
为您声明几个 bool 部分
bool customerIsCollapsed = NO;
bool siteIsCollapsed = NO;
...code
...代码
now in your tableview delegate methods...
现在在您的 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;
}
and finally the function that gets called when you touch one of the section header buttons:
最后是当您触摸部分标题按钮之一时调用的函数:
- (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];
}
回答by vamsi575kg
This is the best way i found to create expandable table view cells
这是我发现创建可扩展表格视图单元格的最佳方式
.h file
.h 文件
NSMutableIndexSet *expandedSections;
.m file
.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];
Table view delegate methods
表视图委托方法
- (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);
}
回答by C?ur
So, based on the 'button in header' solution, here is a clean and minimalist implementation:
因此,基于“标题中的按钮”解决方案,这是一个干净且简约的实现:
- you keep track of collapsed (or expanded) sections in a property
- you tag the button with the section index
- you set a selected state on that button to change the arrow direction (like △ and ▽)
- 您跟踪属性中的折叠(或展开)部分
- 你用部分索引标记按钮
- 您在该按钮上设置选定状态以更改箭头方向(如 △ 和 ▽)
Here is the code:
这是代码:
@interface MyTableViewController ()
@property (nonatomic, strong) NSMutableIndexSet *collapsedSections;
@end
...
@implementation MyTableViewController
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (!self)
return;
self.collapsedSections = [NSMutableIndexSet indexSet];
return self;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// if section is collapsed
if ([self.collapsedSections containsIndex:section])
return 0;
// if section is expanded
#warning incomplete implementation
return [super tableView:tableView numberOfRowsInSection:section];
}
- (IBAction)toggleSectionHeader:(UIView *)sender
{
UITableView *tableView = self.tableView;
NSInteger section = sender.tag;
MyTableViewHeaderFooterView *headerView = (MyTableViewHeaderFooterView *)[self tableView:tableView viewForHeaderInSection:section];
if ([self.collapsedSections containsIndex:section])
{
// section is collapsed
headerView.button.selected = YES;
[self.collapsedSections removeIndex:section];
}
else
{
// section is expanded
headerView.button.selected = NO;
[self.collapsedSections addIndex:section];
}
[tableView beginUpdates];
[tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
}
@end
回答by yunhasnawa
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.
我找到了另一种相对简单的方法来解决这个问题。通过使用这种方法,我们不需要更改几乎总是与数据数组索引相关的单元格,这可能会导致我们的视图控制器混乱。
First, we add this following properties to our controller class:
首先,我们将以下属性添加到我们的控制器类中:
@property (strong, nonatomic) NSMutableArray* collapsedSections;
@property (strong, nonatomic) NSMutableArray* sectionViews;
collapsedSections
will save collapsed section numbers.
sectionViews
will store our custom section view.
collapsedSections
将保存折叠部分编号。
sectionViews
将存储我们的自定义剖面视图。
Synthesize it:
合成它:
@synthesize collapsedSections;
@synthesize sectionViews;
Initialize it:
初始化它:
- (void) viewDidLoad
{
[super viewDidLoad];
self.collapsedSections = [NSMutableArray array];
self.sectionViews = [NSMutableArray array];
}
After that, we must connect our UITableView so it can be accessed from within our view controller class:
之后,我们必须连接我们的 UITableView 以便可以从我们的视图控制器类中访问它:
@property (strong, nonatomic) IBOutlet UITableView *tblMain;
Connect it from XIB to view controller using ctrl + drag
like usually.
ctrl + drag
通常使用类似的方法将其从 XIB 连接到视图控制器。
Then we create view as custom section header for our table view by implementing this UITableView delegate:
然后我们通过实现这个 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;
}
Next, we implement method to save our previously created custom section header in class property:
接下来,我们实现方法将我们之前创建的自定义节标题保存在类属性中:
- (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];
}
}
}
Add UIGestureRecognizerDelegate
to our view controller .h file:
添加UIGestureRecognizerDelegate
到我们的视图控制器 .h 文件中:
@interface MyViewController : UIViewController<UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate>
Then we create method attachTapGestureToView:
然后我们创建方法 attachTapGestureToView:
- (void) attachTapGestureToView:(UIView*) view
{
UITapGestureRecognizer* tapAction = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
[tapAction setDelegate:self];
[view addGestureRecognizer:tapAction];
}
Above method will add tap gesture recognizer to all of section view we created before. Next we should implement onTap:
selector
上述方法将向我们之前创建的所有剖面视图添加点击手势识别器。接下来我们应该实现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.
当用户点击我们的任何表格视图部分时,将调用上述方法。此方法根据sectionViews
我们之前创建的数组搜索正确的节号。
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;
}
Next, we must implement method toggleCollapseSection:
接下来,我们必须实现方法 toggleCollapseSection:
- (void) toggleCollapseSection:(NSInteger) section
{
if([self isCollapsedSection:section])
{
[self removeCollapsedSection:section];
}
else
{
[self addCollapsedSection:section];
}
[[self tblMain] reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade];
}
This method will insert/remove section number to our collapsedSections
array we created before. When a section number inserted to that array, it means that the section should be collapsed and expanded if otherwise.
此方法将向collapsedSections
我们之前创建的数组插入/删除节号。当一个节号插入到该数组中时,这意味着该节应该被折叠和展开,否则的话。
Next we implement removeCollapsedSection:
, addCollapsedSection:section
and isCollapsedSection:section
接下来我们实现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]];
}
This three method is just helpers to make us easier in accessing collapsedSections
array.
这三种方法只是帮助我们更容易访问collapsedSections
数组。
Finally, implement this table view delegate so our custom section views looks nice.
最后,实现这个表视图委托,让我们的自定义部分视图看起来不错。
- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 30.f; // Same as each custom section view height
}
Hope it helps.
希望能帮助到你。
回答by Angel
I've used a NSDictionary as datasource, this looks like a lot of code, but it's really simple and works very well! how looks here
我使用了 NSDictionary 作为数据源,这看起来像很多代码,但它真的很简单,而且效果很好! 这里看起来如何
I created a enum for the sections
我为这些部分创建了一个枚举
typedef NS_ENUM(NSUInteger, TableViewSection) {
TableViewSection0 = 0,
TableViewSection1,
TableViewSection2,
TableViewSectionCount
};
sections property:
部分属性:
@property (nonatomic, strong) NSMutableDictionary * sectionsDisctionary;
A method returning my sections:
返回我的部分的方法:
-(NSArray <NSNumber *> * )sections{
return @[@(TableViewSection0), @(TableViewSection1), @(TableViewSection2)];
}
And then setup my data 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;
}
The next methods, will help you to know when a section is opened, and how to respond to tableview datasource:
接下来的方法,将帮助您了解某个部分何时被打开,以及如何响应 tableview 数据源:
Respond the section to datasource:
响应数据源的部分:
/**
* 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;
}
Tools:
工具:
/**
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];
}
Toggle section visibility
切换部分可见性
/**
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];
}
}