使用UITableView实现树视图

来源:互联网 发布:泸州网络问政平台app 编辑:程序博客网 时间:2024/06/04 20:10

本文的目的,是研究有没有可能在一个TableView中呈现树形数据,尤其是树形菜单。众多的网络资料都强调,Cocoa框架不支持树形视图,苹果推荐程序员使用TableViewController+NavigationController的方式展现树形菜单。如果2-3层的树形数据还可以忍受,万一层次稍多一点,必须反复的用导航按钮在视图中转来转去,显然并不太方便。何况笔者认为2-3层的导航也要切换多次视图,也是一种浪费。

 

一、搭建基本框架 1

二、实现树节点 2

三、实现树 3

四、实现TreeViewCell 4

五、 在TreeViewController中展现树视图 6

六、 一些改进 8

七、 进一步的封装 10

一、搭建基本框架

 

1、新建Winddow-Based-Application项目TreeView;

2、删除MainWindow.xib,删除plist中Main nib file base name;

3、修改main.m: int retVal = UIApplicationMain(argc, argv, nil,@"TreeViewAppDelegate");

 

4、修改TreeViewAppDelegate,删除属性window的声明,删除window的synthesize语句。增加变量声明: TreeViewController* rootViewController;

修改(BOOL)application: didFinishLaunchingWithOptions方法 :

    window=[[UIWindow alloc]initWithFrame:CGRectMake(0, 0,320, 480)];

rootViewController=[[TreeViewController alloc]init];

[window addSubview:rootViewController.view];

    [window makeKeyAndVisible];

return YES;

5、新建类TreeViewController,继承UIViewController.

二、实现树节点

 

1、树由节点构成。树节点是一种链表结构。它包含有父节点、子节点等内容,同时应实现节点添加等操作。

2、新建TreeNode类。

===============.h文件==============

#import <Foundation/Foundation.h>

@interface TreeNode : NSObject {

TreeNode* p_node;//父节点

NSMutableArray* children;//子节点

id data;//节点可以包含任意数据

NSString* title;//节点要显示的文字

NSString* key;//主键,在树中唯一

BOOL expanded;//标志:节点是否已展开,保留给TreeViewCell使用的

}

@property (retain) TreeNode* p_node;

@property (retain) id data;

@property (retain) NSString *title,*key;

@property (assign) BOOL expanded;

@property (retain) NSMutableArray* children;

-(int) deep;//hasChildren的访问方法

-(BOOL)hasChildren;

//子节点的添加方法

-(void)addChild:(TreeNode*)child;

-(int)childrenCount;

@end

===============.m文件==============

#import "TreeNode.h"

 

@implement ation TreeNode

@synthesize p_node,children,data,title,key,expanded;

-(id)init{

if (self=[super init]) {

p_node=nil;

children=nil;

key=nil;

}

return self;

}

-(void)addChild:(TreeNode *)child{

if (children==nil) {

children=[[NSMutableArray alloc]init];

}

child.p_node=self;

[children addObject:child];

}

-(int)childrenCount{

return children==nil?0:children.count;

}

-(int)deep{

return p_node==nil?0:[p_node.deep]+1;

}

-(BOOL)hasChildren{

if(children==nil || children.count==0)

return NO;

else return YES;

}

@end

 

三、实现树

 

1、节点其实就是一种树,有父节点、子节点。但树的最大用处在于遍历树、查找任意子节点。我们可以在TreeNode中增加遍历树的操作。

2、在TreeNode的头文件中增加方法声明:

+(TreeNode*)findNodeByKey:(NSString*)_key :(TreeNode*)node;

+(void)getNodes:(TreeNode*)root :(NSMutableArray*) array;

两个方法都使用递归对树节点进行遍历 ,不同的是前者在查找到key相同的节点返回,而后者则直接把树的所有节点添加到数组中返回。

4、 findNodeByKey 和getNodes 方法:

+(TreeNode*)findNodeByKey:(NSString*)_key :(TreeNode*)node{

if ([_key isEqualToString:[node key]]) {//如果node就匹配,返回node

return node;

}else if([node hasChildren]){//如果node有子节点,查找node 的子节点

for(TreeNode* each in [node children]){

NSLog(@"retrieve node:%@ %@",each.title,each.key);

TreeNode* a=[TreeNode findNodeByKey:_key :each];

if (a!=nil) {

return a;

}

}

}

//如果node没有子节点,则查找终止,返回nil

return nil;

}

