《iOS 高级编程》之Tableview进阶指南

来源:互联网 发布:js判断是否显示隐藏 编辑:程序博客网 时间:2024/05/03 04:23

学习如何进阶使用UITableView,带给应用更高级的观感(look and feel)

●    学习如何开发自己定制的UITableView类,模仿iMessage应用的观感

●    为一个基于分组的UITableView实现下钻逻辑


    在iOS应用中呈现数据时,UITableView可能是最经常使用的用户界面对象。在本章中,将学习到以超越标准实现的方式使用UITableView,并理解UITableView类的工作方式。你会创建一个聊天视图控制器,它支持定制的单元格和灵活的行高,以及下钻功能的实现,能够将多个对象的多个分类进行分组,从而生成一个高级的用户界面。最后,你会为表格视图的实现添加搜索功能。

2.1  理解UITableView

UITableView直接继承于UIScrollView类,从而给它带来直向(译者注:横向和纵向)滚动的能力。当想要使用UITableView时,必须首先创建UITableView类的实例,将它指向UIView控件而使其可见,并且建立一个datasource对象和一个负责与UITableView进行交互的delegate对象。

2.1.1  datasource和delegate

每一个UITableView都需要datasource和delegate这两个对象。datasource对象为UITableView提供数据。通常,datasource对象使用NSArray类或者NSDictionary类在内部存储数据,并且根据需要将数据提供给表视图。delegate对象必须实现UITableViewDelegate和UITableViewDataSource这两个协议。

UITableViewDelegate协议定义了几个方法,delegate对象需要实现其中至少三个方法。

delegate对象必须实现的方法有:

●     tableview:numberOfRowsInSection:

●     numberOfSectionsInTableView:

●     tableview:cellForRowAtIndexPath:

启动Xcode开发环境,使用Single View ApplicationProject模板创建新项目,并使用如图2-1中所示的配置将其命名为PlainTable。

图1

使用Interface Builder工具打开YDViewController.xib文件,并将一个UITableView控件添加到该窗口中。使用Assistant Editor工具为这个UITableView控件创建一个属性。也需要设置Referencing Outlets一栏中的datasource和delegate指向UITableView对象。确保YDViewController.xib文件看起来如图2-2中所示。


图2

打开YDViewController.h文件,创建名为rowData的NSMutableArray对象充当datasource,如代码清单2-1中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-1  Chapter2/PlainTable/YDViewController.h  
  2.     #import <UIKit/UIKit.h>  
  3.   
  4. @interface YDViewController : UIViewController  
  5.   
  6. @property (weak, nonatomic) IBOutlet UITableView *mTableView;  
  7. @property(nonatomic,strong) NSMutableArray* rowData;  
  8.   
  9. @end  
  10. </span>  

打开YDViewController.m文件,实现如代码清单2-2中所示的代码,关于这段代码,会在代码清单后详细说明。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-2  Chapter2/PlainTable/YDViewController.m  
  2. #import "YDViewController.h"  
  3.   
  4. @interface YDViewController ()  
  5.   
  6. @end  
  7.   
  8. @implementation YDViewController  
  9.   
  10. - (void)viewDidLoad  
  11. {  
  12.     [super viewDidLoad];  
  13.     // Do any additional setup after loading the view, typically from a nib.  
  14.     [self loadData];  
  15. }  
  16. -(void)loadData  
  17. {  
  18.     if (self.rowData!=nil)  
  19.         {  
  20.         [self.rowData removeAllObjects];  
  21.         self.rowData=nil;  
  22.           
  23.         }  
  24.     self.rowData = [[NSMutableArray alloc] init];  
  25.     for (int i=0 ; i<100;i++)  
  26.         {  
  27.         [self.rowData addObject:[NSString stringWithFormat:@"Row: %i",i]];  
  28.         }  
  29.     //now my datasource if populated let's reload the tableview  
  30.     [self.mTableView reloadData];  
  31. }  
  32. #pragma mark UITableView delegate  
  33. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {  
  34.     return 1;  
  35. }  
  36.   
  37. - (NSInteger)tableView:(UITableView *)tableView   
  38.     numberOfRowsInSection:(NSInteger)section {  
  39.       
  40.    return [self.rowData count];  
  41. }  
  42. - (UITableViewCell *)tableView:(UITableView *)tableView   
  43.      cellForRowAtIndexPath:(NSIndexPath *)indexPath {  
  44.     static NSString *CellIdentifier = @"Cell";  
  45.     UITableViewCell *cell = (UITableViewCell *)[tableView   
  46.         dequeueReusableCellWithIdentifier:CellIdentifier];  
  47.     if (cell == nil) {  
  48.         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault   
  49.                  reuseIdentifier:CellIdentifier];  
  50.     }  
  51.     cell.selectionStyle = UITableViewCellSelectionStyleNone;  
  52.     cell.textLabel.text = [self.rowData objectAtIndex:indexPath.row];  
  53.     return cell;  
  54. }  
  55. -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:  
  56.        (NSIndexPath *)indexPath  
  57. {  
  58.     [tableView deselectRowAtIndexPath:indexPath animated:YES];  
  59. }  
  60. - (void)didReceiveMemoryWarning  
  61. {  
  62.     [super didReceiveMemoryWarning];  
  63.     // Dispose of any resources that can be recreated.  
  64. }  
  65.   
  66. @end  
  67. </span>  

下面对这段代码进行分解,向你解释代码中各方法的作用。

在viewDidLoad方法中,调用了本地方法loadData,该方法创建了一个带有100个记录的NSMutableArray对象,并将reloadData消息发送给self.mTableView对象。

reloadData方法迫使mTableView对象通过调用delegate方法重新加载数据,并更新用户界面。

在#pragma mark UITableView delegate标记语句之后,需要实现表视图运行所必须的delegate对象的最小方法集合。

调用numberOfSectionsInTableView: 这个delegate方法来决定UITableView控件的section的数量。如果使用UITableViewStylePlain风格,UITableView控件的section数通常是1。后面将会学习到带有下钻功能的例子,如果使用例子中那种风格的section,则需要返回实际的section的数量。

当渲染单元格时,会调用tableview:cellForRowAtIndexPath:这个delegate方法。这个方法恰好是布局UITableViewCell的地方(UITableView中的一行)。现在,先简单地创建一个UITableviewCell,如果单元格仍然可用的话,试着在内存中重用它。

为了显示rowData数组中的正确的行,需要将[rowData objectAtIndex :indexPath.row];方法的返回值赋给cell.textLabel.text属性。

当用户以单击某行的方式选择该行时,会调用tableview:didSelectRowAtIndexPath:这个delegate方法。deselectRowAtIndexPath:animated:的delegate方法会取消这一行的选择,因此单元格不会保持高亮的状态。

如果想要保持选择状态仍然可见,那么请省略这行代码。

当应用运行时,结果如图2-3中所示。

图3

2.1.2  滚动

