框架:
所有代码文件:
Model:
1 // 2 // Message.h 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 // message信息模型,存储聊天记录 9 10 #import <Foundation/Foundation.h> 11 12 typedef enum { 13 MessageTypeMe = 0, // 我发出的信息 14 MessageTypeOhter = 1 // 对方发出的信息 15 } MessageType; 16 17 @interface Message : NSObject 18 19 /** 信息 */ 20 @property(nonatomic, copy) NSString *text; 21 22 /** 发送时间 */ 23 @property(nonatomic, copy) NSString *time; 24 25 /** 发送方 */ 26 @property(nonatomic, assign) MessageType type; 27 28 /** 是否隐藏发送时间 */ 29 @property(nonatomic, assign) BOOL hideTime; 30 31 - (instancetype) initWithDictionary:(NSDictionary *) dictionary; 32 + (instancetype) messageWithDictionary:(NSDictionary *) dictionary; 33 + (instancetype) message; 34 35 @end
1 // 2 // Message.m 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 9 #import "Message.h" 10 11 @implementation Message 12 13 - (instancetype) initWithDictionary:(NSDictionary *) dictionary { 14 if (self = [super init]) { 15 [self setValuesForKeysWithDictionary:dictionary]; 16 } 17 18 return self; 19 } 20 21 + (instancetype) messageWithDictionary:(NSDictionary *) dictionary { 22 return [[self alloc] initWithDictionary:dictionary]; 23 } 24 25 + (instancetype) message { 26 return [self messageWithDictionary:nil]; 27 } 28 29 @end
1 // 2 // MessageFrame.h 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 // 存储每个cell内子控件的位置尺寸的frame 9 10 #import <Foundation/Foundation.h> 11 #import <UIKit/UIKit.h> 12 #import "Message.h" 13 14 #define MESSAGE_TIME_FONT [UIFont systemFontOfSize:13] 15 #define MESSAGE_TEXT_FONT [UIFont systemFontOfSize:15] 16 #define TEXT_INSET 20 17 18 @interface MessageFrame : NSObject 19 20 /** 发送时间 */ 21 @property(nonatomic, assign, readonly) CGRect timeFrame; 22 23 /** 头像 */ 24 @property(nonatomic, assign, readonly) CGRect iconFrame; 25 26 /** 信息 */ 27 @property(nonatomic, assign, readonly) CGRect textFrame; 28 29 /** 信息model */ 30 @property(nonatomic, strong) Message *message; 31 32 /** cell的高度 */ 33 @property(nonatomic, assign) CGFloat cellHeight; 34 35 36 @end
1 // 2 // MessageFrame.m 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 9 #import "MessageFrame.h" 10 #import "NSString+Extension.h" 11 12 @implementation MessageFrame 13 14 /** 设置message,计算位置尺寸 */ 15 - (void)setMessage:(Message *)message { 16 _message = message; 17 18 // 间隙 19 CGFloat padding = 10; 20 21 // 1.发送时间 22 if (NO == message.hideTime) { 23 CGFloat timeWidth = [UIScreen mainScreen].bounds.size.width; 24 CGFloat timeHeight = 40; 25 CGFloat timeX = 0; 26 CGFloat timeY = 0; 27 _timeFrame = CGRectMake(timeX, timeY, timeWidth, timeHeight); 28 } 29 30 // 2.头像 31 CGFloat iconWidth = 40; 32 CGFloat iconHeight = 40; 33 34 // 2.1 根据信息的发送方调整头像位置 35 CGFloat iconX; 36 if (MessageTypeMe == message.type) { 37 // 我方,放在右边 38 iconX = [UIScreen mainScreen].bounds.size.width - padding - iconWidth; 39 } else { 40 // 对方,放在左边 41 iconX = padding; 42 } 43 44 CGFloat iconY = CGRectGetMaxY(_timeFrame) + padding; 45 _iconFrame = CGRectMake(iconX, iconY, iconWidth, iconHeight); 46 47 // 3.信息,尺寸可变 48 CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; 49 // 3.1 设置文本最大尺寸 50 CGSize textMaxSize = CGSizeMake(screenWidth - iconWidth - padding * 10, MAXFLOAT); 51 // 3.2 计算文本真实尺寸 52 CGSize textRealSize = [message.text sizeWithFont:MESSAGE_TEXT_FONT maxSize:textMaxSize]; 53 54 // 3.3 按钮尺寸 55 CGSize btnSize = CGSizeMake(textRealSize.width + TEXT_INSET*2, textRealSize.height + TEXT_INSET*2); 56 57 // 3.4 调整信息的位置 58 CGFloat textX; 59 if (MessageTypeMe == message.type) { 60 // 我方,放在靠右 61 textX = CGRectGetMinX(_iconFrame) - btnSize.width - padding; 62 } else { 63 // 对方,放在靠左 64 textX = CGRectGetMaxX(_iconFrame) + padding; 65 } 66 67 CGFloat textY = iconY; 68 _textFrame = CGRectMake(textX, textY, btnSize.width, btnSize.height); 69 70 // 4.cell的高度 71 CGFloat iconMaxY = CGRectGetMaxY(_iconFrame); 72 CGFloat textMaxY = CGRectGetMaxY(_textFrame); 73 _cellHeight = MAX(iconMaxY, textMaxY) + padding; 74 } 75 76 77 @end
View:
1 // 2 // MessageCell.h 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 9 #import <UIKit/UIKit.h> 10 11 #define BACKGROUD_COLOR [UIColor colorWithRed:235/255.0 green:235/255.0 blue:235/255.0 alpha:1.0] 12 13 @class MessageFrame, Message; 14 15 @interface MessageCell : UITableViewCell 16 17 /** 持有存储了聊天记录和聊天框位置尺寸的frame */ 18 @property(nonatomic, strong) MessageFrame *messageFrame; 19 20 /** 传入父控件tableView引用的构造方法 */ 21 + (instancetype) cellWithTableView:(UITableView *) tableView; 22 23 @end
1 // 2 // MessageCell.m 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 9 #import "MessageCell.h" 10 #import "MessageFrame.h" 11 #import "UIImage+Extension.h" 12 13 @interface MessageCell() 14 15 // 定义cell内的子控件,用于保存控件,然后进行数据和位置尺寸的计算 16 /** 发送时间 */ 17 @property(nonatomic, weak) UILabel *timeLabel; 18 19 /** 头像 */ 20 @property(nonatomic, weak) UIImageView *iconView; 21 22 /** 信息 */ 23 @property(nonatomic, weak) UIButton *textView; 24 25 @end 26 27 @implementation MessageCell 28 29 - (void)awakeFromNib { 30 // Initialization code 31 } 32 33 - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 34 [super setSelected:selected animated:animated]; 35 36 // Configure the view for the selected state 37 } 38 39 #pragma mark - 构造方法 40 // 自定义构造方法 41 + (instancetype) cellWithTableView:(UITableView *) tableView { 42 static NSString *ID = @"message"; 43 44 // 使用缓存池 45 MessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; 46 47 // 创建一个新的cell 48 if (nil == cell) { 49 cell = [[MessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID]; 50 } 51 52 return cell; 53 } 54 55 // 重写构造方法,创建cell中的各个子控件 56 - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 57 self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 58 59 // 设置cell的背景色 60 self.backgroundColor = BACKGROUD_COLOR; 61 62 // 1.发送时间 63 UILabel *timeLabel = [[UILabel alloc] init]; 64 [timeLabel setTextAlignment:NSTextAlignmentCenter]; 65 [timeLabel setFont:MESSAGE_TIME_FONT]; 66 [timeLabel setTextColor:[UIColor grayColor]]; 67 [self.contentView addSubview:timeLabel]; 68 self.timeLabel = timeLabel; 69 70 // 2.头像 71 UIImageView *iconView = [[UIImageView alloc] init]; 72 [self.contentView addSubview:iconView]; 73 self.iconView = iconView; 74 75 // 3.信息 76 UIButton *textView = [[UIButton alloc] init]; 77 [textView setTitle:@"text" forState:UIControlStateNormal]; 78 [textView.titleLabel setFont:MESSAGE_TEXT_FONT]; 79 80 // 3.1 如果是浅色背景,记得设置字体颜色,因为按钮的字体颜色默认是白色 81 [textView setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 82 [textView.titleLabel setNumberOfLines:0]; // 设置自动换行 83 84 // 3.2 调整文字的内边距 85 textView.contentEdgeInsets = UIEdgeInsetsMake(TEXT_INSET, TEXT_INSET, TEXT_INSET, TEXT_INSET); 86 87 [self.contentView addSubview:textView]; 88 self.textView = textView; 89 90 return self; 91 } 92 93 #pragma mark - 加载数据 94 // 加载frame,初始化cell中子控件的数据、位置尺寸 95 - (void)setMessageFrame:(MessageFrame *) messageFrame { 96 _messageFrame = messageFrame; 97 98 // 1.发送时间 99 self.timeLabel.text = messageFrame.message.time; 100 self.timeLabel.frame = messageFrame.timeFrame; 101 102 // 2.头像 103 NSString *icon = (messageFrame.message.type == MessageTypeMe)? @"me":@"other"; 104 self.iconView.image = [UIImage imageNamed:icon]; 105 self.iconView.frame = messageFrame.iconFrame; 106 107 // 3.信息 108 [self.textView setTitle:messageFrame.message.text forState:UIControlStateNormal]; 109 self.textView.frame = messageFrame.textFrame; 110 111 // 3.1 设置聊天框 112 NSString *chatImageNormalName; 113 NSString *chatImageHighlightedName; 114 if (MessageTypeMe == messageFrame.message.type) { 115 chatImageNormalName = @"chat_send_nor"; 116 chatImageHighlightedName = @"chat_send_press_pic"; 117 } else { 118 chatImageNormalName = @"chat_receive_nor"; 119 chatImageHighlightedName = @"chat_receive_press_pic"; 120 } 121 122 UIImage *chatImageNormal = [UIImage resizableImage:chatImageNormalName]; 123 UIImage *chatImageHighlighted = [UIImage resizableImage:chatImageHighlightedName]; 124 [self.textView setBackgroundImage:chatImageNormal forState:UIControlStateNormal]; 125 [self.textView setBackgroundImage:chatImageHighlighted forState:UIControlStateHighlighted]; 126 } 127 128 129 @end
Controller:
1 // 2 // ViewController.m 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 9 #import "ViewController.h" 10 #import "Message.h" 11 #import "MessageCell.h" 12 #import "MessageFrame.h" 13 14 @interface ViewController () <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate> 15 16 /** 聊天区tableView */ 17 @property (weak, nonatomic) IBOutlet UITableView *tableView; 18 19 /** 信息记录数据 */ 20 @property(nonatomic, strong) NSMutableArray *messages; 21 22 /** 信息输入框 */ 23 @property (weak, nonatomic) IBOutlet UITextField *inputView; 24 25 @end 26 27 @implementation ViewController 28 29 - (void)viewDidLoad { 30 [super viewDidLoad]; 31 // Do any additional setup after loading the view, typically from a nib. 32 33 // 设置dataSource 34 self.tableView.dataSource = self; 35 36 // 设置tableView的delegate 37 self.tableView.delegate = self; 38 39 // 设置tableView背景色,当键盘呼出隐藏的时候,避免默认的黑色背景出现太突兀 40 self.tableView.backgroundColor = BACKGROUD_COLOR; 41 42 // 设置聊天区TableView 43 // 不使用分割线 44 self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 45 // 禁止选中cell 46 [self.tableView setAllowsSelection:NO]; 47 48 // 设置虚拟键盘监听器 49 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; 50 51 // 设置TextField文字左间距 52 self.inputView.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 0)]; 53 self.inputView.leftViewMode = UITextFieldViewModeAlways; 54 55 // 设置信息输入框的代理 56 self.inputView.delegate = self; 57 } 58 59 - (void)didReceiveMemoryWarning { 60 [super didReceiveMemoryWarning]; 61 // Dispose of any resources that can be recreated. 62 } 63 64 - (BOOL)prefersStatusBarHidden { 65 return YES; 66 } 67 68 #pragma mark - 数据加载 69 /** 延迟加载plist文件数据 */ 70 - (NSMutableArray *)messages { 71 if (nil == _messages) { 72 NSArray *dictArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"messages.plist" ofType:nil]]; 73 74 NSMutableArray *mdictArray = [NSMutableArray array]; 75 for (NSDictionary *dict in dictArray) { 76 Message *message = [Message messageWithDictionary:dict]; 77 78 // 判断是否发送时间与上一条信息的发送时间相同,若是则不用显示了 79 MessageFrame *lastMessageFrame = [mdictArray lastObject]; 80 if (lastMessageFrame && [message.time isEqualToString:lastMessageFrame.message.time]) { 81 message.hideTime = YES; 82 } 83 84 MessageFrame *messageFrame = [[MessageFrame alloc] init]; 85 messageFrame.message = message; 86 [mdictArray addObject:messageFrame]; 87 } 88 89 _messages = mdictArray; 90 } 91 92 return _messages; 93 } 94 95 #pragma mark - dataSource方法 96 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 97 return self.messages.count; 98 } 99 100 - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 101 MessageCell *cell = [MessageCell cellWithTableView:self.tableView]; 102 cell.messageFrame = self.messages[indexPath.row]; 103 104 return cell; 105 } 106 107 108 #pragma mark - tableView代理方法 109 /** 动态设置每个cell的高度 */ 110 - (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 111 MessageFrame *messageFrame = self.messages[indexPath.row]; 112 return messageFrame.cellHeight; 113 } 114 115 #pragma mark - scrollView 代理方法 116 /** 点击拖曳聊天区的时候,缩回键盘 */ 117 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 118 // 1.缩回键盘 119 [self.view endEditing:YES]; 120 } 121 122 123 #pragma mark - 监听事件 124 - (void) keyboardWillChangeFrame:(NSNotification *) note { 125 // 1.取得弹出后的键盘frame 126 CGRect keyboardFrame = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 127 128 // 2.键盘弹出的耗时时间 129 CGFloat duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]; 130 131 // 3.键盘变化时,view的位移,包括了上移/恢复下移 132 CGFloat transformY = keyboardFrame.origin.y - self.view.frame.size.height; 133 134 [UIView animateWithDuration:duration animations:^{ 135 self.view.transform = CGAffineTransformMakeTranslation(0, transformY); 136 }]; 137 } 138 139 #pragma mark - TextField 代理方法 140 /** 回车响应事件 */ 141 - (BOOL)textFieldShouldReturn:(UITextField *)textField { 142 // 我方发出信息 143 [self sendMessageWithContent:textField.text andType:MessageTypeMe]; 144 145 // 自动回复 146 [self sendMessageWithContent:[NSString stringWithFormat:@"%@\n%@", textField.text, @"你妹!!!"] andType:MessageTypeOhter]; 147 148 // 消除消息框内容 149 self.inputView.text = nil; 150 151 [self.tableView reloadData]; 152 153 // 滚动到最新的消息 154 NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:self.messages.count - 1 inSection:0]; 155 [self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; 156 157 return YES; // 返回值意义不明 158 } 159 160 // 发送消息 161 - (void) sendMessageWithContent:(NSString *) text andType:(MessageType) type { 162 // 获取当前时间 163 NSDate *date = [NSDate date]; 164 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 165 formatter.dateFormat = @"yyyy-MMM-dd hh:mm:ss"; 166 NSString *dateStr = [formatter stringFromDate:date]; 167 168 // 我方发出信息 169 NSDictionary *dict = @{@"text":text, 170 @"time":dateStr, 171 @"type":[NSString stringWithFormat:@"%d", type]}; 172 173 Message *message = [[Message alloc] init]; 174 [message setValuesForKeysWithDictionary:dict]; 175 MessageFrame *messageFrame = [[MessageFrame alloc] init]; 176 messageFrame.message = message; 177 178 [self.messages addObject:messageFrame]; 179 } 180 181 @end 182
工具类:
1 // 2 // NSString+Extension.h 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 // NSString扩展类 9 10 #import <Foundation/Foundation.h> 11 #import <UIKit/UIKit.h> 12 13 @interface NSString (Extension) 14 15 /** 测量文本的尺寸 */ 16 - (CGSize) sizeWithFont:(UIFont *)font maxSize:(CGSize) maxSize; 17 18 @end
1 // 2 // NSString+Extension.m 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 9 #import "NSString+Extension.h" 10 11 @implementation NSString (Extension) 12 13 /** 测量文本的尺寸 */ 14 - (CGSize)sizeWithFont:(UIFont *)font maxSize:(CGSize)maxSize { 15 NSDictionary *attrs = @{NSFontAttributeName: font}; 16 CGSize size = [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size; 17 18 return size; 19 } 20 21 @end
1 // 2 // UIImage+Extension.h 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 // NSImage 类的扩展 9 10 #import <Foundation/Foundation.h> 11 #import <UIKit/UIKit.h> 12 13 @interface UIImage (Extension) 14 15 + (UIImage *) resizableImage:(NSString *) imageName; 16 17 @end 18
1 // 2 // UIImage+Extension.m 3 // QQChatDemo 4 // 5 // Created by hellovoidworld on 14/12/8. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 9 #import "UIImage+Extension.h" 10 11 @implementation UIImage (Extension) 12 13 + (UIImage *) resizableImage:(NSString *) imageName { 14 UIImage *image = [UIImage imageNamed:imageName]; 15 // 取图片中部的1 x 1进行拉伸 16 UIEdgeInsets insets = UIEdgeInsetsMake(image.size.height/2, image.size.width/2, image.size.height/2 + 1, image.size.width/2 + 1); 17 return [image resizableImageWithCapInsets:insets]; 18 } 19 20 @end