iOS开发高级分享 - iOS的可折叠表视图

时间:2024-09-19 22:34:50

导言

我曾经开发过一个iphone应用程序,它显示了大量的输入,这些输入分为不同的类别,在`UITableView`...若要更改其中一个输入的值,用户按下表视图中的对应行,并在出现的单独屏幕中更改该值。表视图为每个类别有一个节,每个节包含每个输入的表格单元格(行)。

问题是输入的数量变得非常非常大,所以它没有给用户一个非常好的概述。从桌面滚动到底部甚至很乏味。

我们决定用户应该能够通过简单地按下节的标题来折叠和展开表中的部分(类别)。我们要求实现这一目标的代码应该是可重用的,并且要求对现有代码进行尽可能少的更改。

另外,如果你想一起进阶,不妨添加一下交流群[1012951431](),选择加入一起交流,一起学习。期待你的加入!(进群获取本文源码)

下面的屏幕截图显示了表视图及其可折叠部分的外观:

最高达到IOS 6                                       iOS 7+

iOS开发高级分享 - iOS的可折叠表视图         iOS开发高级分享 - iOS的可折叠表视图

实施

我认为实现上述目标的最佳方法是创建 UITableView 类,命名为 CollapsableTableView ...这确保了代码是可重用的。如果操作正确,则不需要对 UITableView- 他们会像一个普通的人一样处理桌子上的风景`UITableView`...唯一必要的更改是更改 UITableView 在西布文件到这个新的子类。,以确保客户端可以像普通用户一样使用表视图。UITableView ,我们必须尝试允许完全通过 UITableView 类,包括 UITableViewDelegate ,以及 UITableViewDataSource 协议。

可折叠表视图必须以某种方式跟踪哪些区段被折叠(收缩)以及哪些部分被展开。可能最明显的方法是维护一组已展开的节的索引,或者一个布尔数组,其中每个索引的值指示对应的节是否展开。但是,如果我们假设表视图的客户端可以添加和删除节(在我们的场景中是这样的),那么节的索引将不会保持固定,因此处理索引充其量也是很麻烦的。因此,我们必须为节找到不同的标识符。为此,我们可以使用章节的标题文本。当然,这假定节的标题文本唯一地标识该节,并且其标题文本保持不变,但考虑到必须坚持使用 UITableView 同学们,这可能是我们能做的最好的了。这还假设客户端实现了 tableView:titleForHeaderInSection: 的选择器 UITableViewDelegate 所有表格单元格的协议。在我们的项目中,情况就是这样。在使用代码节中,我们将解释我们的类是如何支持实现 tableView:viewForHeaderInSection: 选择器。

为了更容易地管理头视图,我们创建了一个 UIViewController 类,命名为 CollapsableTableViewHeaderViewController ...对于这门课,有两个西布一号西布用于平面布局的表,而用于具有分组布局的表。此类包含视图中可操作的所有标签的`IB`出口。它存储一个布尔值,指示区段是否折叠。此视图控制器类还确保它的视图在用户点击它时通知我们,以便 CollapsableTableView 才能采取必要的行动。

下面是.h.的档案 CollapsableTableViewHeaderViewController:

#import <UIKit/UIKit.h>
#import "TapDelegate.h"
#import "CollapsableTableViewTapRecognizer.h"

@interface CollapsableTableViewHeaderViewController : UIViewController
{
IBOutlet UILabel *collapsedIndicatorLabel,*titleLabel,*detailLabel;

CollapsableTableViewTapRecognizer* tapRecognizer;

BOOL viewWasSet;
id<TapDelegate> tapDelegate;

NSString* fullTitle;
BOOL isCollapsed;
}

@property (nonatomic, retain) NSString* fullTitle;
@property (nonatomic, readonly) UILabel* titleLabel;
@property (nonatomic, retain) NSString* titleText;
@property (nonatomic, readonly) UILabel* detailLabel;
@property (nonatomic, retain) NSString* detailText;
@property (nonatomic, assign) id<TapDelegate> tapDelegate;
@property (nonatomic, assign) BOOL isCollapsed;

@end

collapsedIndicatorLabel 是否显示“-”或“+”的小标签,取决于区段是否折叠。当 isCollapsed 的文本被更改。 collapsedIndicatorLabel 则相应地设置为“-”或“+”。 titleLabel 是包含标题和 detailLabel 显示标题右侧的可选详细文本。