+(void)getNodes:(TreeNode*)root :(NSMutableArray*) array{

[array addObject:root];

if ([root hasChildren]) {

for(TreeNode* each in [root children]){

[TreeNode getNodes:each :array];

}

}

return;

}

四、实现TreeViewCell

 

1、新建类TreeViewCell.

2、TreeViewCell.h文件:

#import <UIKit/UIKit.h>

#import "TreeNode.h"

 

@interface TreeViewCell : UITableViewCell {

UIButton* btnExpand;//按钮:用于展开子节点

SEL onExpand;//selector:点击“+”展开按钮时触发

TreeNode* treeNode;//每个单元格表示一个节点

UILabel* label;//标签:显示节点title

id owner;//表示 onExpand方法委托给哪个对象

UIImageView* imgIcon;//图标

}

@property (assign) SEL onExpand;

@property (retain) id owner;

@property (retain) UIImageView* imgIcon;

-(void)setTreeNode:(TreeNode *)node;

@end

3、TreeViewCell.m文件:

#import "TreeViewCell.h"

 

 

@implementation TreeViewCell

@synthesize onExpand,imgIcon,owner;

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {

    if ((self = [super initWithStyle:stylereuseIdentifier:reuseIdentifier])) {

        // Initialization code

    }

    return self;

}

 

 

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {

 

    [super setSelected:selected animated:animated];

 

    // Configure the view for the selected state

}

-(void)onExpand:(id)sender{

if ([treeNode hasChildren]) {//如果有子节点

//NSLog(@"%d",[treeNode hasChildren]);

treeNode.expanded=!treeNode.expanded;//切换“展开/收起”状态

if(treeNode.expanded){//若展开状态设置“+/-”号图标

[btnExpand setImage:[UIImage imageNamed:

 @"minus.png"] forState:UIControlStateNormal];

}else {

[btnExpand setImage:[UIImage imageNamed:

 @"plus.png"] forState:UIControlStateNormal];

}

if(owner!=nil && onExpand!=nil)//若用户设置了onExpand属性则调用

[owner performSelector:onExpand withObject:treeNode];

}

}

-(void)setTreeNode:(TreeNode *)node{

treeNode=node;

if (label==nil) {

//NSLog(@"label is nil");

imgIcon=[[UIImageView alloc]initWithFrame:

  CGRectMake(20+(15*node.deep), 6, 32, 32)];

label=[[UILabel alloc]initWithFrame:

   CGRectMake(50+(15*node.deep), 0, 200,36)];

btnExpand=[[UIButtonalloc]initWithFrame:CGRectMake((15*node.deep), 5, 32, 32)];

[btnExpand addTarget:self action:@selector(onExpand:)

forControlEvents:UIControlEventTouchUpInside];

[imgIcon setImage:[UIImageimageNamed:@"folder_small.png"]];

[self addSubview:label];

[self addSubview:imgIcon];

[self addSubview:btnExpand];

}else {

[label setFrame:CGRectMake(50+(15*node.deep), 0, 200, 36)];

[imgIcon setFrame:CGRectMake(20+(15*node.deep), 6, 32,32)];

[btnExpand setFrame:CGRectMake(15*node.deep, 5, 32, 32)];

}

if ([node hasChildren]) {

NSLog(@"node has children");

if ([node expanded]) {

[btnExpand setImage:[UIImage imageNamed:@"minus.png"]

   forState:UIControlStateNormal];

}else {

UIImage *img=[UIImage imageNamed:@"plus.png"];

//NSLog(@"%d",img==nil);

[btnExpand setImage:img

   forState:UIControlStateNormal];

}

}else {

[btnExpand setImage:nil forState:UIControlStateNormal];

}

[label setText:node.title];

}

- (void)dealloc {

    [super dealloc];

}

 

 

@end

 

五、 在TreeViewController中展现树视图

 

1、接下来应该建立一个TableViewController,使用我们的TreeViewCell。新建类TreeViewController。

2、TreeViewController.h文件:

#import <Foundation/Foundation.h>

#import "TreeNode.h"

 

@interface TreeViewController : UITableViewController

<UITableViewDelegate,UITableViewDataSource>

{

TreeNode* tree;

NSMutableArray* nodes;

}

