You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1374 lines
49 KiB

//
// XHMessageTableViewController.m
// MessageDisplayExample
//
// Created by HUAJIE-1 on 14-4-24.
// Copyright (c) 2014年 曾宪华 开发团队(http://iyilunba.com ) 本人QQ:543413507 本人QQ群(142557668). All rights reserved.
//
#import "XHMessageTableViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import "XHAudioPlayerHelper.h"
#import "JSToastDialogs.h"
@interface XHMessageTableViewController ()
{
CGFloat _defInputFrameHeight; // 默认的输入工具条的高度
}
/**
* 判断是否用户手指滚动
*/
@property (nonatomic, assign) BOOL isUserScrolling;
/**
* 记录旧的textView contentSize Heigth
*/
@property (nonatomic, assign) CGFloat previousTextViewContentHeight;
/**
* 记录键盘的高度,为了适配iPad和iPhone
*/
@property (nonatomic, assign) CGFloat keyboardViewHeight;
@property (nonatomic, assign, readwrite) XHInputViewType textViewInputViewType;
@property (nonatomic, weak, readwrite) XHMessageTableView *messageTableView;
@property (nonatomic, weak, readwrite) XHMessageInputView *messageInputView;
@property (nonatomic, weak, readwrite) XHShareMenuView *shareMenuView;
@property (nonatomic, weak, readwrite) XHEmotionManagerView *emotionManagerView;
@property (nonatomic, strong, readwrite) XHVoiceRecordHUD *voiceRecordHUD;
@property (nonatomic, strong) UIView *headerContainerView;
@property (nonatomic, strong) UIActivityIndicatorView *loadMoreActivityIndicatorView;
/**
* 管理地理位置的工具对象
*/
@property (nonatomic, strong) XHLocationHelper *locationHelper;
/**
* 判断是不是超出了录音最大时长
*/
@property (nonatomic) BOOL isMaxTimeStop;
#pragma mark - DataSource Change
/**
* 改变数据源需要的子线程
*
* @param queue 子线程执行完成的回调block
*/
- (void)exChangeMessageDataSourceQueue:(void (^)())queue;
/**
* 执行块代码在主线程
*
* @param queue 主线程执行完成回调block
*/
- (void)exMainQueue:(void (^)())queue;
#pragma mark - Previte Method
/**
* 判断是否允许滚动
*
* @return 返回判断结果
*/
- (BOOL)shouldAllowScroll;
#pragma mark - Life Cycle
/**
* 配置默认参数
*/
- (void)setup;
/**
* 初始化显示控件
*/
- (void)initilzer;
#pragma mark - RecorderPath Helper Method
/**
* 获取录音的路径
*
* @return 返回录音的路径
*/
- (NSString *)getRecorderPath;
#pragma mark - UITextView Helper Method
/**
* 获取某个UITextView对象的content高度
*
* @param textView 被获取的textView对象
*
* @return 返回高度
*/
- (CGFloat)getTextViewContentH:(UITextView *)textView;
#pragma mark - Layout Message Input View Helper Method
/**
* 动态改变TextView的高度
*
* @param textView 被改变的textView对象
*/
- (void)layoutAndAnimateMessageInputTextView:(UITextView *)textView;
#pragma mark - Scroll Message TableView Helper Method
/**
* 根据bottom的数值配置消息列表的内部布局变化
*
* @param bottom 底部的空缺高度
*/
- (void)setTableViewInsetsWithBottomValue:(CGFloat)bottom;
/**
* 根据底部高度获取UIEdgeInsets常量
*
* @param bottom 底部高度
*
* @return 返回UIEdgeInsets常量
*/
- (UIEdgeInsets)tableViewInsetsWithBottomValue:(CGFloat)bottom;
#pragma mark - Message Calculate Cell Height
/**
* 统一计算Cell的高度方法
*
* @param message 被计算目标消息对象
* @param indexPath 被计算目标消息所在的位置
*
* @return 返回计算的高度
*/
- (CGFloat)calculateCellHeightWithMessage:(XHMessage *)message atIndexPath:(NSIndexPath *)indexPath;
#pragma mark - Message Send helper Method
/**
* 根据文本开始发送文本消息
*
* @param text 目标文本
*/
- (void)didSendMessageWithText:(NSString *)text;
/**
* 根据图片开始发送图片消息
*
* @param photo 目标图片
*/
- (void)didSendMessageWithPhoto:(UIImage *)photo;
/**
* 根据视频的封面和视频的路径开始发送视频消息
*
* @param videoConverPhoto 目标视频的封面图
* @param videoPath 目标视频的路径
*/
- (void)didSendMessageWithVideoConverPhoto:(UIImage *)videoConverPhoto videoPath:(NSString *)videoPath;
/**
* 根据录音路径开始发送语音消息
*
* @param voicePath 目标语音路径
* @param voiceDuration 目标语音时长
*/
- (void)didSendMessageWithVoice:(NSString *)voicePath voiceDuration:(NSNumber*)voiceDuration;
/**
* 根据第三方gif表情路径开始发送表情消息
*
* @param emotionPath 目标gif表情路径
*/
- (void)didSendEmotionMessageWithEmotionPath:(NSString *)emotionPath;
/**
* 根据地理位置信息和地理经纬度开始发送地理位置消息
*
* @param geolcations 目标地理信息
* @param location 目标地理经纬度
*/
- (void)didSendGeolocationsMessageWithGeolocaltions:(NSString *)geolcations location:(CLLocation *)location;
#pragma mark - Voice Recording Helper Method
/**
* 开始录音
*/
- (void)startRecord;
/**
* 完成录音
*/
- (void)finishRecorded;
/**
* 想停止录音
*/
- (void)pauseRecord;
/**
* 继续录音
*/
- (void)resumeRecord;
/**
* 取消录音
*/
- (void)cancelRecord;
@end
@implementation XHMessageTableViewController
#pragma mark - DataSource Change
- (void)exChangeMessageDataSourceQueue:(void (^)())queue {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), queue);
}
- (void)exMainQueue:(void (^)())queue {
dispatch_async(dispatch_get_main_queue(), queue);
}
- (void)addMessage:(XHMessage *)addedMessage {
WEAKSELF
[self exChangeMessageDataSourceQueue:^{
NSMutableArray *messages = [NSMutableArray arrayWithArray:[weakSelf getMessagesToShow]];
[messages addObject:addedMessage];
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:1];
[indexPaths addObject:[NSIndexPath indexPathForRow:messages.count - 1 inSection:0]];
[weakSelf exMainQueue:^{
weakSelf.messages = messages;
[weakSelf.messageTableView reloadData];
// [weakSelf.messageTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[weakSelf scrollToBottomAnimated:YES];
}];
}];
}
- (void)removeMessageAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row >= self.messages.count)
return;
[self.messages removeObjectAtIndex:indexPath.row];
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:1];
[indexPaths addObject:indexPath];
[self.messageTableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationBottom];
}
static CGPoint delayOffset = {0.0};
// http://stackoverflow.com/a/11602040 Keep UITableView static when inserting rows at the top
- (void)insertOldMessages:(NSArray *)oldMessages completion:(void (^)())completion {
WEAKSELF
[self exChangeMessageDataSourceQueue:^{
delayOffset = weakSelf.messageTableView.contentOffset;
NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:oldMessages.count];
NSMutableIndexSet *indexSets = [[NSMutableIndexSet alloc] init];
[oldMessages enumerateObjectsUsingBlock:^(XHMessage *obj, NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
[indexPaths addObject:indexPath];
delayOffset.y += [weakSelf calculateCellHeightWithMessage:[oldMessages objectAtIndex:idx] atIndexPath:indexPath];
[indexSets addIndex:idx];
}];
NSMutableArray *messages = [[NSMutableArray alloc] initWithArray:weakSelf.messages];
[messages insertObjects:oldMessages atIndexes:indexSets];
[weakSelf exMainQueue:^{
[UIView setAnimationsEnabled:NO];
weakSelf.messageTableView.userInteractionEnabled = NO;
//[self.messageTableView beginUpdates];
weakSelf.messages = messages;
[weakSelf.messageTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
//[self.messageTableView endUpdates];
[UIView setAnimationsEnabled:YES];
[weakSelf.messageTableView setContentOffset:delayOffset animated:NO];
weakSelf.messageTableView.userInteractionEnabled = YES;
if (completion) {
completion();
}
}];
}];
}
- (void)insertOldMessages:(NSArray *)oldMessages {
[self insertOldMessages:oldMessages completion:nil];
}
#pragma mark - Propertys
- (NSMutableArray *)getMessagesToShow{
NSMutableArray *arr = [NSMutableArray arrayWithArray:_messages];
[_messages enumerateObjectsUsingBlock:^(XHMessage *obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.hide) {
[arr removeObject:obj];
}
}];
return arr;
}
- (NSMutableArray *)messages {
if (!_messages) {
_messages = [[NSMutableArray alloc] initWithCapacity:0];
}
return _messages;
}
- (UIView *)headerContainerView {
if (!_headerContainerView) {
_headerContainerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 8)];
_headerContainerView.backgroundColor = self.messageTableView.backgroundColor;
[_headerContainerView addSubview:self.loadMoreActivityIndicatorView];
}
return _headerContainerView;
}
- (UIActivityIndicatorView *)loadMoreActivityIndicatorView {
if (!_loadMoreActivityIndicatorView) {
_loadMoreActivityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
_loadMoreActivityIndicatorView.center = CGPointMake(CGRectGetWidth(_headerContainerView.bounds) / 2.0, CGRectGetHeight(_headerContainerView.bounds) / 2.0);
}
return _loadMoreActivityIndicatorView;
}
- (void)setLoadingMoreMessage:(BOOL)loadingMoreMessage {
_loadingMoreMessage = loadingMoreMessage;
if (loadingMoreMessage) {
[self.loadMoreActivityIndicatorView startAnimating];
} else {
[self.loadMoreActivityIndicatorView stopAnimating];
}
}
- (void)setLoadMoreActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)loadMoreActivityIndicatorViewStyle {
_loadMoreActivityIndicatorViewStyle = loadMoreActivityIndicatorViewStyle;
self.loadMoreActivityIndicatorView.activityIndicatorViewStyle = loadMoreActivityIndicatorViewStyle;
}
- (XHShareMenuView *)shareMenuView {
if (!_shareMenuView) {
XHShareMenuView *shareMenuView = [[XHShareMenuView alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.view.bounds), CGRectGetWidth(self.view.bounds), self.keyboardViewHeight)];
shareMenuView.delegate = self;
shareMenuView.backgroundColor = [UIColor colorWithWhite:0.961 alpha:1.000];
shareMenuView.alpha = 0.0;
shareMenuView.shareMenuItems = self.shareMenuItems;
[self.view addSubview:shareMenuView];
_shareMenuView = shareMenuView;
}
[self.view bringSubviewToFront:_shareMenuView];
return _shareMenuView;
}
- (XHEmotionManagerView *)emotionManagerView {
if (!_emotionManagerView)
{
CGFloat hh = ((ScreenWidth-80)/4)*3+48;
XHEmotionManagerView *emotionManagerView = [[XHEmotionManagerView alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.view.bounds), ScreenWidth, Adapted(hh))];
emotionManagerView.delegate = self;
emotionManagerView.dataSource = self;
emotionManagerView.backgroundColor = RGB(250, 249, 249);
emotionManagerView.alpha = 0.0;
[self.view addSubview:emotionManagerView];
_emotionManagerView = emotionManagerView;
}
[self.view bringSubviewToFront:_emotionManagerView];
return _emotionManagerView;
}
- (XHVoiceRecordHUD *)voiceRecordHUD {
if (!_voiceRecordHUD) {
_voiceRecordHUD = [[XHVoiceRecordHUD alloc] initWithFrame:CGRectMake(0, 0, 140, 140)];
}
return _voiceRecordHUD;
}
- (XHPhotographyHelper *)photographyHelper {
if (!_photographyHelper) {
_photographyHelper = [[XHPhotographyHelper alloc] init];
}
return _photographyHelper;
}
- (XHLocationHelper *)locationHelper {
if (!_locationHelper) {
_locationHelper = [[XHLocationHelper alloc] init];
}
return _locationHelper;
}
- (XHVoiceRecordHelper *)voiceRecordHelper {
if (!_voiceRecordHelper) {
_isMaxTimeStop = NO;
WEAKSELF
_voiceRecordHelper = [[XHVoiceRecordHelper alloc] init];
_voiceRecordHelper.maxTimeStopRecorderCompletion = ^{
DLog(@"已经达到最大限制时间了,进入下一步的提示");
// Unselect and unhilight the hold down button, and set isMaxTimeStop to YES.
UIButton *holdDown = weakSelf.messageInputView.holdDownButton;
holdDown.selected = NO;
holdDown.highlighted = NO;
weakSelf.isMaxTimeStop = YES;
// add by lsz 2021-06-09
[[JSToastDialogs shareInstance] makeToast:@"语音最长15秒" duration:3.0]; //,文字最多15个
[weakSelf finishRecorded];
};
_voiceRecordHelper.peakPowerForChannel = ^(float peakPowerForChannel) {
weakSelf.voiceRecordHUD.peakPower = peakPowerForChannel;
};
_voiceRecordHelper.recordProgress = ^(float progress) {
weakSelf.voiceRecordHUD.recordingTime = progress;
};
_voiceRecordHelper.maxRecordTime = kVoiceRecorderTotalTime;
}
return _voiceRecordHelper;
}
#pragma mark - Messages View Controller
- (void)finishSendMessageWithBubbleMessageType:(XHBubbleMessageMediaType)mediaType {
switch (mediaType) {
case XHBubbleMessageMediaTypeText: {
[self.messageInputView.inputTextView setText:nil];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
self.messageInputView.inputTextView.enablesReturnKeyAutomatically = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.messageInputView.inputTextView.enablesReturnKeyAutomatically = YES;
[self.messageInputView.inputTextView reloadInputViews];
});
}
break;
}
case XHBubbleMessageMediaTypePhoto: {
break;
}
case XHBubbleMessageMediaTypeVideo: {
break;
}
case XHBubbleMessageMediaTypeVoice: {
break;
}
case XHBubbleMessageMediaTypeEmotion: {
break;
}
case XHBubbleMessageMediaTypeLocalPosition: {
break;
}
default:
break;
}
}
- (void)setBackgroundColor:(UIColor *)color {
self.view.backgroundColor = color;
_messageTableView.backgroundColor = color;
}
- (void)setBackgroundImage:(UIImage *)backgroundImage {
self.messageTableView.backgroundView = nil;
self.messageTableView.backgroundView = [[UIImageView alloc] initWithImage:backgroundImage];
}
- (void)scrollToBottomAnimated:(BOOL)animated {
if (![self shouldAllowScroll])
return;
NSInteger rows = [self.messageTableView numberOfRowsInSection:0];
if (rows > 0) {
[self.messageTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rows - 1 inSection:0]
atScrollPosition:UITableViewScrollPositionBottom
animated:animated];
}
}
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath
atScrollPosition:(UITableViewScrollPosition)position
animated:(BOOL)animated {
if (![self shouldAllowScroll])
return;
[self.messageTableView scrollToRowAtIndexPath:indexPath
atScrollPosition:position
animated:animated];
}
#pragma mark - Previte Method
- (BOOL)shouldAllowScroll {
if (self.isUserScrolling) {
if ([self.delegate respondsToSelector:@selector(shouldPreventScrollToBottomWhileUserScrolling)]
&& [self.delegate shouldPreventScrollToBottomWhileUserScrolling]) {
return NO;
}
}
return YES;
}
#pragma mark - Life Cycle
- (void)setup {
// iPhone or iPad keyboard view height set here.
self.keyboardViewHeight = (kIsiPad ? 264 : 216);
_allowsPanToDismissKeyboard = NO;
_allowsSendText = YES;
_allowsSendVoice = YES;
_allowsSendMultiMedia = YES;
_allowsSendFace = YES;
_inputViewStyle = XHMessageInputViewStyleFlat;
self.delegate = self;
self.msgDataSource = self;
}
- (id)init {
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib {
[self setup];
}
//xTT
- (void)applicationWillResignActive:(NSNotification *)notification{
if (self.voiceRecordHelper.recorder.isRecording) {
[self cancelRecord];
}
[self.currentSelectedCell.messageBubbleView.animationVoiceImageView stopAnimating];
[[XHAudioPlayerHelper shareInstance] stopAudio];
}
- (void)initilzer {
if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) {
self.automaticallyAdjustsScrollViewInsets = NO;
}
if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
//xTT
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:app];
// 默认设置用户滚动为NO
_isUserScrolling = NO;
// 初始化message tableView
XHMessageTableView *messageTableView = [[XHMessageTableView alloc] initWithFrame:CGRectMake(0, iPhoneX_NavHeight, ScreenWidth, ScreenHeight-iPhoneX_NavHeight) style:UITableViewStylePlain];
messageTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
messageTableView.dataSource = self;
messageTableView.delegate = self;
messageTableView.separatorColor = [UIColor clearColor];
messageTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
BOOL shouldLoadMoreMessagesScrollToTop = YES;
if ([self.delegate respondsToSelector:@selector(shouldLoadMoreMessagesScrollToTop)]) {
shouldLoadMoreMessagesScrollToTop = [self.delegate shouldLoadMoreMessagesScrollToTop];
}
if (shouldLoadMoreMessagesScrollToTop) {
messageTableView.tableHeaderView = self.headerContainerView;
}
[self.view addSubview:messageTableView];
[self.view sendSubviewToBack:messageTableView];
_messageTableView = messageTableView;
// 设置Message TableView 的bottom edg
// CGFloat inputViewHeight = (self.inputViewStyle == XHMessageInputViewStyleFlat) ? 45.0f : 40.0f;
_defInputFrameHeight = TabBarHeight;
// if([UIScreen mainScreen].bounds.size.height >= 812.0){
// _defInputFrameHeight += 35;
// }
[self setTableViewInsetsWithBottomValue:_defInputFrameHeight];
// 设置整体背景颜色
[self setBackgroundColor:[UIColor whiteColor]];
// 输入工具条的frame
CGRect inputFrame = CGRectMake(0.0f,
self.view.frame.size.height - _defInputFrameHeight,
self.view.frame.size.width,
_defInputFrameHeight);
WEAKSELF
if (self.allowsPanToDismissKeyboard) {
// 控制输入工具条的位置块
void (^AnimationForMessageInputViewAtPoint)(CGPoint point) = ^(CGPoint point) {
CGRect inputViewFrame = weakSelf.messageInputView.frame;
CGPoint keyboardOrigin = [weakSelf.view convertPoint:point fromView:nil];
inputViewFrame.origin.y = keyboardOrigin.y - inputViewFrame.size.height;
weakSelf.messageInputView.frame = inputViewFrame;
};
self.messageTableView.keyboardDidScrollToPoint = ^(CGPoint point) {
if (weakSelf.textViewInputViewType == XHInputViewTypeText)
AnimationForMessageInputViewAtPoint(point);
};
self.messageTableView.keyboardWillSnapBackToPoint = ^(CGPoint point) {
if (weakSelf.textViewInputViewType == XHInputViewTypeText)
AnimationForMessageInputViewAtPoint(point);
};
self.messageTableView.keyboardWillBeDismissed = ^() {
CGRect inputViewFrame = weakSelf.messageInputView.frame;
inputViewFrame.origin.y = weakSelf.view.bounds.size.height - inputViewFrame.size.height;
weakSelf.messageInputView.frame = inputViewFrame;
};
}
// block回调键盘通知
self.messageTableView.keyboardWillChange = ^(CGRect keyboardRect, UIViewAnimationOptions options, double duration, BOOL showKeyboard) {
if (weakSelf.textViewInputViewType == XHInputViewTypeText) {
[UIView animateWithDuration:duration
delay:0.0
options:options
animations:^{
CGFloat keyboardY = [weakSelf.view convertRect:keyboardRect fromView:nil].origin.y;
CGFloat inputViewHeigght = showKeyboard ? 60 : TabBarHeight;
weakSelf.messageInputView.size = CGSizeMake(weakSelf.messageInputView.frame.size.width, inputViewHeigght);
CGRect inputViewFrame = weakSelf.messageInputView.frame;
CGFloat inputViewFrameY = keyboardY - inputViewFrame.size.height;
// for ipad modal form presentations
CGFloat messageViewFrameBottom = weakSelf.view.frame.size.height - inputViewFrame.size.height;
if (inputViewFrameY > messageViewFrameBottom)
inputViewFrameY = messageViewFrameBottom;
weakSelf.messageInputView.frame = CGRectMake(inputViewFrame.origin.x,
inputViewFrameY,
inputViewFrame.size.width,
inputViewFrame.size.height);
[weakSelf setTableViewInsetsWithBottomValue:weakSelf.view.frame.size.height
- weakSelf.messageInputView.frame.origin.y];
if (showKeyboard)
[weakSelf scrollToBottomAnimated:NO];
}
completion:nil];
}
};
self.messageTableView.keyboardDidChange = ^(BOOL didShowed) {
if ([weakSelf.messageInputView.inputTextView isFirstResponder]) {
if (didShowed) {
if (weakSelf.textViewInputViewType == XHInputViewTypeText) {
weakSelf.shareMenuView.alpha = 0.0;
weakSelf.emotionManagerView.alpha = 0.0;
}
}
}
};
self.messageTableView.keyboardDidHide = ^() {
[weakSelf.messageInputView.inputTextView resignFirstResponder];
};
// 初始化输入工具条
XHMessageInputView *inputView = [[XHMessageInputView alloc] initWithFrame:inputFrame];
inputView.allowsSendText = self.allowsSendText;
inputView.allowsSendFace = self.allowsSendFace;
inputView.allowsSendVoice = self.allowsSendVoice;
inputView.allowsSendMultiMedia = self.allowsSendMultiMedia;
inputView.delegate = self;
[self.view addSubview:inputView];
[self.view bringSubviewToFront:inputView];
_messageInputView = inputView;
// 默认发送语音 add by lsz 20201206
[_messageInputView messageStyleButtonClicked:_messageInputView.voiceChangeButton];
// _messageInputView.voiceChangeButton.enabled = NO;
// _messageInputView.voiceChangeButton.hidden = YES;
/// 隐藏+号按钮
// _messageInputView.multiMediaSendButton.enabled = self.chatType == 111 : YES : NO;
// _messageInputView.multiMediaSendButton.hidden = self.chatType == 111 ? NO : YES;
_messageInputView.allowsSendMultiMedia = self.chatType == 111 ? NO : YES;
// 设置手势滑动,默认添加一个bar的高度值
self.messageTableView.messageInputBarHeight = CGRectGetHeight(_messageInputView.bounds);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// 设置键盘通知或者手势控制键盘消失
[self.messageTableView setupPanGestureControlKeyboardHide:self.allowsPanToDismissKeyboard];
// KVO 检查contentSize
[self.messageInputView.inputTextView addObserver:self
forKeyPath:@"contentSize"
options:NSKeyValueObservingOptionNew
context:nil];
[self.messageInputView.inputTextView setEditable:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.textViewInputViewType != XHInputViewTypeNormal) {
[self layoutOtherMenuViewHiden:YES];
}
// remove键盘通知或者手势
[self.messageTableView disSetupPanGestureControlKeyboardHide:self.allowsPanToDismissKeyboard];
// remove KVO
[self.messageInputView.inputTextView removeObserver:self forKeyPath:@"contentSize"];
[self.messageInputView.inputTextView setEditable:NO];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// 初始化消息页面布局
self.allowsSendMultiMedia = self.chatType == 111 ? YES : NO;
[self initilzer];
[[XHMessageBubbleView appearance] setFont:[UIFont systemFontOfSize:16.0f]];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)dealloc {
_messages = nil;
_delegate = nil;
_messageTableView.delegate = nil;
_messageTableView.dataSource = nil;
_messageTableView = nil;
_messageInputView = nil;
_photographyHelper = nil;
_locationHelper = nil;
}
#pragma mark - View Rotation
- (BOOL)shouldAutorotate {
return NO;
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0
- (NSUInteger)supportedInterfaceOrientations
#else
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
#endif
{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
#pragma mark - RecorderPath Helper Method
- (NSString *)getRecorderPath {
NSString *recorderPath = nil;
recorderPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
NSDate *now = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyyMMddHHmmssSSS"];
recorderPath = [recorderPath stringByAppendingPathComponent:[dateFormatter stringFromDate:now]];
recorderPath = [recorderPath stringByAppendingPathExtension:@"wav"];
return recorderPath;
}
#pragma mark - UITextView Helper Method
- (CGFloat)getTextViewContentH:(UITextView *)textView {
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
return ceilf([textView sizeThatFits:textView.frame.size].height);
} else {
return textView.contentSize.height;
}
}
#pragma mark - Layout Message Input View Helper Method
- (void)layoutAndAnimateMessageInputTextView:(UITextView *)textView {
CGFloat maxHeight = [XHMessageInputView maxHeight];
CGFloat contentH = [self getTextViewContentH:textView];
BOOL isShrinking = contentH < self.previousTextViewContentHeight;
CGFloat changeInHeight = contentH - _previousTextViewContentHeight;
if (!isShrinking && (self.previousTextViewContentHeight == maxHeight || textView.text.length == 0)) {
changeInHeight = 0;
}
else {
changeInHeight = MIN(changeInHeight, maxHeight - self.previousTextViewContentHeight);
}
if (changeInHeight != 0.0f) {
[UIView animateWithDuration:0.25f
animations:^{
[self setTableViewInsetsWithBottomValue:self.messageTableView.contentInset.bottom + changeInHeight];
[self scrollToBottomAnimated:NO];
if (isShrinking) {
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0) {
self.previousTextViewContentHeight = MIN(contentH, maxHeight);
}
// if shrinking the view, animate text view frame BEFORE input view frame
[self.messageInputView adjustTextViewHeightBy:changeInHeight];
}
CGRect inputViewFrame = self.messageInputView.frame;
self.messageInputView.frame = CGRectMake(0.0f,
inputViewFrame.origin.y - changeInHeight,
inputViewFrame.size.width,
inputViewFrame.size.height + changeInHeight);
if (!isShrinking) {
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0) {
self.previousTextViewContentHeight = MIN(contentH, maxHeight);
}
// growing the view, animate the text view frame AFTER input view frame
[self.messageInputView adjustTextViewHeightBy:changeInHeight];
}
}
completion:^(BOOL finished) {
}];
self.previousTextViewContentHeight = MIN(contentH, maxHeight);
}
// Once we reached the max height, we have to consider the bottom offset for the text view.
// To make visible the last line, again we have to set the content offset.
if (self.previousTextViewContentHeight == maxHeight) {
double delayInSeconds = 0.01;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime,
dispatch_get_main_queue(),
^(void) {
CGPoint bottomOffset = CGPointMake(0.0f, contentH - textView.bounds.size.height);
[textView setContentOffset:bottomOffset animated:YES];
});
}
}
#pragma mark - Scroll Message TableView Helper Method
- (void)setTableViewInsetsWithBottomValue:(CGFloat)bottom {
UIEdgeInsets insets = [self tableViewInsetsWithBottomValue:bottom];
self.messageTableView.contentInset = insets;
self.messageTableView.scrollIndicatorInsets = insets;
}
- (UIEdgeInsets)tableViewInsetsWithBottomValue:(CGFloat)bottom {
UIEdgeInsets insets = UIEdgeInsetsZero;
if ([self respondsToSelector:@selector(topLayoutGuide)]) {
// insets.top = self.topLayoutGuide.length;
insets.top = 0;
}
insets.bottom = bottom;
return insets;
}
#pragma mark - Message Calculate Cell Height
- (CGFloat)calculateCellHeightWithMessage:(XHMessage *)message atIndexPath:(NSIndexPath *)indexPath {
CGFloat cellHeight = 0;
BOOL displayTimestamp = [message shouldShowTimestamp];
if ([self.delegate respondsToSelector:@selector(shouldDisplayTimestampForRowAtIndexPath:)]) {
displayTimestamp = [self.delegate shouldDisplayTimestampForRowAtIndexPath:indexPath];
}
cellHeight = [XHMessageTableViewCell calculateCellHeightWithMessage:message displaysTimestamp:displayTimestamp];
return cellHeight;
}
#pragma mark - Message Send helper Method
- (void)didSendMessageWithText:(NSString *)text {
DLog(@"send text : %@", text);
if ([self.delegate respondsToSelector:@selector(didSendText:fromSender:onDate:)]) {
[self.delegate didSendText:text fromSender:self.messageSender onDate:[NSDate date]];
}
}
- (void)didSendMessageWithPhoto:(UIImage *)photo {
DLog(@"send photo : %@", photo);
if ([self.delegate respondsToSelector:@selector(didSendPhoto:fromSender:onDate:)]) {
[self.delegate didSendPhoto:photo fromSender:self.messageSender onDate:[NSDate date]];
}
}
- (void)didSendMessageWithVideoConverPhoto:(UIImage *)videoConverPhoto videoPath:(NSString *)videoPath {
DLog(@"send videoPath : %@ videoConverPhoto : %@", videoPath, videoConverPhoto);
if ([self.delegate respondsToSelector:@selector(didSendVideoConverPhoto:videoPath:fromSender:onDate:)]) {
[self.delegate didSendVideoConverPhoto:videoConverPhoto videoPath:videoPath fromSender:self.messageSender onDate:[NSDate date]];
}
}
- (void)didSendMessageWithVoice:(NSString *)voicePath voiceDuration:(NSNumber *)voiceDuration {
DLog(@"send voicePath : %@", voicePath);
@try {
if ([self.delegate respondsToSelector:@selector(didSendVoice:voiceDuration:fromSender:onDate:)]) {
[self.delegate didSendVoice:voicePath voiceDuration:voiceDuration fromSender:self.messageSender onDate:[NSDate date]];
}
} @catch (NSException *exception) {
} @finally {
}
}
- (void)didSendEmotionMessageWithEmotionPath:(NSString *)emotionPath {
DLog(@"send emotionPath : %@", emotionPath);
if ([self.delegate respondsToSelector:@selector(didSendEmotion:fromSender:onDate:)]) {
[self.delegate didSendEmotion:emotionPath fromSender:self.messageSender onDate:[NSDate date]];
}
}
- (void)didSendGeolocationsMessageWithGeolocaltions:(NSString *)geolcations location:(CLLocation *)location {
DLog(@"send geolcations : %@", geolcations);
if ([self.delegate respondsToSelector:@selector(didSendGeoLocationsPhoto:geolocations:location:fromSender:onDate:)]) {
[self.delegate didSendGeoLocationsPhoto:[UIImage imageNamed:@"Fav_Cell_Loc"] geolocations:geolcations location:location fromSender:self.messageSender onDate:[NSDate date]];
}
}
#pragma mark - Other Menu View Frame Helper Mehtod
- (void)layoutOtherMenuViewHiden:(BOOL)hide
{
CGFloat inputViewHeigght = !hide ? 60 : TabBarHeight;
self.messageInputView.size = CGSizeMake(self.messageInputView.frame.size.width, inputViewHeigght);
[self.messageInputView.inputTextView resignFirstResponder];
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
__block CGRect inputViewFrame = self.messageInputView.frame;
__block CGRect otherMenuViewFrame;
void (^InputViewAnimation)(BOOL hide) = ^(BOOL hide) {
inputViewFrame.origin.y = (hide ? (CGRectGetHeight(self.view.bounds) - CGRectGetHeight(inputViewFrame)) : (CGRectGetMinY(otherMenuViewFrame) - CGRectGetHeight(inputViewFrame)));
self.messageInputView.frame = inputViewFrame;
};
void (^EmotionManagerViewAnimation)(BOOL hide) = ^(BOOL hide) {
otherMenuViewFrame = self.emotionManagerView.frame;
otherMenuViewFrame.origin.y = (hide ? CGRectGetHeight(self.view.frame) : (CGRectGetHeight(self.view.frame) - CGRectGetHeight(otherMenuViewFrame)));
self.emotionManagerView.alpha = !hide;
self.emotionManagerView.frame = otherMenuViewFrame;
};
void (^ShareMenuViewAnimation)(BOOL hide) = ^(BOOL hide) {
otherMenuViewFrame = self.shareMenuView.frame;
otherMenuViewFrame.origin.y = (hide ? CGRectGetHeight(self.view.frame) : (CGRectGetHeight(self.view.frame) - CGRectGetHeight(otherMenuViewFrame)));
self.shareMenuView.alpha = !hide;
self.shareMenuView.frame = otherMenuViewFrame;
};
if (hide) {
switch (self.textViewInputViewType) {
case XHInputViewTypeEmotion: {
EmotionManagerViewAnimation(hide);
break;
}
case XHInputViewTypeShareMenu: {
ShareMenuViewAnimation(hide);
break;
}
default:
break;
}
} else {
// 这里需要注意block的执行顺序,因为otherMenuViewFrame是公用的对象,所以对于被隐藏的Menu的frame的origin的y会是最大值
switch (self.textViewInputViewType) {
case XHInputViewTypeEmotion: {
// 1、先隐藏和自己无关的View
ShareMenuViewAnimation(!hide);
// 2、再显示和自己相关的View
EmotionManagerViewAnimation(hide);
break;
}
case XHInputViewTypeShareMenu: {
// 1、先隐藏和自己无关的View
EmotionManagerViewAnimation(!hide);
// 2、再显示和自己相关的View
ShareMenuViewAnimation(hide);
break;
}
default:
break;
}
}
InputViewAnimation(hide);
[self setTableViewInsetsWithBottomValue:self.view.frame.size.height
- self.messageInputView.frame.origin.y];
[self scrollToBottomAnimated:NO];
} completion:^(BOOL finished) {
if (hide) {
self.textViewInputViewType = XHInputViewTypeNormal;
}
}];
}
#pragma mark - Voice Recording Helper Method
- (void)prepareRecordWithCompletion:(XHPrepareRecorderCompletion)completion {
[self.voiceRecordHelper prepareRecordingWithPath:[self getRecorderPath] prepareRecorderCompletion:completion];
}
- (void)startRecord {
[self.voiceRecordHUD startRecordingHUDAtView:self.view];
[self.voiceRecordHelper startRecordingWithStartRecorderCompletion:^{
}];
}
- (void)finishRecorded {
WEAKSELF
[self.voiceRecordHUD stopRecordCompled:^(BOOL fnished) {
weakSelf.voiceRecordHUD = nil;
}];
[self.voiceRecordHelper stopRecordingWithStopRecorderCompletion:^{
[weakSelf didSendMessageWithVoice:weakSelf.voiceRecordHelper.recordPath
voiceDuration:weakSelf.voiceRecordHelper.recordDuration];
}];
}
- (void)pauseRecord {
[self.voiceRecordHUD pauseRecord];
}
- (void)resumeRecord {
[self.voiceRecordHUD resaueRecord];
}
- (void)cancelRecord {
WEAKSELF
[self.voiceRecordHUD cancelRecordCompled:^(BOOL fnished) {
weakSelf.voiceRecordHUD = nil;
}];
[self.voiceRecordHelper cancelledDeleteWithCompletion:^{
}];
}
#pragma mark - XHMessageInputView Delegate
- (void)inputTextViewWillBeginEditing:(XHMessageTextView *)messageInputTextView {
self.textViewInputViewType = XHInputViewTypeText;
}
- (void)inputTextViewDidBeginEditing:(XHMessageTextView *)messageInputTextView {
if (!self.previousTextViewContentHeight)
self.previousTextViewContentHeight = [self getTextViewContentH:messageInputTextView];
}
- (void)didChangeSendVoiceAction:(BOOL)changed {
if (changed) {
if (self.textViewInputViewType == XHInputViewTypeText)
return;
// 在这之前,textViewInputViewType已经不是XHTextViewTextInputType
[self layoutOtherMenuViewHiden:YES];
}
}
- (void)didSendTextAction:(NSString *)text {
DLog(@"text : %@", text);
if ([self.delegate respondsToSelector:@selector(didSendText:fromSender:onDate:)]) {
[self.delegate didSendText:text fromSender:self.messageSender onDate:[NSDate date]];
}
}
- (void)didSelectedMultipleMediaAction {
DLog(@"didSelectedMultipleMediaAction");
// self.textViewInputViewType = XHInputViewTypeShareMenu;
self.textViewInputViewType = XHInputViewTypeNormal;
[self layoutOtherMenuViewHiden:YES];
if ([self.delegate respondsToSelector:@selector(didSendVideoConverPhoto:videoPath:fromSender:onDate:)]) {
[self.delegate didSendVideoConverPhoto:nil videoPath:nil fromSender:nil onDate:nil];
}
}
/// 表情/文字切换
- (void)didSendFaceAction:(BOOL)sendFace {
if (sendFace) {
self.textViewInputViewType = XHInputViewTypeEmotion;
[self layoutOtherMenuViewHiden:NO];
} else {
[self.messageInputView.inputTextView becomeFirstResponder];
}
}
- (void)prepareRecordingVoiceActionWithCompletion:(BOOL (^)(void))completion {
DLog(@"prepareRecordingWithCompletion");
[self prepareRecordWithCompletion:completion];
}
- (void)didStartRecordingVoiceAction {
DLog(@"didStartRecordingVoice");
[self startRecord];
}
- (void)didCancelRecordingVoiceAction {
DLog(@"didCancelRecordingVoice");
[self cancelRecord];
}
- (void)didFinishRecoingVoiceAction {
DLog(@"didFinishRecoingVoice");
if (self.isMaxTimeStop == NO) {
[self finishRecorded];
} else {
self.isMaxTimeStop = NO;
}
}
- (void)didDragOutsideAction {
DLog(@"didDragOutsideAction");
[self resumeRecord];
}
- (void)didDragInsideAction {
DLog(@"didDragInsideAction");
[self pauseRecord];
}
#pragma mark - XHShareMenuView Delegate
- (void)didSelecteShareMenuItem:(XHShareMenuItem *)shareMenuItem atIndex:(NSInteger)index {
DLog(@"title : %@ index:%ld", shareMenuItem.title, (long)index);
WEAKSELF
void (^PickerMediaBlock)(UIImage *image, NSDictionary *editingInfo) = ^(UIImage *image, NSDictionary *editingInfo) {
if (image) {
[weakSelf didSendMessageWithPhoto:image];
} else {
if (!editingInfo)
return ;
NSString *mediaType = [editingInfo objectForKey: UIImagePickerControllerMediaType];
NSString *videoPath;
NSURL *videoUrl;
if (CFStringCompare ((__bridge CFStringRef) mediaType, kUTTypeMovie, 0) == kCFCompareEqualTo) {
videoUrl = (NSURL*)[editingInfo objectForKey:UIImagePickerControllerMediaURL];
videoPath = [videoUrl path];
UIImage *thumbnailImage = [XHMessageVideoConverPhotoFactory videoConverPhotoWithVideoPath:videoPath];
[weakSelf didSendMessageWithVideoConverPhoto:thumbnailImage videoPath:videoPath];
} else {
[weakSelf didSendMessageWithPhoto:[editingInfo valueForKey:UIImagePickerControllerOriginalImage]];
}
}
};
switch (index) {
case 0: {
// [self.photographyHelper showOnPickerViewControllerSourceType:UIImagePickerControllerSourceTypePhotoLibrary onViewController:self compled:PickerMediaBlock];
[weakSelf didSendMessageWithVideoConverPhoto:nil videoPath:nil];
break;
}
case 1: {
[self.photographyHelper showOnPickerViewControllerSourceType:UIImagePickerControllerSourceTypeCamera onViewController:self compled:PickerMediaBlock];
break;
}
case 2: {
WEAKSELF
[self.locationHelper getCurrentGeolocationsCompled:^(NSArray *placemarks) {
CLPlacemark *placemark = [placemarks lastObject];
if (placemark) {
NSDictionary *addressDictionary = placemark.addressDictionary;
NSArray *formattedAddressLines = [addressDictionary valueForKey:@"FormattedAddressLines"];
NSString *geoLocations = [formattedAddressLines lastObject];
if (geoLocations) {
[weakSelf didSendGeolocationsMessageWithGeolocaltions:geoLocations location:placemark.location];
}
}
}];
break;
}
case 4:{
break;
}
default:
break;
}
}
#pragma mark - XHEmotionManagerView Delegate
- (void)didSelecteEmotion:(XHEmotion *)emotion atIndexPath:(NSIndexPath *)indexPath {
[self didSendEmotionMessageWithEmotionPath:emotion.emotionPath];
}
#pragma mark - XHEmotionManagerView DataSource
- (NSInteger)numberOfEmotionManagers {
return 0;
}
- (XHEmotionManager *)emotionManagerForColumn:(NSInteger)column {
return nil;
}
- (NSArray *)emotionManagersAtManager {
return nil;
}
#pragma mark - UIScrollView Delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if ([self.delegate respondsToSelector:@selector(shouldLoadMoreMessagesScrollToTop)]) {
BOOL shouldLoadMoreMessages = [self.delegate shouldLoadMoreMessagesScrollToTop];
if (shouldLoadMoreMessages) {
if (scrollView.contentOffset.y >= 0 && scrollView.contentOffset.y <= 44) {
if (!self.loadingMoreMessage) {
if ([self.delegate respondsToSelector:@selector(loadMoreMessagesScrollTotop)]) {
[self.delegate loadMoreMessagesScrollTotop];
}
}
}
}
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
self.isUserScrolling = YES;
UIMenuController *menu = [UIMenuController sharedMenuController];
if (menu.isMenuVisible) {
[menu setMenuVisible:NO animated:YES];
}
if (self.textViewInputViewType != XHInputViewTypeNormal) {
[self layoutOtherMenuViewHiden:YES];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
self.isUserScrolling = NO;
}
#pragma mark - XHMessageTableViewController Delegate
- (BOOL)shouldPreventScrollToBottomWhileUserScrolling {
return YES;
}
#pragma mark - XHMessageTableViewController DataSource
- (XHMessage *)messageForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self getMessagesToShow][indexPath.row];
}
#pragma mark - Table View Data Source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self getMessagesToShow].count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
XHMessage *message = [self.msgDataSource messageForRowAtIndexPath:indexPath];
// 如果需要定制复杂的业务UI,那么就实现该DataSource方法
if ([self.myDataSource respondsToSelector:@selector(tableView:cellForRowAtIndexPath:targetMessage:)]) {
UITableViewCell *tableViewCell = [self.msgDataSource tableView:tableView cellForRowAtIndexPath:indexPath targetMessage:message];
return tableViewCell;
}
BOOL displayTimestamp = [message shouldShowTimestamp];
if ([self.delegate respondsToSelector:@selector(shouldDisplayTimestampForRowAtIndexPath:)]) {
displayTimestamp = [self.delegate shouldDisplayTimestampForRowAtIndexPath:indexPath];
}
if (displayTimestamp) {
if (!_lastShowTimetamp || [_lastShowTimetamp doubleValue] < [message.timestamp doubleValue]) {
_lastShowTimetamp = message.timestamp;
}
}
static NSString *cellIdentifier = @"XHMessageTableViewCell";
XHMessageTableViewCell *messageTableViewCell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!messageTableViewCell) {
messageTableViewCell = [[XHMessageTableViewCell alloc] initWithMessage:message displaysTimestamp:displayTimestamp reuseIdentifier:cellIdentifier];
messageTableViewCell.delegate = self;
}
messageTableViewCell.indexPath = indexPath;
[messageTableViewCell configureCellWithMessage:message displaysTimestamp:displayTimestamp];
//[messageTableViewCell setBackgroundColor:tableView.backgroundColor];
// UIImageView *line = [UIImageView new];
// line.backgroundColor = UIColor.blueColor;
// [messageTableViewCell addSubview:line];
// [line mas_makeConstraints:^(MASConstraintMaker *make) {
// make.left.right.equalTo(messageTableViewCell);
// make.bottom.equalTo(messageTableViewCell.mas_bottom).inset(1);
// make.height.mas_equalTo(1);
// }];
if ([self.delegate respondsToSelector:@selector(configureCell:atIndexPath:)]) {
[self.delegate configureCell:messageTableViewCell atIndexPath:indexPath];
}
return messageTableViewCell;
}
#pragma mark - Table View Delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
XHMessage *message = [self.msgDataSource messageForRowAtIndexPath:indexPath];
CGFloat calculateCellHeight = 0;
if ([self.delegate respondsToSelector:@selector(tableView:heightForRowAtIndexPath:targetMessage:)])
{
calculateCellHeight = [self.delegate tableView:tableView heightForRowAtIndexPath:indexPath targetMessage:message];
return calculateCellHeight;
}
else
{
calculateCellHeight = [self calculateCellHeightWithMessage:message atIndexPath:indexPath];
}
return calculateCellHeight;
}
#pragma mark - Key-value Observing
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (object == self.messageInputView.inputTextView && [keyPath isEqualToString:@"contentSize"]) {
[self layoutAndAnimateMessageInputTextView:object];
}
}
@end