由于UITableView对象继承于UIScrollView类,因此它本身拥有完全的滚动功能。然而,在某些情况下,例如在UITableView中添加一个新行,或者删除一行时,可能要直接滚动到UITableView中的某个位置。

可以通过调用UITableView的scrollToRowAtIndexPath:atScrollPosition:animated:方法,获得UITableView上基于代码的滚动效果。这个方法传入的第一个参数是NSIndexPath类型的对象。NSIndexPath对象表示到嵌套数组集合树上的某一特定节点的路径。这个路径称为索引路径。在iOS应用中,用NSIndexPath对象来确定到表格视图内的行和section的路径。调用NSIndexPath类的indexPathForRow:inSection:方法,传入行和section的索引数字,通过这种方式可以创建NSIndexPath的实例。

启动Xcode开发环境,使用SingleView Application Project模板创建一个新项目,并使用如图2-4中所示的配置,将其命名为ScrollingTable。

使用Interface Builder工具打开YDViewController.xib文件,创建一个用户界面,如图2-5中所示。

图4

图5

如代码清单2-3中所示,建立YDViewController.h文件。作为前一个例子的补充,为引入的两个UIButton添加两个动作。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-3  Chapter2/ScrollingTable/YDViewController.h  
  2. #import <UIKit/UIKit.h>  
  3.   
  4. @interface YDViewController : UIViewController  
  5.   
  6. @property (weak, nonatomic) IBOutlet UITableView *mTableView;  
  7. @property(nonatomic,strong) NSMutableArray* rowData;  
  8. - (IBAction)scrollToTop:(UIButton *)sender;  
  9. - (IBAction)scrollToBottom:(UIButton *)sender;  
  10.   
  11. @end  
  12. </span>  

YDViewController.m文件的实现与前面的代码清单2-2中的类似,唯一的区别在于此时scrollToTop:和scrollToBottom:这两个方法的实现如代码清单2-4中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-4  Chapter2/ScrollingTable/YDViewController.m  
  2. #import "YDViewController.h"  
  3.   
  4. @interface YDViewController ()  
  5.   
  6. @end  
  7.   
  8. @implementation YDViewController  
  9.   
  10. - (void)viewDidLoad  
  11. {  
  12.     [super viewDidLoad];  
  13.     // Do any additional setup after loading the view, typically from a nib.  
  14.     [self loadData];  
  15. }  
  16. -(void)loadData  
  17. {  
  18.     if (self.rowData!=nil)  
  19.         {  
  20.         [self.rowData removeAllObjects];  
  21.         self.rowData=nil;  
  22.           
  23.         }  
  24.     self.rowData = [[NSMutableArray alloc] init];  
  25.     for (int i=0 ; i<100;i++)  
  26.         {  
  27.         [self.rowData addObject:[NSString stringWithFormat:@"Row: %i",i]];  
  28.         }  
  29.     //now my datasource if populated let's reload the tableview  
  30.     [self.mTableView reloadData];  
  31. }  
  32. #pragma mark UITableView delegates  
  33. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {  
  34.     return 1;  
  35. }  
  36.   
  37. - (NSInteger)tableView:(UITableView *)tableView   
  38.     numberOfRowsInSection:(NSInteger)section {  
  39.       
  40.     return [self.rowData count];  
  41. }  
  42. - (UITableViewCell *)tableView:(UITableView *)tableView   
  43.     cellForRowAtIndexPath:(NSIndexPath *)indexPath {  
  44.     static NSString *CellIdentifier = @"Cell";  
  45.     UITableViewCell *cell = (UITableViewCell *)[tableView   
  46.      dequeueReusableCellWithIdentifier:CellIdentifier];  
  47.     if (cell == nil) {  
  48.         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault   
  49.                  reuseIdentifier:CellIdentifier];  
  50.     }  
  51.     cell.selectionStyle = UITableViewCellSelectionStyleNone;  
  52.     cell.textLabel.text = [self.rowData objectAtIndex:indexPath.row];  
  53.     return cell;  
  54. }  
  55. -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:  
  56.    (NSIndexPath *)indexPath  
  57. {  
  58.     [tableView deselectRowAtIndexPath:indexPath animated:YES];  
  59. }  
  60. - (void)didReceiveMemoryWarning  
  61. {  
  62.     [super didReceiveMemoryWarning];  
  63.     // Dispose of any resources that can be recreated.  
  64. }  
  65.   
  66. - (IBAction)scrollToTop:(UIButton *)sender  
  67. {  
  68.     NSIndexPath *topRow = [NSIndexPath indexPathForRow:0 inSection:0];  
  69.     [self.mTableView scrollToRowAtIndexPath:topRow   
  70.      atScrollPosition:UITableViewScrollPositionTop animated:YES];  
  71. }  
  72.   
  73. - (IBAction)scrollToBottom:(UIButton *)sender  
  74. {  
  75.     NSIndexPath *bottomRow = [NSIndexPath indexPathForRow:  
  76.       [self.rowData count]-1 inSection:0];  
  77.     [self.mTableView scrollToRowAtIndexPath:bottomRow   
  78.        atScrollPosition:UITableViewScrollPositionBottom animated:YES];  
  79. }  
  80. @end  
  81. </span>  

在scrollToTop:方法中,创建一个NSIndexPath对象的实例,把indexPathForRow的值置为0,可以将表视图滚动至顶部。在scrollToBottom:方法中,使用[self.rowData count]-1的值创建NSIndexPath实例,可以将表视图滚动至底部。

用所创建的NSIndexPath对象调用scrollToRowAtIndexPath:atScrollPosition:animated:方法时,mTableView控件既可以滚动到表格的顶部,也可以滚动到表格的底部。

这一实现的结果如图2-6和图2-7中所示。

                                  图6                                                                                                                                                                                                            图7

2.2  构建聊天视图控制器

在本节中,将开发一个聊天视图控制器模拟iMessage以及其他即时通信应用的行为。为此,将学习如何使用灵活的单元格高度和定制的单元格创建一个定制的UITableView的实例。

最终的应用看起来如图2-8所示。

启动Xcode开发环境,用Single View ApplicationProject模板创建一个新项目,使用如图2-9所示的配置,将其命名为YDChatApp。

本例中所使用的图片,可以从本章的下载文件中获得。

YDViewController类会呈现即将开发的定制的UITableView,而且不使用Interface Builder工具开发。所有的UI代码是YDViewController.m文件的一个组成部分。


2.2.1  构建datasource

