本文的代码例子 : "Cell行高自适应.zip" http://vdisk.weibo.com/s/Gb9Mt
下面我们来看看代码。我需要一个第三方库EGO异步下载、addtion文件夹和StringUtil文件以及Comment、Status、User这三个数据模型,这篇文章的主要目的是讲解如何计算Cell的高度,jSON数据分类见上面那篇文章,上面说的在代码例子中都有的。将它们考入你的工程。
实现思路:
/*
File.strings
Cell行高自适应
Created by 杜甲 on 13-4-2.
Copyright (c) 2013年杜甲. All rights reserved.
实现类似微博这样的项目需要怎么做?
1、首先,要获取数据。(这部分在网络请求中介绍)
2、其次,将数据分类保存在对应的模型中。(这个技术在大字典中介绍)
3、最后,将获取的数据显示在屏幕上。
一、为什么要学习Cell的行高自适应?
因为,我们要将获取的内容合理的显示在屏幕上。
二、实现步骤
1、要先将数据发送到显示界面的类ShowDataViewController中
2、计算获取每个数据单元需要在屏幕上占多大的空间,这个功能的实现在类Algorithm中,详细解释在类中。
3、创建一个UITableViewCell类型的类:StatusCell这个类的作用就是为了设计如何在Cell中显示数据进行布局。
三、技术难点:
1、控件.autoresizingMask属性:每个控件几乎都有autoresizingMask的属性,该属性的作用是将改控件固定在相对的某个位置,例如:要将位置固定在当前cell的左下方,需要按下面的格式写UIViewAutoresizingFlexibleTopMargin代表是上方、UIViewAutoresizingFlexibleRightMargin是右方的意思。因此写代码设置控件的相对位置需要写出相反的方向。
*/
我们先实现一个计算Cell高度的类:
Algorithm.h
[plain] view plaincopyPRint?
<span >#import <Foundation/Foundation.h> #import "Status.h" @interface Algorithm : NSObject -(CGFloat)cellHeight:(NSString*)contentText with:(CGFloat)with; //计算Cell的高度 -(CGFloat)calculation:(NSIndexPath*)indexPath StatusArr:(NSMutableArray*)statusArr; @end </span>
Algorithm.m
[plain] view plaincopyprint?
<span >#import "Algorithm.h" #define kTextViewPadding 16.0 #define kLineBreakMode1 UILineBreakModeWordWrap @implementation Algorithm -(CGFloat)calculation:(NSIndexPath *)indexPath StatusArr:(NSMutableArray *)statusArr { NSInteger row = indexPath.row; if (row >= [statusArr count]) { return 1; } Status* status = [statusArr objectAtIndex:row]; NSString* url = status.retweetedStatus.thumbnailPic; NSString* url2 = status.thumbnailPic; CGFloat height = 0.0f; if (status.retweetedStatus && ![status.retweetedStatus isEqual:[NSNull null]]) { //调用下面-(CGFloat)cellHeight:(NSString *)contentText with:(CGFloat)with的方法计算一段文字所占的高度 height = [self cellHeight:status.text with:320.0f] + [self cellHeight:[NSString stringWithFormat:@"%@:%@",status.retweetedStatus.user.screenName,status.retweetedStatus.text] with:300.0f] - 22.0f; NSLog(@"status.retweetedStatus,height = %f",height); } else { height = [self cellHeight:status.text with:320.0f]; //后加调式用 height+=40; NSLog(@"height = %f",height); } if ((url && [url length] != 0) || (url2 && [url2 length] != 0)) { //如果有图片将高度增加80个像素 height = height + 80; NSLog(@"height = %f",height); } return height; }</span>
[plain] view plaincopyprint?
<span >//计算一段文字的高度 -(CGFloat)cellHeight:(NSString *)contentText with:(CGFloat)with { UIFont* font = [UIFont systemFontOfSize:14]; //sizeWithFont:字体大小constrainedToSize:显示的范围lineBreakMode: CGSize size = [contentText sizeWithFont:font constrainedToSize:CGSizeMake(with - kTextViewPadding , 3000000.f) lineBreakMode:kLineBreakMode1]; CGFloat height = size.height + 44; return height; } </span>
接下来我们创建一个继承自UITableViewCell
StatusCell.h
[plain] view plaincopyprint?
<span >#import <UIKit/UIKit.h> #import "Status.h" #import "EGOImageView.h" @interface StatusCell : UITableViewCell @property (nonatomic, retain) UITableViewCell* tableViewCell; @property (nonatomic, retain) UITableView* tableVw; @property (nonatomic, retain) NSMutableArray* StatusArr; @property (retain, nonatomic) UILabel *countLB; //数量 @property (retain, nonatomic) EGOImageView *avatarImage; @property (retain, nonatomic) UITextView *contentTF; //微博正文 @property (retain, nonatomic) UILabel *userNameLB; @property (retain, nonatomic) UIImageView *bgImage; //微博背景 @property (retain, nonatomic) EGOImageView *contentImage; //正文的图片 @property (retain, nonatomic) UIView *retwitterMainV; //转发的View @property (retain, nonatomic) EGOImageView *retwitterBgImage; //转发的背景 @property (retain, nonatomic) UITextView *retwitterContentTF;//转发的正文 @property (retain, nonatomic) EGOImageView *retwitterContentImage;//转发正文的图片 //@property (assign, nonatomic) id<StatusViewCellDelegate> delegate; @property (retain, nonatomic) NSIndexPath *cellIndexPath; @property (retain, nonatomic) UILabel *fromLB; @property (retain, nonatomic) UILabel *timeLB; @property (retain, nonatomic) UILabel* sourceLB; //微博来源 -(CGFloat)setTFHeightWithImage:(BOOL)hasImage haveRetwitterImage:(BOOL)haveRetwitterImage; -(void)setupCell:(Status*)status; -(void)controlPosition; @end </span>
StatusCell.m
本文中CELL中的控件全部用代码实现,所以下面的代码比较多。
[plain] view plaincopyprint?
<span >#define IMAGE_VIEW_HEIGHT 80.0f #import "StatusCell.h" @implementation StatusCell - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { // Initialization code //点击Cell时背景不变蓝 self.selectionStyle = UITableViewCellSelectionStyleNone; self.bgImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"table_header_bg.png"]]; self.bgImage.frame = CGRectMake(0, 29 , 320, 73); // self.bgImage.contentMode = UIViewContentModeScaleToFill; [self.contentView addSubview:self.bgImage]; self.avatarImage = [[EGOImageView alloc] initWithFrame: CGRectMake(5.0 , 5.0, 22, 22)]; self.avatarImage.image = [UIImage imageNamed:@"loadingImage_50x118.png"]; self.avatarImage.contentMode = UIViewContentModeScaleToFill; [self.contentView addSubview:self.avatarImage]; self.userNameLB = [[UILabel alloc] initWithFrame:CGRectMake(35, 0, 165, 28)]; self.userNameLB.font = [UIFont systemFontOfSize:17.0]; self.userNameLB.backgroundColor = [UIColor clearColor]; [self.contentView addSubview:self.userNameLB]; self.contentTF = [[UITextView alloc] init]; self.contentTF.editable = NO; self.contentTF.frame = CGRectMake(0, 29 , 320, 73); self.contentTF.font = [UIFont systemFontOfSize:14.0]; //UITextView如果有网址可以直接打开 self.contentTF.dataDetectorTypes = UIDataDetectorTypeLink; //开启多点触摸 self.contentTF.multipleTouchEnabled = YES; self.contentTF.backgroundColor = [UIColor clearColor]; [self.contentView addSubview:self.contentTF]; self.contentImage = [[EGOImageView alloc] initWithImage:[UIImage imageNamed:@"loadingImage_50x118.png"]]; self.contentImage.frame = CGRectMake(90, 84, 140, 80); self.contentImage.backgroundColor = [UIColor clearColor]; [self.contentView addSubview:self.contentImage]; self.retwitterMainV = [[UIView alloc] initWithFrame:CGRectMake(0, 166, 320, 160)]; self.retwitterMainV.contentMode = UIViewContentModeScaleaspectFill; self.retwitterMainV.backgroundColor = [UIColor clearColor]; [self.contentView addSubview:self.retwitterMainV]; //转发背景图 self.retwitterBgImage = [[EGOImageView alloc] initWithFrame:CGRectMake(-10, -10, 320, 100)]; self.retwitterBgImage.contentMode = UIViewContentModeScaleToFill; self.retwitterBgImage.backgroundColor = [UIColor clearColor]; [self.retwitterMainV addSubview:self.retwitterBgImage]; //转发的正文 self.retwitterContentTF = [[UITextView alloc] initWithFrame:CGRectMake(10, 5, 300, 150)]; self.retwitterContentTF.editable = NO; self.retwitterContentTF.backgroundColor = [UIColor clearColor]; [self.retwitterMainV addSubview:self.retwitterContentTF]; //转发的图片 self.retwitterContentImage = [[EGOImageView alloc] initWithFrame:CGRectMake(90, 100, 140, 80)]; self.retwitterContentImage.backgroundColor = [UIColor clearColor]; self.retwitterContentImage.contentMode = UIViewContentModeScaleAspectFit; [self.retwitterMainV addSubview:self.retwitterContentImage]; self.countLB = [[UILabel alloc] init]; self.countLB.backgroundColor = [UIColor clearColor]; [self.countLB setFont:[UIFont fontWithName:@"Arial Rounded MT Bold" size:13.0]]; self.countLB.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleLeftMargin; [self.contentView addSubview:self.countLB]; self.sourceLB = [[UILabel alloc] init]; self.sourceLB.backgroundColor = [UIColor clearColor]; [self.sourceLB setFont:[UIFont fontWithName:@"Arial Rounded MT Bold" size:13.0]]; //每个控件几乎都有autoresizingMask的属性,该属性的作用是将改控件固定在相对的某个位置,例如:要将位置固定在当前cell的左下方,需要按下面的格式写UIViewAutoresizingFlexibleTopMargin代表是上方、UIViewAutoresizingFlexibleRightMargin是右方的意思。因此写代码设置控件的相对位置需要写出相反的方向。 self.sourceLB.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleRightMargin; [self.contentView addSubview:self.sourceLB]; self.timeLB = [[UILabel alloc] initWithFrame:CGRectMake(200, 0, 165, 28)]; [self.timeLB setFont:[UIFont fontWithName:@"Arial Rounded MT Bold" size:13.0]]; self.timeLB.backgroundColor = [UIColor clearColor]; [self.contentView addSubview:self.timeLB]; } return self; }</span>
[plain] view plaincopyprint?
<span >-(void)setupCell:(Status *)status { //头像图片的网址 NSURL* avatarImageUrl = [NSURL URLWithString:status.user.profileImageUrl]; //正文图片的网址 NSURL* contentImageUrl = [NSURL URLWithString:status.thumbnailPic]; //转发图片的方法 NSURL* retweetedStatusUrl = [NSURL URLWithString:status.retweetedStatus.thumbnailPic]; self.contentTF.text = status.text; self.userNameLB.text = status.user.screenName; self.timeLB.text = [status timestamp]; self.avatarImage.imageURL =avatarImageUrl; self.countLB.text = [NSString stringWithFormat:@"评论:%d 转发:%d",status.commentsCount,status.retweetsCount]; self.fromLB.text = [NSString stringWithFormat:@"来自:%@",status.source]; self.timeLB.text = status.timestamp; self.sourceLB.text = [NSString stringWithFormat:@"来源:%@",status.source]; Status *retwitterStatus = status.retweetedStatus; //有转发 if (retwitterStatus && ![retwitterStatus isEqual:[NSNull null]]) { self.retwitterMainV.hidden = NO; self.retwitterContentTF.text = [NSString stringWithFormat:@"%@:%@",status.retweetedStatus.user.screenName,retwitterStatus.text]; self.contentImage.hidden = YES; if (![retweetedStatusUrl isEqual:[NSNull null]]) { self.retwitterContentImage.imageURL = retweetedStatusUrl; } NSString *url = status.retweetedStatus.thumbnailPic; self.retwitterContentImage.hidden = url != nil && [url length] != 0 ? NO : YES; [self setTFHeightWithImage:NO haveRetwitterImage:url != nil && [url length] != 0 ? YES : NO];//计算cell的高度,以及背景图的处理 } //无转发 else { self.retwitterMainV.hidden = YES; if (![contentImageUrl isEqual:[NSNull null]]) { self.contentImage.imageURL = contentImageUrl; } NSString *url = status.thumbnailPic; self.contentImage.hidden = url != nil && [url length] != 0 ? NO : YES; [self setTFHeightWithImage:url != nil && [url length] != 0 ? YES : NO haveRetwitterImage:NO];//计算cell的高度,以及背景图的处理 } }</span>
[plain] view plaincopyprint?
<span >//计算cell的高度,以及背景图的处理 -(CGFloat)setTFHeightWithImage:(BOOL)hasImage haveRetwitterImage:(BOOL)haveRetwitterImage { [self.contentTF layoutIfNeeded]; //博文Text CGRect frame = self.contentTF.frame; frame.size = self.contentTF.contentSize; self.contentTF.frame = frame; //设置正文的背景 self.bgImage.frame = frame; //转发博文Text frame = self.retwitterContentTF.frame; frame.size = self.retwitterContentTF.contentSize; self.retwitterContentTF.frame = frame; //调整转发背景 self.retwitterBgImage.image = [[UIImage imageNamed:@"timeline_rt_border_t.png"] stretchableImageWithLeftCapWidth:130 topCapHeight:7]; frame.origin.x -=10; frame.origin.y -=5; frame.size.width = frame.size.width +15; self.retwitterBgImage.frame = frame; //转发的主View frame = self.retwitterMainV.frame; if (haveRetwitterImage) frame.size.height = self.retwitterContentTF.frame.size.height + IMAGE_VIEW_HEIGHT + 15; else frame.size.height = self.retwitterContentTF.frame.size.height + 15; if(hasImage) frame.origin.y = self.contentTF.frame.size.height + self.contentTF.frame.origin.y + IMAGE_VIEW_HEIGHT; else frame.origin.y = self.contentTF.frame.size.height + self.contentTF.frame.origin.y; self.retwitterMainV.frame = frame; //转发的图片 frame = self.retwitterContentImage.frame; frame.origin.y = self.retwitterContentTF.frame.size.height; frame.size.height = IMAGE_VIEW_HEIGHT; self.retwitterContentImage.frame = frame; //正文的图片 frame = self.contentImage.frame; frame.origin.y = self.contentTF.frame.size.height + self.contentTF.frame.origin.y - 5.0f; frame.size.height = IMAGE_VIEW_HEIGHT; self.contentImage.frame = frame; //背景设置 self.bgImage.image = [[UIImage imageNamed:@"table_header_bg.png"] stretchableImageWithLeftCapWidth:10 topCapHeight:10]; // = self.retwitterContentTF.frame; return self.contentTF.contentSize.height; }</span>
[plain] view plaincopyprint?
<span >-(void)controlPosition { self.countLB.frame = CGRectMake(198, self.frame.size.height - 20, 142, 21); self.sourceLB.frame = CGRectMake(20, self.frame.size.height - 20, 152, 21); }</span>
再创建一个继承自的UIViewController类ShowDataViewController这个类用来创建tableView以及显示Cell中的内容。
ShowDataViewController.h
[plain] view plaincopyprint?
<span >#import <UIKit/UIKit.h> #import "Algorithm.h" @interface ShowDataViewController : UIViewController<UITableViewDataSource,UITableViewDelegate> //创建一个UITableView @property (strong, nonatomic) UITableView* tb; //Algorithm类的作用是根据内容计算Cell的高度 @property (strong, nonatomic) Algorithm* algorithm; //用一个NSMutableArray接收Status类的对象 @property (strong , nonatomic) NSMutableArray* statusArr; @end </span>
ShowDataViewController.m
[plain] view plaincopyprint?
<span >-(id)init { if (self = [super init]) { //为什么要在init方法中注册消息中心,这样可以在AppDelegate方法中初始化这个类时注册消息中心 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getArray:) name:@"getArray" object:nil]; } return self; }</span>
[plain] view plaincopyprint?
<span >-(void)getArray:(NSNotification*)aNotification { //接收传过来的数组,数组中存有Status类的对象 self.statusArr = aNotification.object; }</span>
[plain] view plaincopyprint?
<span >- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.tb = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 548) style:UITableViewStylePlain]; self.tb.delegate = self; self.tb.dataSource = self; [self.view addSubview:self.tb]; self.algorithm = [[Algorithm alloc] init]; }</span>
[plain] view plaincopyprint?
<span >- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.statusArr count]; }</span>
[plain] view plaincopyprint?
<span >-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { //调用Algorithm类的方法计算cell的高度 这个方法如何实现见Algorithm类中的注释 CGFloat height = [self.algorithm calculation:indexPath StatusArr:self.statusArr]; return height; }</span>
[plain] view plaincopyprint?
<span >- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { int row = indexPath.row; static NSString* strIdentifier = @"StatusCell"; // // //自定义Cell只需要将初始化的类变成自定义的Cell StatusCell* cell = [tableView dequeueReusableCellWithIdentifier:strIdentifier]; if (cell == nil) { cell = [[StatusCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:strIdentifier]; } //下面这个方法是用来调整控件坐标的 [cell controlPosition]; //Status是储存微博信息的类,将数组中的微博类的对象按照行号取出 Status* status = [self.statusArr objectAtIndex:row]; NSLog(@"row = %d",row); //下面这个方法是将微博信息放在Cell中 [cell setupCell:status]; return cell; } </span>
接下来我们在ViewController类中添加如下代码
ViewController.h
[plain] view plaincopyprint?
<span >@interface ViewController : UIViewController -(IBAction)changeVC:(id)sender; -(IBAction)sendData:(id)sender; @end </span>
自己在XIB中拖2个button与下面两个方法相关联吧
[plain] view plaincopyprint?
<span >-(IBAction)sendData:(id)sender { //将JSON格式的数据文件的路径找出 NSString* path = [[NSBundle mainBundle] pathForResource:@"jsonTest" ofType:@"json"]; //将数据放入NSData中 NSData* data = [NSData dataWithContentsOfFile:path]; //JSON解析 NSDictionary* dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil]; // NSLog(@"dic = %@",dic); NSArray* arr = [dic objectForKey:@"statuses"]; NSLog(@"%d",arr.count); NSLog(@"arr = %@",arr); //初始化一个数组 NSMutableArray* tempArr = [NSMutableArray array]; //将数组中的字典放入Status模型中,大家在这里会有一个疑问将数组中的字典都放入模型中,怎么区分不同数组中的数据? for (NSDictionary* item in arr) { //答案在statusWithJsonDictionary:的类方法中见该方法注释 Status* status = [Status statusWithJsonDictionary:item]; //将Status类型的对象放入数组中 [tempArr addObject:status]; } //将tempArr数组通过消息中心发送到@"getArray" 这个名字的消息对应的方法中 [[NSNotificationCenter defaultCenter] postNotificationName:@"getArray" object:tempArr]; } </span>
[plain] view plaincopyprint?
<span >-(IBAction)changeVC:(id)sender { //切换视图控制器 [[NSNotificationCenter defaultCenter] postNotificationName:@"changeVC" object:nil]; } </span>
我们还要在AppDelegate类中添加如下代码。
[plain] view plaincopyprint?
<span >#import <UIKit/UIKit.h> <span >#import "ShowDataViewController.h" @class ViewController;</span><span > </span> @interface AppDelegate : UIResponder <UIapplicationDelegate> @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) ViewController *viewController; <span >@property (strong, nonatomic) ShowDataViewController* showVC; </span> @end</span>
AppDelegate.m
[plain] view plaincopyprint?
<span >#import "AppDelegate.h" #import "ShowDataViewController.h" <span >#import "ViewController.h"</span> @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {</span>
[plain] view plaincopyprint?
<span ><span > </span>//为了注册消息</span>
[plain] view plaincopyprint?
<span > <span > [[ShowDataViewController alloc] init];</span></span>
[plain] view plaincopyprint?
<span ><span >//注册消息为了切换视图控制器 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeVC) name:@"changeVC" object:nil];</span> self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil]; self.showVC = [[ShowDataViewController alloc] init]; self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; return YES; }</span>
[plain] view plaincopyprint?
<span >-(void)changeVC { self.window.rootViewController = self.showVC; }</span>