使用UITableView实现树视图

  本文的目的,是研究有没有可能在一个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, 屏宽, 屏高)];
    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:style reuseIdentifier: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=[[UIButton alloc]initWithFrame:CGRectMake((15*node.deep), 5, 32, 32)];
    [btnExpand addTarget:self action:@selector(onExpand:)forControlEvents:UIControlEventTouchUpInside];

    [imgIcon setImage:[UIImage imageNamed:@"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];
    ode[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 - UITableView DataSource
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
  return 1;
}
-(NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section{
  return nodes.count;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

  static NSString* cellid = @"cell";

  TreeViewCell* cell = (TreeViewCell*)[tableView dequeueReusableCellWithIdentifier:cellid];

  if (cell == nil) {

    cell = [[[TreeViewCell alloc]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 intindent=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 *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

  [self onSelectedRow:indexPath];
}

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

  static NSString* cellid = @"cell";

  TreeViewCell* cell = (TreeViewCell*)[tableView dequeueReusableCellWithIdentifier: cellid];

  if(cell == nil) {

    cell = [[[TreeViewCell alloc]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=[NSString stringWithFormat:@"%@.png",node.key];
    UIImage* img=[UIImage imageNamed:filename];
    [cell.imgIcon setImage:img];
  }else {
    [cell.imgIcon setImage:nil];
  }
}

运行效果如下:

时间: 2024-08-26 07:18:36

使用UITableView实现树视图的相关文章

C#-树视图TreeView---ShinePans

1.pyquery简介 python中的pyquery模块语法与jquery相近,可用来解析HTML文件.官方文档地址:https://pythonhosted.org/pyquery/ .通过HTML中的标签.id.给定的索引等来获取元素,使得解析HTML文件极为方便. 2.实例 2.1 爬取豆瓣电影页面中主演 右键chrome中的审查元素,观察到主演的标签为<a href="/celebrity/1005773/" rel="v:starring">

SharePoint 树视图自定义

树视图是提供的列表. 库和当前网站的子网站的分层视图的导航选项.树视图显示大多数网页的网站中,快速启动栏下方的一侧.默认情况下,禁用树视图. 如果您的网站具有复杂的层次结构,使用树视图,网站用户可以更方便地在网站层次结构中的不同内容之间(例如,当前网站中的库和子网站中的列表之间)导航.下面是树视图的一个示例. 在实际使用中,在某个文档库只想看到文档库当前的树视图,其他视图不需要该如何操作呢? 1.首先打开SharePoint Designer网站母版页,找到树视图ID为TreeViewNavig

IOS开发中UITableView(表视图)的性能优化及自定义Cell

IOS开发中UITableView(表视图)的滚动优化及自定义Cell IOS 开发中UITableView是非常常用的一个控件,我们平时在手机上看到的联系人列表,微信好友列表等都是通过UITableView实现的.UITableView这个控件中的列表的每一行是一个cell,当UITableView中cell数量特别大的时候,由于每次都需要alloc分配内存并初始化,会导致app运行不流畅,所以可以使用苹果提供的几个方法进行优化,我把这个过程记录下来供自己以后查阅. 当然,既然说到优化,那我们

SharePoint 创建站点地图树视图及格式枚举截图

SharePoint 创建站点地图树视图及格式枚举截图 SharePoint首页隐藏掉左侧导航以后,如果要以树视图呈现网站地图也很简单. 只需要复制v4.master,粘贴出v4_copy(1).master,签出,编辑.直接修改v4.master母版页是不明智的. 在PlaceHolderLeftNavBar中插入树视图.点击小三角,在选择数据源下拉列表新建数据源. 选中站点地图,指定ID为SiteMapDataSource,点击确定. F12在浏览器中预览. 回到SPD,点击树视图小三角,点

C#-树视图的实现以及TreeView属性---ShinePans

树视图TreeView 属性及方法 属性 说明 Nodes 获取分配给树视图控件的树节点集合 PathSeparator 获取或设置树节点路径所使用的分隔符串 SelesctedNode 获取或设置当树节点选定时所使用的图像列表索引值 ShowNodeToolTips 获取或设置一个值,用以指示树图中的树节点是否经过排序 VisibleCount 获取树视图控件黄总完全可见的树节点数目 CollapseAll 折叠所有的树节点 ExpandAll 展开所有的树节点 GetNodeAt 检索位于指

UITableView/UIScrollView内容视图下移

先来看看,普通控制上添加一个UITableView的情况:(设置tableView的背景颜色为蓝色) 再来看看实现的代码: UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width,self.view.bounds.size.height)]; [self.view addSubview:tableView]; self.tableView =

【swift_3】swift之UITableView和UINavigation视图控制器

Demo:链接: http://download.csdn.net/download/riven_wn/9401961 AppDelegate [objc] view plain copy var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { var VC = 

iOS:UITableView表格视图控件

UITableView:表格视图控件,继承滚动视图控件UIScrollView,(类似于UIPickerView选择器,它主要通过设置数据源代理和行为代理实现协议来设置单元格) 对表格的操作主要有:创建表格.设置单元格(行数.内容.行高).编辑单元格(删除单元格.插入单元格).移动单元格.标记单元格.修改单元格等. 一.表格式图的属性和行为: 1.基本属性: @interface UITableView : UIScrollView <NSCoding> @property (nonatomi

SAP CRM 树状视图(TREE VIEW)

树视图可以用于表示数据的层次. 例如:SAP CRM中的组织结构数据可以表示为树视图. 在SAP CRM Web UI的术语当中,没有像表视图(table view)或者表单视图(form view)那种专门的树视图类型.我们可以认为树视图是表视图的一种特例.没有可以用于创建树视图的向导.需要按以下特定的步骤手动改造已存在的视图. 首先,这个看起来挺难的,但是你做的次数越多,就会越容易理解它.在本文,我将创建一个如下模样的树视图. 如你所见,它有两级结构.第一级展示LEAD ID,下一级展示客户