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
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
|
|
|