A.需求
做出一个类似于QQ、微信的聊天界面
1.每个cell包含发送时间、发送人(头像)、发送信息
2.使用对方头像放在左边,我方头像在右边
3.对方信息使用白色背景对话框,我方信息使用蓝色背景对话框
4.隐藏相同的发送时间
5.底部功能按钮:语音按钮、消息输入框、表情按钮、附加按钮
6.响应键盘事件,呼出键盘、隐藏键盘时对上述的视图作出上移操作
7.键盘的发送事件处理
Code Source: https://github.com/hellovoidworld/ChatDemo
B.实现点
1.底层视图搭建
上部分聊天信息框:UITableView
下部分功能区:UIButton
信息输入框使用无边框,然后使用自带背景图片,以保证在不同版本的iOS中样式一致
2.构建框架
依照“微博展示”的代码框架,设计:
自定义message模型自定义cell装载了message模型和cell子控件位置尺寸的frame
3.使用扩展,给NSString加上文本size计算的功能
1@implementation NSString (Extension)23/** 测量文本的尺寸*/4- (CGSize)sizeWithFont:(UIFont *)font maxSize:(CGSize)maxSize {5NSDictionary *attrs =@{NSFontAttributeName: font};6CGSize size =[self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;78returnsize;9}1011@end
给信息文本框计算位置和尺寸
1//3.信息,尺寸可变2CGFloat screenWidth =[UIScreen mainScreen].bounds.size.width;3//3.1 设置最大尺寸4CGSize textMaxSize = CGSizeMake(screenWidth - iconWidth - padding *5, MAXFLOAT);5//3.2 计算真实尺寸6CGSize textRealSize =[message.text sizeWithFont:MESSAGE_TEXT_FONT maxSize:textMaxSize];78//3.3 调整信息的位置9CGFloat textX;10if(MessageTypeMe ==message.type) {11//我方,放在靠右12textX = CGRectGetMinX(_iconFrame) - textRealSize.width -padding;13}else{14//对方,放在靠左15textX = CGRectGetMaxX(_iconFrame) +padding;16}1718CGFloat textY =iconY;19_textFrame = CGRectMake(textX, textY, textRealSize.width, textRealSize.height);
4.使用数据中的信息类型,判断是我方发出的信息还是对方发出的信息,计算头像和信息的位置
1//2.头像2CGFloat iconWidth =40;3CGFloat iconHeight =40;45//2.1 根据信息的发送方调整头像位置6CGFloat iconX;7if(MessageTypeMe ==message.type) {8//我方,放在右边9iconX = [UIScreen mainScreen].bounds.size.width - padding -iconWidth;10}else{11//对方,放在左边12iconX =padding;13}1415CGFloat iconY = CGRectGetMaxY(_timeFrame) +padding;16_iconFrame = CGRectMake(iconX, iconY, iconWidth, iconHeight);
5.加上信息背景框
我方:使用蓝色背景聊天框
对方:使用白色背景聊天框
重点:图片的中心拉伸,利用图片分别水平、垂直方向某个区域进行拉伸,保持其他部分的图形 —>对UIImage使用扩展,返回具备了特定拉伸方式属性的图片
拉伸图片方式属性:
1@implementationUIImage (Extension)23+ (UIImage *) resizableImage:(NSString *) imageName {4UIImage *image =[UIImage imageNamed:imageName];5//取图片中部的1 x 1进行拉伸6UIEdgeInsets insets = UIEdgeInsetsMake(image.size.height/2, image.size.width/2, image.size.height/2+1, image.size.width/2+1);7return[image resizableImageWithCapInsets:insets];8}910@end
设置图片:
1//3.1 设置聊天框2NSString *chatImageNormalName;3NSString *chatImageHighlightedName;4if(MessageTypeMe ==messageFrame.message.type) {5chatImageNormalName =@"chat_send_nor";6chatImageHighlightedName =@"chat_send_PRess_pic";7}else{8chatImageNormalName =@"chat_receive_nor";9chatImageHighlightedName =@"chat_receive_press_pic";10}1112UIImage *chatImageNormal =[UIImage resizableImage:chatImageNormalName];13UIImage *chatImageHighlighted =[UIImage resizableImage:chatImageHighlightedName];14[self.textView setBackgroundImage:chatImageNormal forState:UIControlStateNormal];15[self.textView setBackgroundImage:chatImageHighlighted forState:UIControlStateHighlighted];
6.调整信息文字内边距,让文字被“包裹”在聊天框内
1//3.2 调整文字的内边距2textView.contentEdgeInsets = UIEdgeInsetsMake(TEXT_INSET, TEXT_INSET, TEXT_INSET, TEXT_INSET);
因为背景图片边缘有空白,改变了文字内边距之后,高度会变高,需要对装载信息的view的frame尺寸做出相应改变:
1//3.信息,尺寸可变2CGFloat screenWidth =[UIScreen mainScreen].bounds.size.width;3//3.1 设置文本最大尺寸4CGSize textMaxSize = CGSizeMake(screenWidth - iconWidth - padding *10, MAXFLOAT);5//3.2 计算文本真实尺寸6CGSize textRealSize =[message.text sizeWithFont:MESSAGE_TEXT_FONT maxSize:textMaxSize];78//3.3 按钮尺寸9CGSize btnSize = CGSizeMake(textRealSize.width + TEXT_INSET*2, textRealSize.height + TEXT_INSET*2);1011//3.4 调整信息的位置12CGFloat textX;13if(MessageTypeMe ==message.type) {14//我方,放在靠右15textX = CGRectGetMinX(_iconFrame) - btnSize.width -padding;16}else{17//对方,放在靠左18textX = CGRectGetMaxX(_iconFrame) +padding;19}2021CGFloat textY =iconY;22_textFrame = CGRectMake(textX, textY, btnSize.width, btnSize.height);
7.屏蔽相同的发送时间
(1)在message模型中定义一个标志
1/** 是否隐藏发送时间*/2@property(nonatomic, assign) BOOL hideTime;
(2)当控制器从plist文件装载信息的时候初始化此标志
1//判断是否发送时间与上一条信息的发送时间相同,若是则不用显示了2MessageFrame *lastMessageFrame =[mdictArray lastObject];3if(lastMessageFrame &&[message.time isEqualToString:lastMessageFrame.message.time]) {4message.hideTime =YES;5}
(3)只有hideTime == NO,计算frame的时候,长期需要计算发送时间的frame
1//1.发送时间2if(NO ==message.hideTime) {3CGFloat timeWidth =[UIScreen mainScreen].bounds.size.width;4CGFloat timeHeight =40;5CGFloat timeX =0;6CGFloat timeY =0;7_timeFrame =CGRectMake(timeX, timeY, timeWidth, timeHeight);8}
8.响应键盘呼出缩回事件,上移或下移恢复整个版面(聊天区和功能区)
(1)设置控制器为键盘监听器
// 设置虚拟键盘监听器
1[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotificationobject:nil];
(2)编写监听方法
1/** 点击拖曳聊天区的时候,缩回键盘*/2- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {3//1.缩回键盘4[self.view endEditing:YES];5}678#pragmamark - 监听事件9- (void) keyboardWillChangeFrame:(NSNotification *) note {10//1.取得弹出后的键盘frame11CGRect keyboardFrame =[note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];1213//2.键盘弹出的耗时时间14CGFloat duration =[note.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue];1516//3.键盘变化时,view的位移,包括了上移/恢复下移17CGFloat transformY = keyboardFrame.origin.y -self.view.frame.size.height;1819[UIView animateWithDuration:duration animations:^{20self.view.transform = CGAffineTransformMakeTranslation(0, transformY);21}];222324}
9.设置数据框TextField,改变键盘
(1)当没有文字的时候禁用回车,设置回车样式
(2)调不出中文键盘
虽然在设置里面添加了中文键盘,但是依然找不到进入中文键盘的按钮
10.发送消息
(1)拖入信息输入框到控制器,设置控制器为输入框TextField的代理
1//设置信息输入框的代理2self.inputView.delegate= self;
(2)响应回车事件
1#pragmamark - TextField 代理方法2/** 回车响应事件*/3- (BOOL)textFieldShouldReturn:(UITextField *)textField {4//我方发出信息5[self sendMessageWithContent:textField.text andType:MessageTypeMe];67//自动回复8[self sendMessageWithContent:[NSString stringWithFormat:@"%@\n%@", textField.text,@"你妹!!!"] andType:MessageTypeOhter];910//消除消息框内容11self.inputView.text =nil;1213[self.tableView reloadData];1415//滚动到最新的消息16NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:self.messages.count -1inSection:0];17[self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];1819returnYES;//返回值意义不明20}2122//发送消息23- (void) sendMessageWithContent:(NSString *) text andType:(MessageType) type {24//获取当前时间25NSDate *date =[NSDate date];26NSDateFormatter *formatter =[[NSDateFormatter alloc] init];27formatter.dateFormat =@"yyyy-MMM-dd hh:mm:ss";28NSString *dateStr =[formatter stringFromDate:date];2930//我方发出信息31NSDictionary *dict = @{@"text":text,32@"time":dateStr,33@"type":[NSString stringWithFormat:@"%d", type]};3435Message *message =[[Message alloc] init];36[message setValuesForKeysWithDictionary:dict];37MessageFrame *messageFrame =[[MessageFrame alloc] init];38messageFrame.message =message;3940[self.messages addObject:messageFrame];41}42
(3)自动滚动在最底端
1//滚动到最新的消息2NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:self.messages.count -1inSection:0];3[self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];