你将不会使用标准的UITableView,但是为了支持各种不同的聊天泡泡和section,会创建带有特定行为的定制的UITableView对象,并且使用定制的单元格。基于这个原因,开始时会编码实现一个定制的datasource对象,并被挂接到定制的UITableView上。创建一个继承于NSObject类的新协议,将其命名为YDChatTableViewDataSource。协议的源代码如代码清单2-5中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-5  Chapter2/YDChatApp/YDChatTableViewDataSource.h  
  2. #import <Foundation/Foundation.h>  
  3.   
  4. @class YDChatData;  
  5. @class YDChatTableView;  
  6. @protocol YDChatTableViewDataSource <NSObject>  
  7.   
  8. - (NSInteger)rowsForChatTable:(YDChatTableView *)tableView;  
  9. - (YDChatData *)chatTableView:(YDChatTableView *)tableView   
  10.    dataForRow:(NSInteger)row;  
  11.   
  12. @end  
  13. </span>  

这个协议直接继承于NSObject类,其中定义了必须在YDViewController类里实现的两个方法,定义定制的UITableView的地方就是这里。

2.2.2  构建聊天数据对象

为了使生活更轻松,定义一个名为YDChatData的对象,用来保存一条聊天消息的相关信息。可以用聊天的用户、时间戳、文字或者图片来初始化这个对象。枚举类型YDChatType有两种可能的值,ChatTypeMine和ChatTypeSomeone,用来负责聊天消息在UITableView上的位置。创建一个继承于NSObject的新的Objective-C类,将其命名为YDChatData。

YDChatData.h文件的源代码如代码清单2-6中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-6  Chapter2/YDChatApp/YDChatData.h  
  2. #import <Foundation/Foundation.h>  
  3. @class YDChatUser;  
  4. //enumerator to identify the chattype  
  5. typedef enum _YDChatType  
  6. {  
  7.     ChatTypeMine = 0,  
  8.     ChatTypeSomeone = 1  
  9. }   YDChatType;  
  10.   
  11. @interface YDChatData : NSObject  
  12. @property (readonly, nonatomic) YDChatType type;  
  13. @property (readonly, nonatomic, strong) NSDate *date;  
  14. @property (readonly, nonatomic, strong) UIView *view;  
  15. @property (readonly, nonatomic) UIEdgeInsets insets;  
  16. @property (nonatomic,strong) YDChatUser *chatUser;  
  17.   
  18. //custom initializers  
  19.    
  20. + (id)dataWithText:(NSString *)text date:(NSDate *)date   
  21.       type:(YDChatType)type andUser:(YDChatUser *)_user;  
  22.    
  23. + (id)dataWithImage:(UIImage *)image date:(NSDate *)date   
  24.       type:(YDChatType)type andUser:(YDChatUser *)_user;  
  25.    
  26. + (id)dataWithView:(UIView *)view date:(NSDate *)date   
  27.       type:(YDChatType)type andUser:(YDChatUser *)_user   
  28.       insets:(UIEdgeInsets)insets;  
  29.   
  30.   
  31. @end  
  32. </span>  

在类的实现中,实现几个不同的初始化方法,如代码清单2-7中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-7  Chapter2/YDChatApp/YDChatData.m  
  2. import "YDChatData.h"  
  3. #import <QuartzCore/QuartzCore.h>  
  4.   
  5. @implementation YDChatData  
  6. //create some constant UIEdgeInsets to property align text and images  
  7. const UIEdgeInsets textInsetsMine = {5, 10, 11, 17};  
  8. const UIEdgeInsets textInsetsSomeone = {5, 15, 11, 10};  
  9. const UIEdgeInsets imageInsetsMine = {11, 13, 16, 22};  
  10. const UIEdgeInsets imageInsetsSomeone = {11, 18, 16, 14};  
  11. #pragma initializers  
  12. + (id)dataWithText:(NSString *)text date:(NSDate *)date type:(YDChatType)type   
  13.       andUser:(YDChatUser *)_user  
  14. {  
  15.     return [[YDChatData alloc] initWithText:text date:date   
  16.              type:type andUser:_user];  
  17. }  
  18.   
  19. •(id)initWithText:(NSString *)text date:(NSDate *)date type:(YDChatType)type andUser:(YDChatUser *)_user  
  20. {   
  21.     UIFont* font = [UIFont boldSystemFontOfSize:12];  
  22.     int width = 225, height = 10000.0;  
  23.     NSMutableDictionary *atts = [[NSMutableDictionary alloc] init];  
  24.     [atts setObject:font forKey:NSFontAttributeName];  
  25.     CGRect size = [text boundingRectWithSize:CGSizeMake(width, height)  
  26.                      options:NSStringDrawingUsesLineFragmentOrigin  
  27.                                           attributes:atts  
  28.                                              context:nil];  
  29.     UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, size.size.width, size.size.height)];  
  30.     label.numberOfLines = 0;  
  31.     label.lineBreakMode = NSLineBreakByWordWrapping;  
  32.     label.text = (text ? text : @"");  
  33.     label.font = font;  
  34.     label.backgroundColor = [UIColor clearColor];  
  35.     UIEdgeInsets insets = (type == ChatTypeMine ? textInsetsMine : textInsetsSomeone);  
  36.     return [self initWithView:label date:date type:type andUser:_user   insets:insets];  
  37. }  
  38.   
  39. •(id)initWithImage:(UIImage *)image date:(NSDate *)date type:(YDChatType)type   
  40.     andUser:(YDChatUser *)_user  
  41. {  
  42.     CGSize size = image.size;  
  43.     if (size.width > 220)  
  44.     {  
  45.         size.height /= (size.width / 220);  
  46.         size.width = 220;  
  47.     }  
  48.     UIImageView *imageView = [[UIImageView alloc] initWithFrame:  
  49.                  CGRectMake(0, 0, size.width, size.height)];  
  50.     imageView.image = image;  
  51.     imageView.layer.cornerRadius = 5.0;  
  52.     imageView.layer.masksToBounds = YES;  
  53.     UIEdgeInsets insets =   
  54.     (type == ChatTypeMine ? imageInsetsMine : imageInsetsSomeone);  
  55.     return [self initWithView:imageView date:date type:type andUser:_user    
  56.     insets:insets];  
  57. }  
  58. + (id)dataWithView:(UIView *)view date:(NSDate *)date type:(YDChatType)type   
  59.       andUser:(YDChatUser *)_user insets:(UIEdgeInsets)insets  
  60. {  
  61.     return [[YDChatData alloc] initWithView:view date:date type:type   
  62.              andUser:_user   insets:insets];  
  63. }  
  64.   
  65. •(id)initWithView:(UIView *)view date:(NSDate *)date type:(YDChatType)type   
  66.     andUser:(YDChatUser *)_user insets:(UIEdgeInsets)insets    
  67. {  
  68.     self = [super init];  
  69.     if (self)  
  70.     {  
  71.      _chatUser = _user;  
  72.     _view = view;  
  73.     _date = date;  
  74.     _type = type;  
  75.     _insets = insets;  
  76.     }  
  77.     return self;  
  78. }  
  79.   
  80. @end  
  81. </span>  

2.2.3  构建定制的UITableView控件