下面是`TapDelegate`议定书:

#import <UIKit/UIKit.h>

@protocol TapDelegate

- (void) view:(UIView*) view tappedWithIdentifier:(NSString*) identifier;

@end

这个 view:tappedWithIdentifier: 当头视图被点击时调用选择器 CollapsableTableView 实现 TapDelegate 协议,使其能够折叠或展开相应的报头以响应此协议。调用选择器时,将使用 view 参数的标头的标题字符串。 identifier 参数,以便`CollapsableTableView`可以进行查找,以确定标题当前是否已折叠,以及其当前节索引是什么。

在该项目的第一个已发布的实现中,该选择器由 CollapsableTableViewHeaderViewController 对应的标题视图。因为在该版本中, CollapsableTableView 存储(并因此保留) CollapsableTableViewHeaderViewControllers 其所有章节。但是,为了提高实现的内存效率--特别是对于具有多个节的表 --CollapsableTableView 所以它不再这样做了。因此,结果是 CollapsableTableViewHeaderViewController 在头视图出现在表中后不久,就会从内存中释放报头视图(报头)。UIView 只要它在表中可见,它仍然保留在内存中)。这意味着当头视图被点击时,可能没有。 CollapsableTableViewHeaderViewController 调用 TapDelegate 选择器。

在我们寻找这个问题的解决方案之前,让我们看看头视图的点击是如何在 CollapsableTableViewHeaderViewController.m.

- (void) setView:(UIView*) newView
{
if (viewWasSet)
{
[self.view removeGestureRecognizer:tapRecognizer];
[tapRecognizer release];
}
[super setView:newView];
tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(headerTapped)];
[self.view addGestureRecognizer:tapRecognizer];
viewWasSet = YES;
}

- (void) headerTapped
{
[tapDelegate viewTapped:self.view ofViewController:self];
}

所以我们推翻了 setView: 方法 UIViewController 类以添加 UITapGestureRecognizer 到 UIView 分配给 CollapsableTableViewHeaderViewController ...这,这个 UITapGestureRecognizer 的方法被配置为调用 CollapsableTableViewHeaderViewController 每当头视图被点击时。在新代码中,此技术不再起作用,因为 CollapsableTableViewHeaderViewController 将经常在用户点击头时被解除分配。

这个问题的唯一解决方案可能是最明显的,就是配置 UITapGestureRecognizer 若要调用对象中的选择器,当用户单击标头视图时,该对象将不会被释放。该对象的一些选择如下:

  • CollapsableTableView
  • UIView
  • UITapGestureRecognizer

第二个选择将不起作用,因为我们无法控制 UIView 传递到 setView: 方法来自(也就是说,我们不能使用子类)。 UIView 为了给它添加一个额外的方法,也许我们可以将传入的内容包装起来。 UIView 类的子类中的 UIView 我们自己的,但我们不要去那里!)向 CollapsableTableView 是一个选项,尽管添加没有参数的方法是不行的,因为 CollapsableTableView 不知道哪个标题已被点击。然而,在文件中 UITapGestureRecognizer ,我们看到替代选择器类型是一个选择器,它接受 UITapGestureRecognizer 对象作为参数。但是,我们必须把 UITapGestureRecognizer 以添加存储标题字符串的属性。所以如果我们必须把 UITapGestureRecognizer,使用第三个选项并配置 UITapGestureRecognizer 调用其内部的选择器。这是在实现中采取的方法:我们使用 UITapGestureRecognizer ,我们称之为 CollapsableTableViewTapRecognizer ,定义如下:

#import <Foundation/Foundation.h>
#import "TapDelegate.h"

@interface CollapsableTableViewTapRecognizer : UITapGestureRecognizer
{
id<TapDelegate> tapDelegate;

NSString* fullTitle;
UIView* tappedView;
}

@property (nonatomic, assign) id<TapDelegate> tapDelegate;
@property (nonatomic, retain) NSString* fullTitle;
@property (nonatomic, assign) UIView* tappedView;

- (id) initWithTitle:(NSString*) theFullTitle andTappedView:(UIView*)
theTappedView andTapDelegate:(id<TapDelegate>) theTapDelegate;

@end 

