其实 如果一个项目从一开始就集成环信以及UI还是相当简单的、难就难在项目已经成型后 添加通讯功能。因为EaseUI中有几个常用的第三方库 MJRefresh SDWebImage 等。会跟自己项目中的冲突。
- 第一步:下载环信demo
http://downloads.easemob.com/downloads/ios_IM_sdk_V3.1.4.zip - 第二步 :拖文件
集成UI和基本的通讯功能 这几个文件就够了,集成红包功能就加上RedacketSDK
还有重要的一步:把demo中的pch文件 拷贝到自己的pch文件中,并且在自己所有的pch文件的头和尾添加
#ifdef __OBJC__
//
//
#endif
\n
第一个坑来了
C++库编译不支持
//编译报错:
clang: error: invalid library name in argument '-stdlib=libstdc++ libc++'
clang: error: invalid library name in argument '-stdlib=libstdc++ libc++'
解决方法:按照demo中修改Build setting 中的
至此第一个坑填好
第二个坑:修改玩上面的坑可能会报错
解决方法:
把demo中的pch文件 拷贝到自己的pch文件中,并且在自己所有的pch文件的头和尾添加
#ifdef __OBJC__
//
//
#endif
开始填第三个大坑:第二个坑填好 后开始最蛋疼的一步 ,去除跟自己项目中重复的第三方库
我的项目中报错:当然这是我的项目 不同项目可能会不一样,根据具体情况修改
很明显是MJRefresh报错,原因是因为这个项目中竟然集成的是老版本的MJRefresh',环信的EaseUI中同样集成了MJRefresh,但是是新版的方法不太一样
解决方法:删掉EaseUI中的MjRefresh,修改报错代码。
直接Move to trush
修改后代码
改完MjRefresh MBProgressHUD 又报错,也是一样冲突了 删了EaseUI中的MBProgressHUD 再编译
然后 整个人都不好了
336个重复的文件。。。。一点点找
ld: 336 duplicate symbols for architecture x86_64
统计一下 ,
删掉了 MjRefresh ,MBProgressHUD(MBProgressHUD+Add保留了),DACircularProgress,MWPhotoBrowser ,PSTCollectionView
具体删除哪些东西还是根据自己的项目来定,重复就删掉 不重复就留下。
至此 我的坑填完了 你的呢
至此应该已经把环信的通讯库 以及UI集成到了我们的项目中下一步就是按照问道集成聊天界面了
先把环信初始化出来
照搬官方文档
初始化 SDK
第 1 步:引入相关头文件 #import “EMSDK.h”。
第 2 步:在工程的 AppDelegate 中的以下方法中,调用 SDK 对应方法。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//AppKey:注册的AppKey,详细见下面注释。
//apnsCertName:推送证书名(不需要加后缀),详细见下面注释。
EMOptions *options = [EMOptions optionsWithAppkey:@"douser#istore"];
options.apnsCertName = @"istore_dev";
[[EMClient sharedClient] initializeSDKWithOptions:options];
return YES;
}
// APP进入后台
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[[EMClient sharedClient] applicationDidEnterBackground:application];
}
// APP将要从后台返回
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[EMClient sharedClient] applicationWillEnterForeground:application];
}
调用的 SDK 接口参数解释如下:
AppKey: 区别 APP 的标识,参考开发者注册及管理后台。
apnsCertName: iOS 中推送证书名称,参考制作与上传推送证书。
初始化EaseUI 按照官网文档来
初始化
第 1 步:引入相关头文件 #import “EaseUI.h”。
第 2 步:在工程的 AppDelegate 中的以下方法中,调用 EaseUI 对应方法。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//官网文档写的这个方法 已经移除了
/* [[EaseSDKHelper shareHelper] easemobApplication:application
didFinishLaunchingWithOptions:launchOptions
appkey:@"douser#istore"
apnsCertName:@"istore_dev"
otherConfig:@{kSDKConfigEnableConsoleLogger:[NSNumber numberWithBool:YES]}];*/
//用这个方法
[[EaseSDKHelper shareHelper] hyphenateApplication
didFinishLaunchingWithOptions:launchOptions
appkey:@"douser#istore"
apnsCertName:@"istore_dev"
otherConfig:@{kSDKConfigEnableConsoleLogger:[NSNumber numberWithBool:YES]}];
return YES;
}
上面设置好以后就可以调用聊天界面的控制器了
在需要推出聊天界面的地方写上这个就行了
如果需要设置导航栏 就在EaseMessageViewController.m 的viewdidload方法中添加 设置导航栏的相关代码或方法就行
EaseMessageViewController *chatController = [[EaseMessageViewController alloc] initWithConversationChatter:@"8001" conversationType:EMConversationTypeChat];
chatController.title = @"8001";
chatController.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:chatController animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
这边有个坑,就是在聊天界面点击表情报野指针错误
上图中的_emotionManagers是nil的
_emotionManagers实际是EaseMessageViewController中 EaseFacialView属性的一个表情管理器 对应的是
我找遍这个类没有发现这个emotionManagers的初始化方法
由此可知,这个数组肯定是从外界传入的,如果不传入肯定是空的,所以当表情弹出的时候也就报野指针错误了。
解决方案有两种:
1.在 EaseMessageViewController 的viewdidload 方法中设置 self.EaseFacialView 的emotionManagers
//EaseMessageViewController.m
EaseEmotionManager * manager = [[EaseEmotionManager alloc] initWithType:EMEmotionDefault emotionRow:3 emotionCol:7 emotions:[EaseEmoji allEmoji]];
[self.faceView setEmotionManagers:@[manager]];
2、在EaseFacialView类中添加emotionManagers 的懒加载,并将所有的_emotionManagers替换成self.emotionManagers 保证_emotionManagers随时都有值
#pragma mark -- 懒加载emotionManagers
- (NSArray *)emotionManagers{
if (_emotionManagers == nil) {
EaseEmotionManager * manager = [[EaseEmotionManager alloc] initWithType:EMEmotionDefault emotionRow:3 emotionCol:7 emotions:[EaseEmoji allEmoji]];
_emotionManagers = @[manager];
}
return _emotionManagers;
}
Mark EaseConversationListViewControllerDataSource 集成环信EaseUI会话列表,不走数据源和代理方法
集成环信EaseUI会话列表,不走数据源和代理方法
解决方法,继承EaseConversationListViewControllerDataSource重写viewdidload
- (void)viewDidLoad {
[super viewDidLoad];
self.showRefreshHeader = YES;
self.dataSource = self;
[self tableViewDidTriggerHeaderRefresh];
[self networkStateView];
[self removeEmptyConversationsFromDB];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self refresh];
}
- (void)removeEmptyConversationsFromDB
{
NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations];
NSMutableArray *needRemoveConversations;
for (EMConversation *conversation in conversations) {
if (!conversation.latestMessage || (conversation.type == EMConversationTypeChatRoom)) {
if (!needRemoveConversations) {
needRemoveConversations = [[NSMutableArray alloc] initWithCapacity:0];
}
[needRemoveConversations addObject:conversation];
}
}
if (needRemoveConversations && needRemoveConversations.count > 0) {
[[EMClient sharedClient].chatManager deleteConversations:needRemoveConversations deleteMessages:YES];
}
}
- (UIView *)networkStateView
{
if (_networkStateView == nil) {
_networkStateView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 44)];
_networkStateView.backgroundColor = [UIColor colorWithRed:255 / 255.0 green:199 / 255.0 blue:199 / 255.0 alpha:0.5];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, (_networkStateView.frame.size.height - 20) / 2, 20, 20)];
imageView.image = [UIImage imageNamed:@"messageSendFail"];
[_networkStateView addSubview:imageView];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(imageView.frame) + 5, 0, _networkStateView.frame.size.width - (CGRectGetMaxX(imageView.frame) + 15), _networkStateView.frame.size.height)];
label.font = [UIFont systemFontOfSize:15.0];
label.textColor = [UIColor grayColor];
label.backgroundColor = [UIColor clearColor];
label.text = NSLocalizedString(@"network.disconnection", @"Network disconnection");
[_networkStateView addSubview:label];
}
return _networkStateView;
}
#pragma mark - EaseConversationListViewControllerDataSource
- (id)conversationListViewController:(EaseConversationListViewController *)conversationListViewController
modelForConversation:(EMConversation *)conversation
{
EaseConversationModel *model = [[EaseConversationModel alloc] initWithConversation:conversation];
return model;
}
- (NSString *)conversationListViewController:(EaseConversationListViewController *)conversationListViewController
latestMessageTimeForConversationModel:(id)conversationModel
{
NSString *latestMessageTime = @"";
EMMessage *lastMessage = [conversationModel.conversation latestMessage];;
if (lastMessage) {
latestMessageTime = [NSDate formattedTimeFromTimeInterval:lastMessage.timestamp];
}
return latestMessageTime;
}
#pragma mark - UISearchBarDelegate
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
{
[searchBar setShowsCancelButton:YES animated:YES];
return YES;
}
#pragma mark - public
-(void)refresh
{
[self refreshAndSortView];
}
-(void)refreshDataSource
{
[self tableViewDidTriggerHeaderRefresh];
}
- (void)isConnect:(BOOL)isConnect{
if (!isConnect) {
self.tableView.tableHeaderView = _networkStateView;
}
else{
self.tableView.tableHeaderView = nil;
}
}
- (void)networkChanged:(EMConnectionState)connectionState
{
if (connectionState == EMConnectionDisconnected) {
self.tableView.tableHeaderView = _networkStateView;
}
else{
self.tableView.tableHeaderView = nil;
}
}
Mark 关于EaseMessageViewController 单聊界面复用消息显示不全
关于EaseMessageViewController 单聊界面复用有问题
单聊界面的Cell 一旦复用就不会再去计算宽度高度导致单元格显示内存不全 以“...”省略掉了要显示的内容:如图所示
我尝试去看了EaseMessageCell中的代码。找到关于setModel的方法 。但一时没有找到解决方案。
应急措施是去掉了单元格的重用
EaseMessageViewController.m中
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
id object = [self.dataArray objectAtIndex:indexPath.row];
//time cell
if ([object isKindOfClass:[NSString class]]) {
NSString *TimeCellIdentifier = [EaseMessageTimeCell cellIdentifier];
EaseMessageTimeCell *timeCell = (EaseMessageTimeCell *)[tableView dequeueReusableCellWithIdentifier:TimeCellIdentifier];
if (timeCell == nil) {
timeCell = [[EaseMessageTimeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TimeCellIdentifier];
timeCell.selectionStyle = UITableViewCellSelectionStyleNone;
}
timeCell.title = object;
return timeCell;
}
else{
id model = object;
if (_delegate && [_delegate respondsToSelector:@selector(messageViewController:cellForMessageModel:)]) {
UITableViewCell *cell = [_delegate messageViewController:tableView cellForMessageModel:model];
if (cell) {
if ([cell isKindOfClass:[EaseMessageCell class]]) {
EaseMessageCell *emcell= (EaseMessageCell*)cell;
if (emcell.delegate == nil) {
emcell.delegate = self;
}
}
return cell;
}
}
if (_dataSource && [_dataSource respondsToSelector:@selector(isEmotionMessageFormessageViewController:messageModel:)]) {
BOOL flag = [_dataSource isEmotionMessageFormessageViewController:self messageModel:model];
if (flag) {
NSString *CellIdentifier = [EaseCustomMessageCell cellIdentifierWithModel:model];
//send cell
#warning -------- 单元格复用有问题,去掉了重用
EaseCustomMessageCell *sendCell = nil;
// (EaseCustomMessageCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (sendCell == nil) {
sendCell = [[EaseCustomMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier model:model];
sendCell.selectionStyle = UITableViewCellSelectionStyleNone;
}
if (_dataSource && [_dataSource respondsToSelector:@selector(emotionURLFormessageViewController:messageModel:)]) {
EaseEmotion *emotion = [_dataSource emotionURLFormessageViewController:self messageModel:model];
if (emotion) {
model.image = [UIImage sd_animatedGIFNamed:emotion.emotionOriginal];
model.fileURLPath = emotion.emotionOriginalURL;
}
}
sendCell.model = model;
sendCell.delegate = self;
return sendCell;
}
}
NSString *CellIdentifier = [EaseMessageCell cellIdentifierWithModel:model];
#warning -------- 单元格复用有问题,去掉了重用
EaseBaseMessageCell *sendCell = nil;
// (EaseBaseMessageCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (sendCell == nil) {
sendCell = [[EaseBaseMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier model:model];
sendCell.selectionStyle = UITableViewCellSelectionStyleNone;
sendCell.delegate = self;
}
sendCell.model = model;
return sendCell;
}
}
Mark 更换自定义头像
环信并没有在他的服务器为我们存储用户信息,自定义头像要依赖于我们自己的用户体系。修改方法是在每个消息中添加一个Ext消息拓展,将用户的昵称以及头像放进去然后每次在显示聊天cell的时候给model赋值acatarPathUrl 以及NickName 没有拓展信息显示默认头像还环信用户名:需要修改的代码如下,
EaseMessageViewController.m中
pragma mark - send message中的代码凡是带有withExt:(NSDictionary*)ext的方法 都对代码做如下修改.
NSMutableDictionary * newExt = [NSMutableDictionary dictionaryWithDictionary:ext];
newExt[@"avatar"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emAvatar"];
newExt[@"nickname"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emNickname"];
改完之后:
#pragma mark - send message
- (void)_refreshAfterSentMessage:(EMMessage*)aMessage
{
if ([self.messsagesSource count] && [EMClient sharedClient].options.sortMessageByServerTime) {
NSString *msgId = aMessage.messageId;
EMMessage *last = self.messsagesSource.lastObject;
if ([last isKindOfClass:[EMMessage class]]) {
__block NSUInteger index = NSNotFound;
index = NSNotFound;
[self.messsagesSource enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(EMMessage *obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[EMMessage class]] && [obj.messageId isEqualToString:msgId]) {
index = idx;
*stop = YES;
}
}];
if (index != NSNotFound) {
[self.messsagesSource removeObjectAtIndex:index];
[self.messsagesSource addObject:aMessage];
//格式化消息
self.messageTimeIntervalTag = -1;
NSArray *formattedMessages = [self formatMessages:self.messsagesSource];
[self.dataArray removeAllObjects];
[self.dataArray addObjectsFromArray:formattedMessages];
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.dataArray count] - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
return;
}
}
}
[self.tableView reloadData];
}
- (void)_sendMessage:(EMMessage *)message
{
if (self.conversation.type == EMConversationTypeGroupChat){
message.chatType = EMChatTypeGroupChat;
}
else if (self.conversation.type == EMConversationTypeChatRoom){
message.chatType = EMChatTypeChatRoom;
}
[self addMessageToDataSource:message
progress:nil];
__weak typeof(self) weakself = self;
[[EMClient sharedClient].chatManager asyncSendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {
if (!aError) {
[weakself _refreshAfterSentMessage:aMessage];
}
else {
[weakself.tableView reloadData];
}
}];
}
- (void)sendTextMessage:(NSString *)text
{
NSDictionary *ext = nil;
if (self.conversation.type == EMConversationTypeGroupChat) {
NSArray *targets = [self _searchAtTargets:text];
if ([targets count]) {
__block BOOL atAll = NO;
[targets enumerateObjectsUsingBlock:^(NSString *target, NSUInteger idx, BOOL *stop) {
if ([target compare:kGroupMessageAtAll options:NSCaseInsensitiveSearch] == NSOrderedSame) {
atAll = YES;
*stop = YES;
}
}];
if (atAll) {
ext = @{kGroupMessageAtList: kGroupMessageAtAll};
}
else {
ext = @{kGroupMessageAtList: targets};
}
}
}
NSMutableDictionary * newExt = [NSMutableDictionary dictionaryWithDictionary:ext];
newExt[@"avatar"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emAvatar"];
newExt[@"nickname"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emNickname"];
[self sendTextMessage:text withExt:newExt];
}
- (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext
{
NSMutableDictionary * newExt = [NSMutableDictionary dictionaryWithDictionary:ext];
newExt[@"avatar"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emAvatar"];
newExt[@"nickname"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emNickname"];
EMMessage *message = [EaseSDKHelper sendTextMessage:text
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:newExt];
[self _sendMessage:message];
}
- (void)sendLocationMessageLatitude:(double)latitude
longitude:(double)longitude
andAddress:(NSString *)address
{
NSMutableDictionary * newExt = [NSMutableDictionary dictionary];
newExt[@"avatar"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emAvatar"];
newExt[@"nickname"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emNickname"];
EMMessage *message = [EaseSDKHelper sendLocationMessageWithLatitude:latitude
longitude:longitude
address:address
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:newExt];
[self _sendMessage:message];
}
- (void)sendImageMessageWithData:(NSData *)imageData
{
NSMutableDictionary * newExt = [NSMutableDictionary dictionary];
newExt[@"avatar"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emAvatar"];
newExt[@"nickname"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emNickname"];
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeImage];
}
else{
progress = self;
}
EMMessage *message = [EaseSDKHelper sendImageMessageWithImageData:imageData
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:newExt];
[self _sendMessage:message];
}
- (void)sendImageMessage:(UIImage *)image
{
NSMutableDictionary * newExt = [NSMutableDictionary dictionary];
newExt[@"avatar"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emAvatar"];
newExt[@"nickname"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emNickname"];
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeImage];
}
else{
progress = self;
}
EMMessage *message = [EaseSDKHelper sendImageMessageWithImage:image
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:newExt];
[self _sendMessage:message];
}
- (void)sendVoiceMessageWithLocalPath:(NSString *)localPath
duration:(NSInteger)duration
{
NSMutableDictionary * newExt = [NSMutableDictionary dictionary];
newExt[@"avatar"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emAvatar"];
newExt[@"nickname"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emNickname"];
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeVoice];
}
else{
progress = self;
}
EMMessage *message = [EaseSDKHelper sendVoiceMessageWithLocalPath:localPath
duration:duration
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:newExt];
[self _sendMessage:message];
}
- (void)sendVideoMessageWithURL:(NSURL *)url
{
NSMutableDictionary * newExt = [NSMutableDictionary dictionary];
newExt[@"avatar"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emAvatar"];
newExt[@"nickname"]=[[NSUserDefaults standardUserDefaults] valueForKey:@"emNickname"];
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeVideo];
}
else{
progress = self;
}
EMMessage *message = [EaseSDKHelper sendVideoMessageWithURL:url
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:newExt];
[self _sendMessage:message];
}
到这一步 所有发送出去的消息都已经拓展上了用户昵称和 头像,然后修改方法
- (NSArray *)formatMessages:(NSArray *)messages
使用自定义的头像和昵称
#pragma mark - public
- (NSArray *)formatMessages:(NSArray *)messages
{
NSMutableArray *formattedArray = [[NSMutableArray alloc] init];
if ([messages count] == 0) {
return formattedArray;
}
for (EMMessage *message in messages) {
//Calculate time interval
CGFloat interval = (self.messageTimeIntervalTag - message.timestamp) / 1000;
if (self.messageTimeIntervalTag < 0 || interval > 60 || interval < -60) {
NSDate *messageDate = [NSDate dateWithTimeIntervalInMilliSecondSince1970:(NSTimeInterval)message.timestamp];
NSString *timeStr = @"";
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:stringForDate:)]) {
timeStr = [_dataSource messageViewController:self stringForDate:messageDate];
}
else{
timeStr = [messageDate formattedTime];
}
[formattedArray addObject:timeStr];
self.messageTimeIntervalTag = message.timestamp;
}
//Construct message model
id model = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:modelForMessage:)]) {
model = [_dataSource messageViewController:self modelForMessage:message];
}
else{
model = [[EaseMessageModel alloc] initWithMessage:message];
model.avatarImage = [UIImage imageNamed:@"EaseUIResource.bundle/user"];
//主要修改这一块代码
if (message.ext!=nil) {
if (message.ext[@"avatar"] != nil) {
model.avatarURLPath = message.ext[@"avatar"];
}
model.nickname =message.ext[@"nickname"];
}
model.failImageName = @"imageDownloadFail";
}
if (model) {
[formattedArray addObject:model];
}
}
return formattedArray;
}