创建一个名为YDChatTableView的新的Objective-C类,继承于UITableView类,并且实现了名为ChatBubbleTypingType的枚举类型和需要的属性,如代码清单2-8中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-8  Chapter2/YDChatApp/YDChatTableView.h  
  2. #import <UIKit/UIKit.h>  
  3.  
  4. #import "YDChatTableViewDataSource.h"  
  5. #import "YDChatTableViewCell.h"  
  6. //enumerator to identify the bubble type  
  7. typedef enum _ChatBubbleTypingType  
  8. {  
  9.     ChatBubbleTypingTypeNobody = 0,  
  10.     ChatBubbleTypingTypeMe = 1,  
  11.     ChatBubbleTypingTypeSomebody = 2  
  12. } ChatBubbleTypingType;  
  13.   
  14. @interface YDChatTableView : UITableView   
  15. @property (nonatomic, assign) id<YDChatTableViewDataSource> chatDataSource;  
  16. @property (nonatomic) NSTimeInterval snapInterval;  
  17. @property (nonatomic) ChatBubbleTypingType typingBubble;  
  18.   
  19. @end  
  20. </span>  

在YDChatTableView类的实现中,私有接口遵从于UITableViewDelegate和UITable- ViewDataSource这两个协议,在这里还定义一个名为bubbleSection的属性。

初始化方法为UITableView设置了默认属性,例如背景颜色、delegate和datasource属性等。重写reloadData方法,并编写你自己的代码,从而在YDChatTableView中加载数据。

另外,必须重写numberOfSectionsInTableView、tableview:numberOfRowsInSection:、tableview:heightForRowAtIndexPath:和tableview:cellForRowAtIndexPath:这几个方法。tableview:cellForRowAtIndexPath:方法创建并返回一个YDChatHeaderTableViewCell对象,或者是一个YDChatTableViewCell对象。

如果正在显示的单元格是首行,那么tableview:heightForRowAtIndexPath:方法就会返回YDChatHeaderTableViewCell控件的高度,或者根据这一特定的数据行与之相关的YDChatData对象,计算出高度并返回。