在 initWithTitle:andTappedView:andTapDelegate: 方法,CollapsableTableViewTapRecognizer 对象配置为调用私有方法。headerTapped 当景物被点击的时候。

- (void) headerTapped
{
[tapDelegate view:tappedView tappedWithIdentifier:fullTitle];
} 

让我们回到 CollapsableTableView 现在。当它从客户端获得一个节的标题时,它需要能够进行一次查找,以查看标头是否折叠,以及标头的节索引是什么。为此,我们保持两个独立的 NSMutableDictionary 对象:将标题标题映射为指示标头是否折叠的布尔值的对象,以及将标题标题映射为整数(给定标头节索引的整数)的对象。我们还可以使用一个字典在指定的索引上查找该节的标题(当然,每当客户端从表中添加或删除一个节时,就必须更新该字典)。

那么,如何 CollapsableTableView 实际上是塌陷和扩张部分?那么,折叠的部分只会有0行,所以即使客户端将返回该节的正常行数, CollapsableTableView 将返回折叠节的行数为0,或由客户端返回的扩展节的行数。这表明 CollapsableTableView 需要拦截对 tableView:numberOfRowsInSection: 方法。它还必须返回 CollapsableTableViewHeaderViewController 对于每个部分,因此它还必须拦截对 tableView:viewForHeaderInSection: 方法。所以为了 CollapsableTableView 为了能够响应这两个选择器,它必须实现 UITableViewDelegate 而 UITableViewDataSource 协议,并在运行时将其委托和数据源属性设置为.本身!但是,必须将对这些协议的选择器的许多调用转发给客户端,因此 CollapsableTableView 存储真实委托和数据源的引用,以便为这些情况提供参考。

- (void) setDelegate:(id <UITableViewDelegate>) newDelegate
{
[super setDelegate:self];
realDelegate = newDelegate;
}

- (void) setDataSource:(id <UITableViewDataSource>) newDataSource
{
[super setDataSource:self];
realDataSource = newDataSource;
}

的接口文件CollapsableTableView:

#import <Foundation/Foundation.h>
#import "TapDelegate.h"

#define COLLAPSED_INDICATOR_LABEL_TAG 36
#define BUSY_INDICATOR_TAG 37
@interface CollapsableTableView :
UITableView <UITableViewDelegate,UITableViewDataSource,TapDelegate>
{
id<UITableViewDelegate> realDelegate;
id<UITableViewDataSource> realDataSource;
id<CollapsableTableViewDelegate> collapsableTableViewDelegate;

...
}

@property (nonatomic,assign) id<CollapsableTableViewDelegate> collapsableTableViewDelegate;
@property (nonatomic,retain) NSString* collapsedIndicator;
@property (nonatomic,retain) NSString* expandedIndicator;
@property (nonatomic,assign) BOOL showBusyIndicator;
@property (nonatomic,assign) BOOL sectionsInitiallyCollapsed;
@property (nonatomic,readonly) NSDictionary* headerTitleToIsCollapsedMap;

- (void) setIsCollapsed:(BOOL) isCollapsed forHeaderWithTitle:(NSString*) headerTitle;
- (void) setIsCollapsed:(BOOL) isCollapsed forHeaderWithTitle:(NSString*)
headerTitle andView:(UIView*) headerView;
- (void) setIsCollapsed:(BOOL) isCollapsed forHeaderWithTitle:(NSString*)
headerTitle withRowAnimation:(UITableViewRowAnimation) rowAnimation;
- (void) setIsCollapsed:(BOOL) isCollapsed forHeaderWithTitle:(NSString*)
headerTitle andView:(UIView*) headerView
withRowAnimation:(UITableViewRowAnimation) rowAnimation;

@end 

执行 CollapsableTableView 从讨论到这一点,基本上都是这样。下面的段落简要解释了该类的公共属性和方法的用途。

这个 collapsableTableViewDelegate 属性可以设置为实现 CollapsableTableViewDelegate 属性,以便每当某个区段折叠或展开时,以及当该对象完成折叠或展开时,都会通知该对象。