@end

3、TreeViewController.m文件:

#import "TreeViewController.h"

#import "TreeViewCell.h"

 

@implementation TreeViewController

-(void)viewDidLoad{

[super viewDidLoad];

tree=[[TreeNode alloc]init];

tree.deep=0;

tree.title=@"根节点";

TreeNode* node[10];

for (int i=0; i<10; i++) {

node[i]=[[TreeNode alloc]init];

node[i].title=[NSString stringWithFormat:@"节点%d",i];

node[i].key=[NSString stringWithFormat:@"%d",i];

}

    [node[0] addChild:node[1]];

[node[0] addChild:node[2]];

[node[0] addChild:node[3]];

[node[2] addChild:node[4]];

[node[2] addChild:node[5]];

[node[2] addChild:node[6]];

[node[6] addChild:node[7]];

[node[6] addChild:node[8]];

[node[3] addChild:node[9]];

[tree addChild:node[0]];

nodes=[[NSMutableArray alloc]init];

[TreeNode getNodes:tree :nodes];

}

#pragma mark ===table view datasource methods====

-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView{

return 1;

}

-(NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section{

return nodes.count;

}

-(UITableViewCell*)tableView:(UITableView *)tableViewcellForRowAtIndexPath:(NSIndexPath *)indexPath{

static NSString* cellid=@"cell";

TreeViewCell* cell=(TreeViewCell*)[tableViewdequeueReusableCellWithIdentifier:

   cellid];

if (cell==nil) {

cell=[[[TreeViewCellalloc]initWithStyle:UITableViewCellStyleDefault

 reuseIdentifier:cellid]autorelease];

}

TreeNode* node=[nodes objectAtIndex:indexPath.row];

[cell setOwner:self];

[cell setOnExpand:@selector(onExpand:)];

[cell setTreeNode:node];

return cell;

}

-(void)onExpand:(TreeNode*)node{

nodes=[[NSMutableArray alloc]init];

[TreeNode getNodes:tree :nodes];

[self.tableView reloadData];

}

@end

首先,继承TableViewController并实现UITableViewDelegate和UITableViewDataSource协议。

在viewDidLoad方法中,我们使用TreeNode构建了一棵树,并把树的根节点和所有需要展开的节点放到nodes数组中(请看TreeNode的getNodes方法是怎么定义的)。注意,因为一开始所有节点的expanded总是false(不展开),所以node数组中除了根节点外,没有其他元素。

tableView的数据源方法没有什么特别的。但对于TreeView,我们还需要实现一个方法(这里是onExpand方法,但其实叫什么名字无所谓),然后对所有cell使用setOnExpand把这个方法的selector传递给TreeViewCell,在TreeViewCell中,这个方法会在展开(+号)按钮点击时触发。

-(void)onExpand:(TreeNode*)node方法有一点特殊,它带了一个参数。由于在TreeViewCell中,触发该方法时用到了 performSelector:withObject:方式而不是普通的performSelect:发送,所以TreeViewCell有可能把这个单元格所包含的TreeNode对象传递到TreeViewController的onExpand:来。从而可以通过这个参数读到各个单元格的modal数据。

六、 一些改进>

 

1.无论我们需不需要,TreeView上总是会显示一个“根节点”,哪怕这个根节点并没有什么实际的用途。

如果我们可以控制节点是否需要显示就好了。要实现这一点,需要在TreeViewCell中增加一个新的变量:

BOOL hidden;//标志,节点是否隐藏

然后修改getNodes方法,将 [array addObject:root]; 修改为:

if(![root hidden])//只有节点被设置为“不隐藏”的时候才返回节点

[array addObject:root];

最后,把TreeViewController的loadView方法稍作修改,使根节点隐藏但同时展开:

tree.hidden=YES;

tree.expanded=YES;

 

这样,根节点不显示了,显示的是它已被展开的子节点“节点0” 。

2、节点左边的文件夹图标真是另人讨厌,我们可以把它替换成自己的图片吗?只需要在 tableView: cellForRowAtIndexPath:方法中修改TreeViewcell的image属性。

NSString* filename=[NSString

stringWithFormat:@"%d.png",[node.key intValue]+1];

UIImage* img=[UIImage imageNamed:filename];

[cell.imgIcon setImage:img];

 

但记住这些操作必须在[cell setTreeNode:node];语句之后,因为setTreeNode方法会将节点的image属性设为默认的文件夹图片,在此之前修改显然是没有用的:

图片似乎了大一点,把它们从(40*40)调整为默认的32*32 就好。

3.最后还有一个问题,上一级和下一级之间的缩进不是那么明显,我们可以调节缩进吗?

在TreeViewCell.h中,声明两个静态方法:

+(int) indent;

+(void)setIndent:(int)value;

在TreeViewCell.m中(注意,是在implementation,而不是interface中),声明一个静态变量: static int indent=15;//默认缩进值15

同时,实现那两个静态访问方法:

+(int)indent{

return indent;

}

+(void)setIndent:(int)value{

indent=value;

}

在setTreeNode方法中,替换所有的 15*node.deep 为indent*node.deep

在TreeViewController的viewDidLoad方法中,增加一句:[TreeViewCell setIndent:45];

现在的缩进显然大多了:

 

七、 进一步的封装

 

1、为了使我们的TreeView类更容易被程序员们使用,我们应当对其进行必要的封装。这样程序员们可以通过简单的继承或者实现某个我们定义的协议来使用它。这两种方式我们都可以采用,但我决定使用第一种,也就是把我们的实现封装成一个可以继承的超类,程序员要想使用它,必须继承并覆盖一系列的方法,这是似乎更容易使用些。

2、我们在interface中声明了三个给使用者覆盖的方法:

//如果你想呈现自己的树,在子类中覆盖此方法

-(void)initTree;

//如果你想在选中某一个节点时,发生自定义行为,在子类中覆盖此方法

-(void)onSelectedRow:(NSIndexPath *)indexPath;

//如果你想定义自己的单元格视图(比如更换默认的文件夹图标),在子类中覆盖此方法

-(void)configCell:(TreeViewCell *)cell :(TreeNode *)node;

然后修改implementation中的几个地方,调用这三个方法:

-(void)viewDidLoad{

[super viewDidLoad];

[self initTree];

}

-(void)tableView:(UITableView *)tableViewdidSelectRowAtIndexPath:(NSIndexPath *)indexPath{

[self onSelectedRow:indexPath];

}

-(UITableViewCell*)tableView:(UITableView *)tableViewcellForRowAtIndexPath:(NSIndexPath *)indexPath{

static NSString* cellid=@"cell";

TreeViewCell* cell=(TreeViewCell*)[tableViewdequeueReusableCellWithIdentifier:

   cellid];

if (cell==nil) {

cell=[[[TreeViewCellalloc]initWithStyle:UITableViewCellStyleDefault

 reuseIdentifier:cellid]autorelease];

}

TreeNode* node=[nodes objectAtIndex:indexPath.row];

[cell setOwner:self];

[cell setOnExpand:@selector(onExpand:)];

[cell setTreeNode:node];

[self configCell:cell :node];

return cell;

}

但是在initTree中必须放一些代码,这样当用户什么代码也没写的情况下,有一棵最基本的树显示在视图中:

-(void)initTree{

//NSLog(@"initTree===");

tree=[[TreeNode alloc]init];

tree.deep=0;

tree.title=@"根节点";

TreeNode* node[10];

for (int i=0; i<10; i++) {

node[i]=[[TreeNode alloc]init];

node[i].title=[NSString stringWithFormat:@"节点%d",i];

node[i].key=[NSString stringWithFormat:@"%d",i];

}

    [node[0] addChild:node[1]];

[node[0] addChild:node[2]];

[node[0] addChild:node[3]];

[node[2] addChild:node[4]];

[node[2] addChild:node[5]];

[node[2] addChild:node[6]];

[node[6] addChild:node[7]];

[node[6] addChild:node[8]];

[node[3] addChild:node[9]];

[tree addChild:node[0]];

nodes=[[NSMutableArray alloc]init];

[TreeNode getNodes:tree :nodes];

}

3、现在测试一下怎样通过继承来展现我们自己的树。新建TreeViewTestController类,继承TreeViewController。先不加入任何代码,运行效果如下:

 


 

4、覆盖父类方法initTree:

-(void)initTree{

[TreeViewCell setIndent:25];

[TreeViewCell setIcoWidth:40];

[TreeViewCell setIcoHeight:40];

[TreeViewCell setLabelMarginLeft:10];

tree=[[TreeNode alloc]init];

tree.deep=0;

tree.title=@"根节点";

tree.hidden=YES;

tree.expanded=YES;

//节点:播放最多

TreeNode* node01=[[TreeNode alloc]init];

node01.title=@"播放最多";

node01.key=@"01";

node01.expanded=YES;

//子节点:今日播放最多

TreeNode* node011=[[TreeNode alloc]init];

node01 1.title=@"今日播放最多";

node011.key=@"001";

node011.data=RANK_PLAY_TODAY;

[node01 addChild:node011];

//子节点:本周播放最多

TreeNode* node012=[[TreeNode alloc]init];

node012.title=@"本周播放最多";

node012.key=@"002";

node012.data=RANK_PLAY_WEEKLY;

[node01 addChild:node012];

//子节点:本月播放最多

TreeNode* node013=[[TreeNode alloc]init];

node013.title=@"本月播放最多";

node013.key=@"003";

node013.data=RANK_PLAY_MONTHLY;

[node01 addChild:node013];

//节点:评论最多

TreeNode* node02=[[TreeNode alloc]init];

node02.title=@"评论最多";

node02.key=@"02";

node02.expanded=YES;

//子节点:今日评论最多

TreeNode* node021=[[TreeNode alloc]init];

node021.title=@"今日评论最多";

node021.key=@"021";

node021.data=RANK_REMARK_TODAY;

[node02 addChild:node021];

//子节点:本周评论最多

TreeNode* node022=[[TreeNode alloc]init];

node022.title=@"本周评论最多";

node022.key=@"022";

node022.data=RANK_REMARK_WEEKLY;

[node02 addChild:node022];

//子节点:本月评论最多

TreeNode* node023=[[TreeNode alloc]init];

node023.title=@"本月评论最多";

node023.key=@"023";

node023.data=RANK_REMARK_MONTHLY;

[node02 addChild:node023];

[tree addChild:node01];

[tree addChild:node02];

nodes=[[NSMutableArray alloc]init];

[TreeNode getNodes:tree :nodes];

}

5、覆盖父类方法 configCell:

//如果你想定义自己的单元格视图(比如更换默认的文件夹图标),在子类中覆盖此方法

-(void)configCell:(TreeViewCell*)cell :(TreeNode*)node{

NSPredicate* predicate=[NSPredicate predicateWithFormat:

@"SELF IN{'01','02'}"];

if ([predicate evaluateWithObject:node.key]) {

NSString* filename=[NSStringstringWithFormat:@"%@.png",node.key];

UIImage* img=[UIImage imageNamed:filename];

[cell.imgIcon setImage:img];

}else {

[cell.imgIcon setImage:nil];

}

}

运行效果如下:

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 联通卡网络信号很差怎么办 电马桶马达坏了怎么办 我这么好看别人看不到怎么办 户口打回原籍不接受怎么办 小电充电宝丢失怎么办 qq连续聊天断了怎么办 胚胎怀疑在切口处怎么办 3D渲染没有材质怎么办 员工拒绝签收员工手册怎么办 二级密码错了三次怎么办 棉签掉到耳朵里怎么办 发财树叶子有黄斑怎么办 翠兰的颈枯萎了怎么办 翠兰主干软了怎么办 花叶子长白色粘粉末怎么办 水培转土培栀子花叶子蔫了怎么办 水冷空调水不循环怎么办 哺乳期乳房一个大一个小怎么办 我喝酒后喂奶了怎么办 磁盘目录不具有读写权限怎么办 玻纤网格布扎手怎么办 模拟城市5细菌太多怎么办 空气风犁叶子卷怎么办 晚上腿比早上粗怎么办 新疆公安边防改革新兵怎么办 专升本没有考上怎么办 摩托车漏检了2年怎么办 19年北京外地车怎么办 汽车遥控钥匙按键坏了怎么办 长安逸动噪音大怎么办 微信设置密码参数错误怎么办 太阳能电加热不加热怎么办 没报到换了工作怎么办 大学最后一年入伍入伍毕业证怎么办 当官不为民做主怎么办 去青海高反了怎么办 地暖地板低于客厅地面怎么办 9万月3分利息怎么办 免维护电瓶亏电怎么办 自煮小火锅吃完怎么办 孕囊形状是扁的怎么办