完整的实现如代码清单2-9中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-9  Chapter2/YDChatApp/YDChatTableView.m  
  2. #import "YDChatTableView.h"  
  3. #import "YDChatData.h"  
  4. #import "YDChatHeaderTableViewCell.h"  
  5. @interface YDChatTableView ()<UITableViewDelegate, UITableViewDataSource>  
  6.   
  7.   
  8. @property (nonatomic, retain) NSMutableArray *bubbleSection;  
  9.   
  10. @end  
  11.   
  12. @implementation YDChatTableView  
  13. - (void)initializer  
  14. {  
  15.     self.backgroundColor = [UIColor clearColor];  
  16.     self.separatorStyle = UITableViewCellSeparatorStyleNone;  
  17.     self.delegate = self;  
  18.     self.dataSource = self;  
  19.     //the snap interval in seconds implements a headerview to seperate chats  
  20.     self.snapInterval = 60 * 60 * 24; //one day  
  21.     self.typingBubble = ChatBubbleTypingTypeNobody;  
  22. }  
  23.   
  24. - (id)init  
  25. {  
  26.     self = [super init];  
  27.     if (self) [self initializer];  
  28.     return self;  
  29. }  
  30.   
  31. - (id)initWithFrame:(CGRect)frame  
  32. {  
  33.     self = [super initWithFrame:frame];  
  34.     if (self) [self initializer];  
  35.     return self;  
  36. }  
  37.   
  38. - (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style  
  39. {  
  40.     self = [super initWithFrame:frame style:UITableViewStylePlain];  
  41.     if (self) [self initializer];  
  42.     return self;  
  43. }  
  44.  
  45. #pragma mark - Override  
  46.   
  47. - (void)reloadData  
  48. {  
  49.     self.showsVerticalScrollIndicator = NO;  
  50.     self.showsHorizontalScrollIndicator = NO;  
  51.     self.bubbleSection = nil;  
  52.     int count = 0;  
  53.     self.bubbleSection = [[NSMutableArray alloc] init];  
  54.     if (self.chatDataSource && (count = [self.chatDataSource   
  55.         rowsForChatTable:self]) > 0)  
  56.     {  
  57.         NSMutableArray *bubbleData = [[NSMutableArray alloc]   
  58.             initWithCapacity:count];  
  59.         for (int i = 0; i < count; i++)  
  60.         {  
  61.             NSObject *object = [self.chatDataSource   
  62.                chatTableView:self dataForRow:i];  
  63.             assert([object isKindOfClass:[YDChatData class]]);  
  64.             [bubbleData addObject:object];  
  65.         }  
  66.           
  67.         [bubbleData sortUsingComparator:^NSComparisonResult(id obj1, id obj2)  
  68.          {  
  69.              YDChatData *bubbleData1 = (YDChatData *)obj1;  
  70.              YDChatData *bubbleData2 = (YDChatData *)obj2;  
  71.                
  72.              return [bubbleData1.date compare:bubbleData2.date];  
  73.          }];  
  74.           
  75.         NSDate *last = [NSDate dateWithTimeIntervalSince1970:0];  
  76.         NSMutableArray *currentSection = nil;  
  77.         for (int i = 0; i < count; i++)  
  78.         {  
  79.             YDChatData *data = (YDChatData *)[bubbleData objectAtIndex:i];  
  80.             if ([data.date timeIntervalSinceDate:last] > self.snapInterval)  
  81.             {  
  82.                 currentSection = [[NSMutableArray alloc] init];  
  83.                 [self.bubbleSection addObject:currentSection];  
  84.             }  
  85.             [currentSection addObject:data];  
  86.             last = data.date;  
  87.         }  
  88.     }  
  89.     [super reloadData];  
  90. }  
  91. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView  
  92. {  
  93.     int result = [self.bubbleSection count];  
  94.     if (self.typingBubble != ChatBubbleTypingTypeNobody) result++;  
  95.     return result;  
  96. }  
  97.   
  98. •(NSInteger)tableView:(UITableView *)tableView   
  99.     numberOfRowsInSection:(NSInteger)section  
  100. {  
  101.     if (section >= [self.bubbleSection count]) return 1;  
  102.     return [[self.bubbleSection objectAtIndex:section] count] + 1;  
  103. }  
  104.   
  105. •(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:  
  106.        (NSIndexPath *)indexPath  
  107. {  
  108.     // Header  
  109.     if (indexPath.row == 0)  
  110.     {  
  111.         return [YDChatHeaderTableViewCell height];  
  112.     }  
  113.     YDChatData *data = [[self.bubbleSection objectAtIndex:indexPath.section]   
  114.                 objectAtIndex:indexPath.row - 1];  
  115.       
  116.       return MAX(data.insets.top + data.view.frame.size.height +   
  117.                  data.insets.bottom, 52);  
  118. }  
  119.   
  120. •(UITableViewCell *)tableView:(UITableView *)tableView   
  121.        cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  122. {  
  123.     // Header based on snapInterval  
  124.     if (indexPath.row == 0)  
  125.     {  
  126.         static NSString *cellId = @"HeaderCell";  
  127.         YDChatHeaderTableViewCell *cell = [tableView   
  128.          dequeueReusableCellWithIdentifier:cellId];  
  129.         YDChatData *data = [[self.bubbleSection objectAtIndex:indexPath.section]   
  130.                              objectAtIndex:0];  
  131.         if (cell == nil) cell = [[YDChatHeaderTableViewCell alloc] init];  
  132.             cell.date = data.date;  
  133.         return cell;  
  134.     }  
  135.     // Standard   
  136.     static NSString *cellId = @"ChatCell";  
  137.     YDChatTableViewCell *cell = [tableView   
  138.           dequeueReusableCellWithIdentifier:cellId];  
  139.     YDChatData *data = [[self.bubbleSection objectAtIndex:indexPath.section]   
  140.                          objectAtIndex:indexPath.row - 1];  
  141.     if (cell == nil) cell = [[YDChatTableViewCell alloc] init];  
  142.     cell.data = data;  
  143.     return cell;  
  144. }  
  145.   
  146.   
  147. @end  
  148. </span>  

2.2.4  灵活的单元格高度

由于聊天消息具有图片和文本,这会使每个单元格的宽度和高度发生变化,因此表格可以是不相同的,并且需要根据单元格的内容进行计算。在重写的tableview:heightForRow- AtIndexPath:方法中,存在一个判断条件,如果当前的单元格是标题单元格,那么就返回标题单元格的高度;如果这个单元格是一个普通的聊天单元格,那么就获取与该单元格相连的YDChatData对象,并计算用作显示单元格的相关UIEdgeInset变量的最大值。下面的代码片段在YDChatTableView类中实现,负责灵活地返回单元格的高度。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">(float)tableView:(UITableView *)tableView   
  2.        heightForRowAtIndexPath:(NSIndexPath *)indexPath  
  3. {  
  4.     // Header  
  5.     if (indexPath.row == 0)  
  6.     {  
  7.         return [YDChatHeaderTableViewCell height];  
  8.     }  
  9.     YDChatData *data = [[self.bubbleSection objectAtIndex:indexPath.section]   
  10.           objectAtIndex:indexPath.row - 1];  
  11.     return MAX(data.insets.top + data.view.frame.size.height +   
  12.                data.insets.bottom, 52);  
  13. }  
  14. </span>  

2.2.5  开发定制的单元格

为了正确显示聊天数据,需要两种不同的定制的UITableViewCell对象,它们都继承于UITableViewCell类。

一种用以显示标题头,在这种情况下,就是显示与snapInterval属性相关的日期和时间编组。另外一种用以显示YDChatData对象中保存的聊天消息。前一种表格对象有一个名为height返回值类型为CGFloat的静态方法,返回这个UITableViewCell的高度,还有一个日期类型的属性,因而日期和时间可以从snapInterval属性中获得。创建一个名为YDChatTableViewHeaderCell的新的Objective-C类,打开YDChatTableViewHeaderCell.h文件,应用如代码清单2-10中所示的代码。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-10  Chapter2/YDChatApp/YDChatTableViewHeaderCell.h  
  2. #import <UIKit/UIKit.h>  
  3.   
  4. @interface YDChatHeaderTableViewCell : UITableViewCell  
  5. + (CGFloat)height;  
  6. @property (nonatomic, strong) NSDate *date;  
  7. @end  
  8. </span>  

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">  
  2. </span>  

YDChatTableViewHeaderCell类的实现简单地返回30.0作为height方法的返回值。setDate方法接收一个日期对象,并创建UILabel控件,将其添加到视图上,用以显示section的日期-时间戳。实现如代码清单2-11中所示的代码。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-11  Chapter2/YDChatApp/YDChatTableViewHeaderCell.m  
  2. #import "YDChatHeaderTableViewCell.h"  
  3. @interface YDChatHeaderTableViewCell ()  
  4.   
  5. @property (nonatomic, retain) UILabel *label;  
  6.   
  7. @end  
  8. @implementation YDChatHeaderTableViewCell  
  9. + (CGFloat)height  
  10. {  
  11.     return 30.0;  
  12. }  
  13.   
  14. - (void)setDate:(NSDate *)value  
  15. {  
  16.     NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];  
  17.     [dateFormatter setDateStyle:NSDateFormatterMediumStyle];  
  18.     [dateFormatter setTimeStyle:NSDateFormatterShortStyle];  
  19.     NSString *text = [dateFormatter stringFromDate:value];  
  20.   
  21.     if (self.label)  
  22.     {  
  23.         self.label.text = text;  
  24.         return;  
  25.     }  
  26.     self.selectionStyle = UITableViewCellSelectionStyleNone;  
  27.     self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0,   
  28.     self.frame.size.width, [YDChatHeaderTableViewCell height])];  
  29.     self.label.text = text;  
  30.     self.label.font = [UIFont boldSystemFontOfSize:12];  
  31.     self.label.textAlignment = NSTextAlignmentCenter;  
  32.     self.label.shadowOffset = CGSizeMake(0, 1);  
  33.     self.label.shadowColor = [UIColor whiteColor];  
  34.     self.label.textColor = [UIColor darkGrayColor];  
  35.     self.label.backgroundColor = [UIColor clearColor];  
  36.     [self addSubview:self.label];  
  37. }  
  38. @end  
  39. </span>  

既然已经为HeaderCell创建了类,那么也需要为ChatCell创建一个定制的类,用来显示真实的聊天消息。创建一个继承于UITableViewCell的新的Objective-C类,将其命名为YDChatTableViewCell。为这个类添加YDChatData类型的唯一的一个属性,用以显示真实的聊天消息,并将单元格作为定制的UITableViewCell对象返回。

在YDChatTableViewCell.h文件中实现如代码清单2-12中所示的代码。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-12  Chapter2/YDChatApp/YDChatTableViewCell.h  
  2. #import <UIKit/UIKit.h>  
  3. #import "YDChatData.h"  
  4.    
  5. @interface YDChatTableViewCell : UITableViewCell  
  6.    
  7. @property (nonatomic, strong) YDChatData *data;  
  8. -(void)setData(YDChatData*)data;  
  9. @end</span>  




setData:方法接受YDChatData对象,将它赋值给data属性。下一步,它会调用rebuild- UserInterface方法,如果该方法之前没有创建过bubbleImage,那么就会创建这个对象。如果YDChatData对象有代表一个用户的值,那么就会使用该聊天用户的头像,作为子视图添加到界面上。