默认折叠和展开指示符(默认值分别为“+”和“-”)可以使用 collapsedIndicator 和 expandedIndicator 财产。
这个 showBusyIndicator 属性的默认值为YES,如果设置,则会导致活动指示符视图( SPINTER )(位于标头视图中的子视图中具有由BUSY_INDICATOR_TAG)当折叠或展开页眉视图的部分花费的时间超过0.5秒时,要在标题视图上动画。
这个 sectionsInitiallyCollapsed 属性的默认值为NO,并控制新的部分是否会首先被折叠或不折叠。

这个 headerTitleToIsCollapsedMap 物业供应 NSDictionary 将标题的标题字符串映射到 NSNumber 对象,该对象包含指示标头是否折叠的布尔值。

这个 setIsCollapsed:forHeaderWithTitle: ...方法将用于以编程方式折叠或展开区段。如果客户端具有对 UIView 对于相应的标头,它可以调用包含 andView: 用那个`UIView`作为参数。如果调用了另外两个方法中的任何一个,则必须重新加载相应的部分(和头视图),并且动画有时会比... andView: 使用方法。

使用“守则”

需要添加到 Xcode 项目以便使用 CollapsableTableView 类中的 CollapsableTableView 文件夹中的压缩文件(下载源代码).

另外,如果你想一起进阶,不妨添加一下交流群[1012951431](),选择加入一起交流,一起学习。期待你的加入!(进群获取本文源码)

CollapsableTableView 完全可以像普通的 UITableView ,只要客户端实现 tableView:titleForHeaderInSection: 选择器(相对于 tableView:viewForHeaderInSection: )用于所有表单元格。唯一必要的更改是更改 UITableView 在西布到 CollapsableTableView ...要做到这一点,请打开西布文件,选择 UITableView ,打开身份检查器并键入“ CollapsableTableView “”类“字段旁边。

执行 CollapsableTableView 也允许使用 tableView:viewForHeaderInSection: ,但在这里,它无法访问标头的标题字符串,它通常用作标头的标识符。相反,它使用字符串“标记%i”,其中%i是tag返回的视图的属性(如果tag为0,但区段索引不是0,此数字默认为 CollapsableTableView )。这意味着,如果客户端返回某些单元格的视图(而不是头文本字符串),并且如果它可以添加和删除部分,则必须分配唯一的tag对应于每个区段的视图的编号。

如果客户端返回某些单元格的视图,则这些视图可以包含一个标签,该标签指示是否折叠了标头。简单地设置tag属性设置为 COLLAPSED_INDICATOR_LABEL_TAG 中定义的 CollapsableTableView.h ...这个 CollapsableTableView 然后,每当区段折叠或展开时,该标签的文本将设置为“+”或“-”(除非 collapsedIndicator或expandedIndicator 属性已设置为不同的字符串)。

的客户端。CollapsableTableView 可能不知道它不适用于常规的 UITableView 但如果它知道 UITableView 是 CollapsableTableView ,它可以将对象强制转换为后一种类型,并使用headerTitleToIsCollapsedMap属性以确定哪些区段已折叠,而setIsCollapsed:forHeaderWithTitle: 方法以编程方式折叠或展开区段。

正如在实施部分,CollapsableTableView 还允许将细节文本显示在标题的右侧。若要使用此功能,请在 tableView:titleForHeaderInSection: ,返回表单的字符串“Header Text\Details Text”。

