A.需求
做出一個類似於QQ、微信的聊天界面
1.每個cell包含發送時間、發送人(頭像)、發送信息
2.使用對方頭像放在左邊,我方頭像在右邊
3.對方信息使用白色背景對話框,我方信息使用藍色背景對話框
4.隱藏相同的發送時間
5.底部功能按鈕:語音按鈕、消息輸入框、表情按鈕、附加按鈕
6.響應鍵盤事件,呼出鍵盤、隱藏鍵盤時對上述的視圖作出上移操作
7.鍵盤的發送事件處理
Code Source:
B.實現點
1.底層視圖搭建
上部分聊天信息框:UITableView
下部分功能區:UIButton
信息輸入框使用無邊框,然后使用自帶背景圖片,以保證在不同版本的iOS中樣式一致
2.構建框架
依照“微博展示”的代碼框架,設計:
- 自定義message模型
- 自定義cell
- 裝載了message模型和cell子控件位置尺寸的frame
3.使用擴展,給NSString加上文本size計算的功能
1 @implementation NSString (Extension)
2
3 /** 測量文本的尺寸 */
4 - (CGSize)sizeWithFont:(UIFont *)font maxSize:(CGSize)maxSize {
5 NSDictionary *attrs = @{NSFontAttributeName: font};
6 CGSize size = [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;
7
8 return size;
9 }
10
11 @end
給信息文本框計算位置和尺寸
1 // 3.信息,尺寸可變
2 CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
3 // 3.1 設置最大尺寸
4 CGSize textMaxSize = CGSizeMake(screenWidth - iconWidth - padding * 5, MAXFLOAT);
5 // 3.2 計算真實尺寸
6 CGSize textRealSize = [message.text sizeWithFont:MESSAGE_TEXT_FONT maxSize:textMaxSize];
7
8 // 3.3 調整信息的位置
9 CGFloat textX;
10 if (MessageTypeMe == message.type) {
11 // 我方,放在靠右
12 textX = CGRectGetMinX(_iconFrame) - textRealSize.width - padding;
13 } else {
14 // 對方,放在靠左
15 textX = CGRectGetMaxX(_iconFrame) + padding;
16 }
17
18 CGFloat textY = iconY;
19 _textFrame = CGRectMake(textX, textY, textRealSize.width, textRealSize.height);
4.使用數據中的信息類型,判斷是我方發出的信息還是對方發出的信息,計算頭像和信息的位置
1 // 2.頭像
2 CGFloat iconWidth = 40;
3 CGFloat iconHeight = 40;
4
5 // 2.1 根據信息的發送方調整頭像位置
6 CGFloat iconX;
7 if (MessageTypeMe == message.type) {
8 // 我方,放在右邊
9 iconX = [UIScreen mainScreen].bounds.size.width - padding - iconWidth;
10 } else {
11 // 對方,放在左邊
12 iconX = padding;
13 }
14
15 CGFloat iconY = CGRectGetMaxY(_timeFrame) + padding;
16 _iconFrame = CGRectMake(iconX, iconY, iconWidth, iconHeight);
5.加上信息背景框
我方:使用藍色背景聊天框
對方:使用白色背景聊天框
重點:圖片的中心拉伸,利用圖片分別水平、垂直方向某個區域進行拉伸,保持其他部分的圖形 —>對UIImage使用擴展,返回具備了特定拉伸方式屬性的圖片
拉伸圖片方式屬性:
1 @implementation UIImage (Extension)
2
3 + (UIImage *) resizableImage:(NSString *) imageName {
4 UIImage *image = [UIImage imageNamed:imageName];
5 // 取圖片中部的1 x 1進行拉伸
6 UIEdgeInsets insets = UIEdgeInsetsMake(image.size.height/2, image.size.width/2, image.size.height/2 + 1, image.size.width/2 + 1);
7 return [image resizableImageWithCapInsets:insets];
8 }
9
10 @end
設置圖片:
1 // 3.1 設置聊天框
2 NSString *chatImageNormalName;
3 NSString *chatImageHighlightedName;
4 if (MessageTypeMe == messageFrame.message.type) {
5 chatImageNormalName = @"chat_send_nor";
6 chatImageHighlightedName = @"chat_send_press_pic";
7 } else {
8 chatImageNormalName = @"chat_receive_nor";
9 chatImageHighlightedName = @"chat_receive_press_pic";
10 }
11
12 UIImage *chatImageNormal = [UIImage resizableImage:chatImageNormalName];
13 UIImage *chatImageHighlighted = [UIImage resizableImage:chatImageHighlightedName];
14 [self.textView setBackgroundImage:chatImageNormal forState:UIControlStateNormal];
15 [self.textView setBackgroundImage:chatImageHighlighted forState:UIControlStateHighlighted];
6.調整信息文字內邊距,讓文字被“包裹”在聊天框內
1 // 3.2 調整文字的內邊距 2 textView.contentEdgeInsets = UIEdgeInsetsMake(TEXT_INSET, TEXT_INSET, TEXT_INSET, TEXT_INSET);
因為背景圖片邊緣有空白,改變了文字內邊距之后,高度會變高,需要對裝載信息的view的frame尺寸做出相應改變:
1 // 3.信息,尺寸可變
2 CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
3 // 3.1 設置文本最大尺寸
4 CGSize textMaxSize = CGSizeMake(screenWidth - iconWidth - padding * 10, MAXFLOAT);
5 // 3.2 計算文本真實尺寸
6 CGSize textRealSize = [message.text sizeWithFont:MESSAGE_TEXT_FONT maxSize:textMaxSize];
7
8 // 3.3 按鈕尺寸
9 CGSize btnSize = CGSizeMake(textRealSize.width + TEXT_INSET*2, textRealSize.height + TEXT_INSET*2);
10
11 // 3.4 調整信息的位置
12 CGFloat textX;
13 if (MessageTypeMe == message.type) {
14 // 我方,放在靠右
15 textX = CGRectGetMinX(_iconFrame) - btnSize.width - padding;
16 } else {
17 // 對方,放在靠左
18 textX = CGRectGetMaxX(_iconFrame) + padding;
19 }
20
21 CGFloat textY = iconY;
22 _textFrame = CGRectMake(textX, textY, btnSize.width, btnSize.height);
7.屏蔽相同的發送時間
(1)在message模型中定義一個標志
1 /** 是否隱藏發送時間 */ 2 @property(nonatomic, assign) BOOL hideTime;
(2)當控制器從plist文件裝載信息的時候初始化此標志
1 // 判斷是否發送時間與上一條信息的發送時間相同,若是則不用顯示了
2 MessageFrame *lastMessageFrame = [mdictArray lastObject];
3 if (lastMessageFrame && [message.time isEqualToString:lastMessageFrame.message.time]) {
4 message.hideTime = YES;
5 }
(3)只有hideTime == NO,計算frame的時候,長期需要計算發送時間的frame
1 // 1.發送時間
2 if (NO == message.hideTime) {
3 CGFloat timeWidth = [UIScreen mainScreen].bounds.size.width;
4 CGFloat timeHeight = 40;
5 CGFloat timeX = 0;
6 CGFloat timeY = 0;
7 _timeFrame = CGRectMake(timeX, timeY, timeWidth, timeHeight);
8 }
8.響應鍵盤呼出縮回事件,上移或下移恢復整個版面(聊天區和功能區)
(1)設置控制器為鍵盤監聽器
// 設置虛擬鍵盤監聽器
1 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
(2)編寫監聽方法
1 /** 點擊拖曳聊天區的時候,縮回鍵盤 */
2 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
3 // 1.縮回鍵盤
4 [self.view endEditing:YES];
5 }
6
7
8 #pragma mark - 監聽事件
9 - (void) keyboardWillChangeFrame:(NSNotification *) note {
10 // 1.取得彈出后的鍵盤frame
11 CGRect keyboardFrame = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
12
13 // 2.鍵盤彈出的耗時時間
14 CGFloat duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue];
15
16 // 3.鍵盤變化時,view的位移,包括了上移/恢復下移
17 CGFloat transformY = keyboardFrame.origin.y - self.view.frame.size.height;
18
19 [UIView animateWithDuration:duration animations:^{
20 self.view.transform = CGAffineTransformMakeTranslation(0, transformY);
21 }];
22
23
24 }
9.設置數據框TextField,改變鍵盤
(1)當沒有文字的時候禁用回車,設置回車樣式
(2)調不出中文鍵盤
雖然在設置里面添加了中文鍵盤,但是依然找不到進入中文鍵盤的按鈕
10.發送消息
(1)拖入信息輸入框到控制器,設置控制器為輸入框TextField的代理
1 // 設置信息輸入框的代理 2 self.inputView.delegate = self;
(2)響應回車事件
1 #pragma mark - TextField 代理方法
2 /** 回車響應事件 */
3 - (BOOL)textFieldShouldReturn:(UITextField *)textField {
4 // 我方發出信息
5 [self sendMessageWithContent:textField.text andType:MessageTypeMe];
6
7 // 自動回復
8 [self sendMessageWithContent:[NSString stringWithFormat:@"%@\n%@", textField.text, @"你妹!!!"] andType:MessageTypeOhter];
9
10 // 消除消息框內容
11 self.inputView.text = nil;
12
13 [self.tableView reloadData];
14
15 // 滾動到最新的消息
16 NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:self.messages.count - 1 inSection:0];
17 [self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
18
19 return YES; // 返回值意義不明
20 }
21
22 // 發送消息
23 - (void) sendMessageWithContent:(NSString *) text andType:(MessageType) type {
24 // 獲取當前時間
25 NSDate *date = [NSDate date];
26 NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
27 formatter.dateFormat = @"yyyy-MMM-dd hh:mm:ss";
28 NSString *dateStr = [formatter stringFromDate:date];
29
30 // 我方發出信息
31 NSDictionary *dict = @{@"text":text,
32 @"time":dateStr,
33 @"type":[NSString stringWithFormat:@"%d", type]};
34
35 Message *message = [[Message alloc] init];
36 [message setValuesForKeysWithDictionary:dict];
37 MessageFrame *messageFrame = [[MessageFrame alloc] init];
38 messageFrame.message = message;
39
40 [self.messages addObject:messageFrame];
41 }
42
(3)自動滾動在最底端
1 // 滾動到最新的消息 2 NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:self.messages.count - 1 inSection:0]; 3 [self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];