YDChatTableViewCell.m文件的实现代码如代码清单2-13中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-13  Chapter2/YDChatApp/YDChatTableViewCell.m  
  2. #import <QuartzCore/QuartzCore.h>  
  3. #import "YDChatTableViewCell.h"  
  4. #import "YDChatData.h"  
  5. #import "YDChatUser.h"  
  6. @interface YDChatTableViewCell ()  
  7. //declare properties  
  8. @property (nonatomic, retain) UIView *customView;  
  9. @property (nonatomic, retain) UIImageView *bubbleImage;  
  10. @property (nonatomic, retain) UIImageView *avatarImage;  
  11.   
  12. - (void) setupInternalData;  
  13.   
  14. @end  
  15.   
  16. @implementation YDChatTableViewCell  
  17. @synthesize data=_data;  
  18. - (void)setData:(YDChatData *)data  
  19. {  
  20.     _data = data;  
  21.     [self rebuildUserInterface];  
  22. }  
  23.   
  24. - (void) rebuildUserInterface  
  25. {  
  26.     self.selectionStyle = UITableViewCellSelectionStyleNone;  
  27.     if (!self.bubbleImage)  
  28.     {  
  29.         self.bubbleImage = [[UIImageView alloc] init];  
  30.         [self addSubview:self.bubbleImage];  
  31.     }  
  32.     YDChatType type = self.data.type;  
  33.     CGFloat width = self.data.view.frame.size.width;  
  34.     CGFloat height = self.data.view.frame.size.height;  
  35.     CGFloat x = (type == ChatTypeSomeone) ? 0 :   
  36.            self.frame.size.width -   
  37.            width -   
  38.            self.data.insets.left -   
  39.            self.data.insets.right;  
  40.     CGFloat y = 0;  
  41.     //if we have a chatUser show the avatar of the YDChatUser property  
  42.     if (self.data.chatUser)  
  43.     {  
  44.         YDChatUser *thisUser = self.data.chatUser;  
  45.         [self.avatarImage removeFromSuperview];  
  46.         self.avatarImage = [[UIImageView alloc] initWithImage:(thisUser.avatar ?   
  47.              thisUser.avatar : [UIImage imageNamed:@"noAvatar.png"])];  
  48.         self.avatarImage.layer.cornerRadius = 9.0;  
  49.         self.avatarImage.layer.masksToBounds = YES;  
  50.         self.avatarImage.layer.borderColor =   
  51.                          [UIColor colorWithWhite:0.0 alpha:0.2].CGColor;  
  52.         self.avatarImage.layer.borderWidth = 1.0;  
  53.         //calculate the x position  
  54.         CGFloat avatarX = (type == ChatTypeSomeone) ? 2 :   
  55.                           self.frame.size.width - 52;  
  56.         CGFloat avatarY = self.frame.size.height - 50;  
  57.         //set the frame correctly  
  58.         self.avatarImage.frame = CGRectMake(avatarX, avatarY, 50, 50);  
  59.         [self addSubview:self.avatarImage];  
  60.         CGFloat delta = self.frame.size.height -   
  61.                        (self.data.insets.top + self.data.insets.bottom +   
  62.                         self.data.view.frame.size.height);  
  63.         if (delta > 0) y = delta;  
  64.         if (type == ChatTypeSomeone) x += 54;  
  65.         if (type == ChatTypeMine) x -= 54;  
  66.     }  
  67.     [self.customView removeFromSuperview];  
  68.     self.customView = self.data.view;  
  69.     self.customView.frame =   
  70.                  CGRectMake(x + self.data.insets.left,   
  71.                          y + self.data.insets.top, width, height);  
  72.     [self.contentView addSubview:self.customView];  
  73.     //depending on the ChatType a bubble image on the left or right  
  74.     if (type == ChatTypeSomeone)  
  75.     {  
  76.         self.bubbleImage.image = [[UIImage imageNamed:@"yoububble.png"]   
  77.                   stretchableImageWithLeftCapWidth:21 topCapHeight:14];  
  78.     }  
  79.     else {  
  80.         self.bubbleImage.image = [[UIImage imageNamed:@"mebubble.png"]   
  81.                   stretchableImageWithLeftCapWidth:15 topCapHeight:14];  
  82.     }  
  83.     self.bubbleImage.frame =   
  84.             CGRectMake(x, y, width + self.data.insets.left +   
  85.                        self.data.insets.right, height +   
  86.                        self.data.insets.top + self.data.insets.bottom);  
  87. }  
  88. - (void)setFrame:(CGRect)frame  
  89. {  
  90.     [super setFrame:frame];  
  91.     [self rebuildUserInterface];  
  92. }  
  93.   
  94. @end  
  95. </span>  

2.2.6  创建聊天用户对象

创建一个新的名为YDChatUser的类,具有两个属性:用户名和头像,它们会被显示在刚刚创建的YDChatTableViewCell中。设计YDChatUser类用来设置用户对象的用户名和头像图片,这样可以关联到YDChatData对象上。

创建一个继承于NSObject的新的Objective-C类,将其命名为YDChatUser。YDChatUser.h文件如代码清单2-14中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-14  Chapter2/YDChatApp/YDChatUser.h  
  2. #import <Foundation/Foundation.h>  
  3.   
  4. @interface YDChatUser : NSObject  
  5. @property (nonatomic, strong) NSString *username;  
  6. @property (nonatomic, strong) UIImage *avatar;  
  7.   
  8. - (id)initWithUsername:(NSString *)user avatarImage:(UIImage *)image;  
  9. @end  
  10. </span>  

实现定制的构造方法,并且将传入的参数值赋给属性,如代码清单2-15中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-15  Chapter2/YDChatApp/YDChatUser.m  
  2. #import "YDChatUser.h"  
  3.   
  4. @implementation YDChatUser  
  5. @synthesize avatar = _avatar;  
  6. @synthesize username = _username;  
  7.   
  8.   
  9. - (id)initWithUsername:(NSString *)user avatarImage:(UIImage *)image  
  10. {  
  11.     self = [super init];  
  12.     if (self)  
  13.     {  
  14.         self.avatar = [image copy];  
  15.         self.username = [user copy];  
  16.     }  
  17.     return self;  
  18. }  
  19. @end</span>  

2.2.7  融会贯通

既然已经开发了所有独立的组件,那么就可以编写YDViewController类,使用聊天消息来显示YDChatTableView了。

YDViewController.h文件不需要任何编码工作。

YDViewController.m文件导入所需的头文件,以此为开始并遵从YDChatTableViewDataSource和UITextViewDelegate协议。在viewDidLoad方法的开头,以编程方式创建了用户界面元素。在这个方法的结尾,创建了YDChatUser类型的两个对象,如下面这段代码示例所示:

  me =[[YDChatUser alloc] initWithUsername:@"Peter"

          avatarImage:[UIImage imageNamed:@"me.png"]];

    you =[[YDChatUser alloc] initWithUsername:@"You"

            avatarImage:[UIImageimageNamed:@"noavatar.png"]];

最终,在viewDidLoad方法中,一些YDChatData记录被创建并添加到Chats数组中,作为YDChatTableView控件的datasource对象。