历史

  • 2011/08/13
    • 初始版本
  • 2011/10/29
    • 在上一个版本中,在IOS 5中,标题的高度都是0。这个tableView:heightForHeaderInSection:选择器现在找到适当的标题视图,并直接询问它的高度,这解决了问题。
    • 增加了对多行标头的支持。如果numberOfLines属性设置为0,并且标头的文本不适合一行,标签将按需要将文本拆分成多行,标头视图控制器将设置标签和标头视图的高度,以便所有行都适合(这在setTitleText:选择器CollapsableTableViewHeaderViewController.m)。可以通过设置标签的numberOfLines属性设置为最大行数。
    • 1 st.osama指出setIsCollapsed:forHeaderWithTitle:在显式重新加载相应标头的整个部分之后,选择器才有效果。这个已经修好了。
    • 当最后一节被折叠并展开时,表视图最多向下滚动到该节的第五行,这样用户就可以看到出现了一些行。
  • 2011/11/05
    • 修正了iOS 4中出现的错误,导致纯文本表格视图的标题消失。
  • 2011/11/27
    • CollapsableTableView被更改,使其不再存储所有的CollapsableTableViewHeaderViewController各部分的对象。此更改的目的是提高实现的内存效率,特别是对于有许多节的表。这是一个相当戏剧性的变化,但文章的相关部分已经更新。
    • 这个getHeaderTitleToIsCollapsedMap方法CollapsableTableView被只读属性替换。headerTitleToIsCollapsedMap.
    • 自定义头视图现在可以包含一个具有魔力的标签。tag值为36(按常量定义)COLLAPSED_INDICATOR_LABEL_TAG在……里面CollapsableTableView.h)当相应的标题折叠或展开时,其文本将被更新为“+”或“-”。
  • 2012/01/26
    • 根据阿拉斯加22的请求,除了initWithCoder:方法,CollapsableTableView现在也重写了init, initWithFrame:,和initWithFrame:style:方法来执行必要的初始化。这是因为CollapsableTableView也可以以编程方式构造,而不是由西布.
  • 2012/02/12
    • 正如magikcm在注释中所建议的那样,我已经实现了对展开节时行插入的优化。我是通过实施“不诚实的代理数据源”策略来做到这一点的。这个职位.
  • 2012/08/10
    • 以前,当表视图需要知道标头视图的高度时,可折叠表视图实际上将创建该标头,以确定并返回其高度。当表视图需要重新计算其总高度时,这会造成很大的延迟,因为在此过程中会查询所有标头的高度。例如,当扩展或折叠一个区段时,就会发生这种情况。这一延误在许多款次中尤为明显。

可折叠表视图现在缓存所有标头视图的高度。这实际上消除了在折叠或扩展区段时发生的过度延迟。

    • 美学改进:扩展部分的标题视图的折叠/扩展指示符现在显示较长的破折号字符,而不是常规的短破折号字符。
  • 2012/11/14
    • 当区段为空时,未显示其折叠/展开指示符。
    • 已折叠和展开的指示符现在可以通过collapsedIndicator和expandedIndicator.的性质CollapsableTableView.
  • 2012/12/23
    • 如果客户端同时为标题提供视图和标题(通过实现tableView:viewForHeaderInSection:和tableView:titleForHeaderInSection:),则当为该节返回的视图未被选中时,视图是首选的。nil.页脚的处理方式类似。这种行为与UITableView已实现。
    • 增加了对节页脚的支持。
    • 修正了当最后一个部分不包含任何行并且被点击时发生的崩溃。
    • 当区段折叠或展开所需时间超过0.5秒时,活动指示符将出现在标头视图中。(呜呜!使用多线程进行用户界面编程是很棘手的!)
    • 可以通过设置showBusyIndicator属性(默认为打开)。若要在自定义标头视图中启用此行为,请添加UIActivityIndicatorView值为BUSY_INDICATOR_TAG(定义为CollapsableTableView.h如37).
    • A CollapsableTableViewDelegate可以分配给CollapsableTableView,这样,每当某个区段开始折叠或展开时,或当它完成折叠或展开时,都可以通知委托。
    • 新财产sectionsInitiallyCollapsed的CollapsableTableView如果新区段最初是否折叠,则控制。默认值是NO.
  • 2013/01/18
    • 修正了在scrollToRowAtIndexPath:…,或selectRowAtIndexPath:…,或deselectRowAtIndexPath:…在折叠部分中的一行被调用。
    • 修正了上面提到的方法,以便在使用动画=调用它们时YES,效果将是即时的(同步的)。
    • 修正了在折叠和扩展部分时偶尔发生的崩溃。
    • 实现了删除-行优化。由于这一点,具有大量行的折叠部分现在应该更快了。
    • CollapsableTableView已与存储板中的静态单元格兼容。
  • 2013/02/10
    • 修正了双击大截面的头部时发生的崩溃.
  • 2013/09/08
    • 修正了使用自定义标头视图时在IOS 6.1旋转时发生的崩溃。
    • 启用即时编程行-选择或在区段展开上滚动行。
  • 2013/09/23
    • 修正了在将行添加到空部分后未显示折叠/展开指示符的错误。
  • 2013/10/26
    • 在iOS 7或更高版本上运行时,可折叠表视图现在使用单独的XIB文件作为表各节的头和页脚,以适应IOS 7的新外观。

翻译地址:https://www.codeproject.com/Articles/240435/Collapsable-Table-View-for-iOS