YDChatData *first = [YDChatData dataWithText:

      @"Hey, how are you doing? I'm inParis take a look at this picture."

      date:[NSDatedateWithTimeIntervalSinceNow:-600]

      type:ChatTypeMine andUser:me];

    YDChatData *second = [YDChatDatadataWithImage:

            [UIImage imageNamed:@"eiffeltower.jpg"]

            date:[NSDatedateWithTimeIntervalSinceNow:-290]

            type:ChatTypeMine andUser:me];

    YDChatData *third = [YDChatDatadataWithText:

            @"Wow.. Really cool pictureout there. Wish I could be with you"

            date:[NSDatedateWithTimeIntervalSinceNow:-5]

            type:ChatTypeSomeone andUser:you];

    YDChatData *forth = [YDChatDatadataWithText:

            @"Maybe next time you can comewith me."

            date:[NSDatedateWithTimeIntervalSinceNow:+0]

            type:ChatTypeMine andUser:me];

        //Initialize the Chats array with thecreated YDChatData objects

    Chats = [[NSMutableArray alloc]

              initWithObjects:first, second,third,forth, nil];

sendMessage方法创建了YDChatData对象,使用从msgText控件中得到的文本来初始化这个对象,将其加入到Chats数组中,并调用chatTable对象的reloadData方法。

当选中UITextView,开始在这个控件内输入文字时,会触发textView:shouldChange- TextInRange:replacementText:、textViewDidBeginEditing:和textViewDidChange:这三个方法,用来操控用户界面。shortenTableView和showTableView方法用来控制YDChatTableView的高度。

完整的实现方式如代码清单2-16中所示。

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Microsoft YaHei;font-size:14px;">代码清单2-16  Chapter2/YDChatApp/YDViewController.m  
  2. #import "YDChatUser.h"  
  3. #import "YDChatTableViewDataSource.h"  
  4. #import "YDViewController.h"  
  5. #import <QuartzCore/QuartzCore.h>  
  6. #import "YDChatTableView.h"  
  7. #import "YDChatTableViewDataSource.h"  
  8. #import "YDChatData.h"  
  9. #import "YDChatUser.h"  
  10. #define lineHeight  16.0f  
  11. @interface YDViewController ()<YDChatTableViewDataSource,UITextViewDelegate>  
  12. {  
  13.     YDChatTableView *chatTable;  
  14.     UIView *textInputView;  
  15.     UITextField *textField;  
  16.     NSMutableArray *Chats;  
  17.       
  18.     UIView* sendView;  
  19.     UIButton* sendButton;  
  20.     UITextView* msgText;  
  21.     BOOL composing;  
  22.     float prevLines;  
  23.     YDChatUser* me ;  
  24.     YDChatUser* you ;  
  25. }  
  26. @end  
  27.   
  28. @implementation YDViewController  
  29.   
  30. CGRect  appFrame;  
  31. - (void)viewDidLoad  
  32. {  
  33.     [super viewDidLoad];  
  34.     self.view.backgroundColor=[UIColor lightGrayColor];  
  35.         //create your instance of YDChatTableView  
  36.     self.chatTable=[[YDChatTableView alloc] initWithFrame:  
  37.     CGRectMake(0,40,[[UIScreen mainScreen] bounds].size.width,  
  38.     [[UIScreen mainScreen] bounds].size.height -40)  style:UITableViewStylePlain];  
  39.     chatTable.backgroundColor=[UIColor whiteColor];  
  40.     [self.view addSubview:chatTable];  
  41.     appFrame= [[UIScreen mainScreen] applicationFrame];  
  42.    
  43.     sendView = [[UIView alloc] initWithFrame:  
  44.                  CGRectMake(0,appFrame.size.height-56,320,56)];  
  45.     sendView.backgroundColor=[UIColor blueColor];  
  46.     sendView.alpha=0.9;  
  47.       
  48.     msgText = [[UITextView alloc] initWithFrame:CGRectMake(7,10,225,36)];  
  49.     msgText.backgroundColor = [UIColor whiteColor];  
  50.     msgText.textColor=[UIColor blackColor];  
  51.     msgText.font=[UIFont boldSystemFontOfSize:12];  
  52.     msgText.autoresizingMask =   
  53.            UIViewAutoresizingFlexibleHeight |   
  54.            UIViewAutoresizingFlexibleTopMargin;  
  55.     msgText.layer.cornerRadius = 10.0f;  
  56.     msgText.returnKeyType=UIReturnKeySend;  
  57.     msgText.showsHorizontalScrollIndicator=NO;  
  58.     msgText.showsVerticalScrollIndicator=NO;  
  59.         //Set the delegate so you can respond to user input  
  60.     msgText.delegate=self;  
  61.     [sendView addSubview:msgText];  
  62.     msgText.contentInset = UIEdgeInsetsMake(0,0,0,0);  
  63.     [self.view addSubview:sendView];  
  64.       
  65.     sendButton = [[UIButton alloc] initWithFrame:CGRectMake(235,10,77,36)];  
  66.     sendButton.backgroundColor=[UIColor lightGrayColor];  
  67.     [sendButton addTarget:self action:@selector(sendMessage)   
  68.                           forControlEvents:UIControlEventTouchUpInside];  
  69.     sendButton.autoresizingMask=UIViewAutoresizingFlexibleTopMargin;  
  70.     sendButton.layer.cornerRadius=6.0f;  
  71.     [sendButton setTitle:@"Send" forState:UIControlStateNormal];  
  72.     [sendView addSubview:sendButton];  
  73.         //create two YDChatUser object one representing me and one   
  74.           representing the other party  
  75.       
  76.     me = [[YDChatUser alloc] initWithUsername:@"Peter"   
  77.            avatarImage:[UIImage imageNamed:@"me.png"]];  
  78.     you  =[[YDChatUser alloc] initWithUsername:@"You"   
  79.             avatarImage:[UIImage imageNamed:@"noavatar.png"]];  
  80.         //Create some YDChatData objects here  
  81.     YDChatData *first = [YDChatData dataWithText:  
  82.          @"Hey, how are you doing? I'm in Paris take a look at this picture."   
  83.          date:[NSDate dateWithTimeIntervalSinceNow:-600]   
  84.          type:ChatTypeMine andUser:me];  
  85.     YDChatData *second = [YDChatData dataWithImage:  
  86.          [UIImage imageNamed:@"eiffeltower.jpg"]   
  87.          date:[NSDate dateWithTimeIntervalSinceNow:-290]   
  88.          type:ChatTypeMine andUser:me];  
  89.     YDChatData *third = [YDChatData dataWithText:  
  90.          @"Wow.. Really cool picture out there. Wish I could be with you"   
  91.          date:[NSDate dateWithTimeIntervalSinceNow:-5]   
  92.          type:ChatTypeSomeone andUser:you];  
  93.     YDChatData *forth = [YDChatData dataWithText:  
  94.          @"Maybe next time you can come with me."   
  95.          date:[NSDate dateWithTimeIntervalSinceNow:+0]   
  96.          type:ChatTypeMine andUser:me];  
  97.         //Initialize the Chats array with the created YDChatData objects  
  98.     Chats = [[NSMutableArray alloc]   
  99.               initWithObjects:first, second, third,forth, nil];  
  100.         //set the chatDataSource  
  101.     chatTable.chatDataSource = self;  
  102.         //call the reloadData, this is actually calling your override method  
  103.     [chatTable reloadData];  
  104. }  
  105.   
  106. -(void)sendMessage  
  107. {  
  108.     composing=NO;  
  109.     YDChatData *thisChat = [YDChatData dataWithText:msgText.text   
  110.                   date:[NSDate date] type:ChatTypeMine andUser:me];  
  111.     [Chats addObject:thisChat];  
  112.     [chatTable reloadData];  
  113.     [self showTableView];  
  114.     [msgText resignFirstResponder];  
  115.     msgText.text=@"";  
  116.     sendView.frame=CGRectMake(0,appFrame.size.height-56,320,56);  
  117.     NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];  
  118.     [chatTable scrollToRowAtIndexPath:indexPath  
  119.                      atScrollPosition:UITableViewScrollPositionBottom   
  120.                 animated:YES];  
  121.       
  122.       
  123. }  
  124. #pragma UITextViewDelegate  
  125.     //if user presses enter consider as end of message and send it  
  126. -(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range   
  127.        replacementText:(NSString *)text  
  128. {  
  129.     if([text isEqualToString:@"\n"]) {  
  130.         [self sendMessage];  
  131.         return NO;  
  132.     }  
  133.     return YES;  
  134. }  
  135.     // this function returns the height of the entered text in the msgText field  
  136. -(CGFloat )textY  
  137. {  
  138.     UIFont* systemFont = [UIFont boldSystemFontOfSize:12];  
  139.      int width = 225.0, height = 10000.0;  
  140.      NSMutableDictionary *atts = [[NSMutableDictionary alloc] init];  
  141.      [atts setObject:systemFont forKey:NSFontAttributeName];  
  142.        
  143.      CGRect size = [msgText.text boundingRectWithSize:CGSizeMake(width, height)  
  144.      options:NSStringDrawingUsesLineFragmentOrigin  
  145.      attributes:atts  
  146.      context:nil];  
  147.        float textHeight = size.size.height;  
  148.       
  149.     float lines = textHeight / lineHeight;  
  150.     if (lines >=4)  
  151.         lines=4;  
  152.     if ([msgText.text length]==0)  
  153.         lines=0.9375f;  
  154.     return 190 - (lines * lineHeight) + lineHeight;  
  155. }  
  156. -(void)textViewDidChange:(UITextView *)textView  
  157. {  
  158.   
  159.     UIFont* systemFont = [UIFont boldSystemFontOfSize:12];  
  160.     int width = 225.0, height = 10000.0;  
  161.     NSMutableDictionary *atts = [[NSMutableDictionary alloc] init];  
  162.     [atts setObject:systemFont forKey:NSFontAttributeName];  
  163.       
  164.     CGRect size = [msgText.text boundingRectWithSize:CGSizeMake(width, height)  
  165.                                    options:NSStringDrawingUsesLineFragmentOrigin  
  166.                                           attributes:atts  
  167.                                              context:nil];  
  168.     float textHeight = size.size.height;  
  169.     float lines = textHeight / lineHeight;  
  170.     if (lines >=4)  
  171.         lines=4;  
  172.       
  173.     composing=YES;  
  174.     msgText.contentInset = UIEdgeInsetsMake(0,0,0,0);  
  175.     sendView.frame = CGRectMake(0,appFrame.size.height-270 - (lines * lineHeight) + lineHeight ,320,56 + (lines * lineHeight)-lineHeight);  
  176.       
  177.     if (prevLines!=lines)  
  178.         [self shortenTableView];  
  179.       
  180.     prevLines=lines;  
  181. }  
  182.   
  183.       
  184.     prevLines=lines;  
  185. }  
  186.     //let's change the frame of the chatTable so we can see the bottom  
  187.   
  188. -(void)shortenTableView  
  189. {   [UIView beginAnimations:@"moveView" context:nil];  
  190.     [UIView setAnimationDuration:0.1];  
  191.     chatTable.frame=CGRectMake(0, 0, 320, [self textY] );  
  192.     [UIView commitAnimations];  
  193.     prevLines=1;  
  194.       
  195. }  
  196.     // show the chatTable as it was  
  197.   
  198. -(void)showTableView  
  199. {  
  200.     [UIView beginAnimations:@"moveView" context:nil];  
  201.     [UIView setAnimationDuration:0.1];  
  202.     chatTable.frame=CGRectMake(0,0,320,460 - 56);  
  203.     [UIView commitAnimations];  
  204. }  
  205.     //when user starts typing change the frame position and shorten the chatTable  
  206.   
  207. -(void)textViewDidBeginEditing:(UITextView *)textView  
  208. { [UIView beginAnimations:@"moveView" context:nil];  
  209.     [UIView setAnimationDuration:0.3];  
  210.     sendView.frame = CGRectMake(0,appFrame.size.height-270,320,56);  
  211.     [UIView commitAnimations];  
  212.     [self shortenTableView];  
  213.     [msgText becomeFirstResponder];  
  214.       
  215. }  
  216. - (BOOL)shouldAutorotateToInterfaceOrientation:  
  217.         (UIInterfaceOrientation)interfaceOrientation  
  218. {  
  219.     return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);  
  220. }  
  221.  
  222. #pragma mark - YDChatTableView implementation  
  223. //here are the required implementation from your YDChatTableViewDataSource  
  224. - (NSInteger)rowsForChatTable:(YDChatTableView *)tableView  
  225. {  
  226.     return [Chats count];  
  227. }  
  228.   
  229. - (YDChatData *)chatTableView:(YDChatTableView *)tableView   
  230.    dataForRow:(NSInteger)row  
  231. {  
  232.     return [Chats objectAtIndex:row];  
  233. }  
  234.   
  235. @end  
  236. </span>  
虽然以非常出色的方式创建的Chat解决方案实现了创建聊天消息并显示这些消息的逻辑,但是它不发送和接收任何消息。因此,还需要实现一个遵循XMPP协议的真正的通信模块(注:有关XMPP协议请参考www.xmpp.org官方网站的内容)。


《iOS 高级编程》试读电子书,免费提供,有需要的留下邮箱,一有空即发送给大家。 别忘啦顶哦!

购书地址:

京东:http://item.jd.com/11573064.html

当当:http://product.dangdang.com/23596918.html

互动:http://product.china-pub.com/3770647

亚马逊:http://www.amazon.cn/dp/B00P7NO4K2

微信:qinghuashuyou  
更多最新图书请点击查看哦(即日起-12月31日:关注@qinghuashuyou 发送:关注技术方向,即有机会参与图书抽奖,每周抽取十个幸运读者)
0 0
原创粉丝点击