iOS

iOS

0
回复

【环信官方外包一个项目】利用环信IM实现一个在线白板 环信

回复

fat1 发起了问题 • 1 人关注 • 84 次浏览 • 2017-09-18 12:21 • 来自相关话题

0
评论

环信公开课16期回放|环信智能鉴黄:用深度学习实现99%精准度的鉴黄服务 环信 环信公开课 环信智能鉴黄 鉴黄

beyond 发表了文章 • 136 次浏览 • 2017-09-04 17:01 • 来自相关话题

   8月31日,环信公开课以“智能鉴黄”为主题,为大家普及了内容审查工作的前世今生,以及环信智能鉴黄算法、技术实现和行业解决方案。
 
关于环信智能鉴黄:
   随着移动互联网的飞速发展和信息量的猛增,大量的色情、赌博、暴力等不良信息图片夹杂在信息流中,严重影响着整个互联网的体验和健康发展。各大互联网公司为了保持自己所提供的服务不触犯国家法律和法规,使得鉴黄工作变得尤为重要。环信智能鉴黄服务基于深度学习的智能鉴黄算法,识别精准度高达99%,可以帮助企业完成95%的图片内容审查工作,节省90%的时间及人工成本。
环信公开课第16期分享内容:


















































环信公开课第16期视频回放:





参加环信公开课还有大礼相送:
恭喜手机尾号8985的leoNN同学成为本期公开课幸运观众,获得环信定制版瑞士军团双肩背包。

环信智能鉴黄免费体验请联系环信小助手,微信huanxin-hh 查看全部
   8月31日,环信公开课以“智能鉴黄”为主题,为大家普及了内容审查工作的前世今生,以及环信智能鉴黄算法、技术实现和行业解决方案。
 
关于环信智能鉴黄:

   随着移动互联网的飞速发展和信息量的猛增,大量的色情、赌博、暴力等不良信息图片夹杂在信息流中,严重影响着整个互联网的体验和健康发展。各大互联网公司为了保持自己所提供的服务不触犯国家法律和法规,使得鉴黄工作变得尤为重要。环信智能鉴黄服务基于深度学习的智能鉴黄算法,识别精准度高达99%,可以帮助企业完成95%的图片内容审查工作,节省90%的时间及人工成本。


环信公开课第16期分享内容:
001.png


002.png


003.png


004.png


005.png


006.png


007.png


008.png


009.png


010.png


环信公开课第16期视频回放:






参加环信公开课还有大礼相送:
恭喜手机尾号8985的leoNN同学成为本期公开课幸运观众,获得环信定制版瑞士军团双肩背包。

环信智能鉴黄免费体验请联系环信小助手,微信huanxin-hh
3a36009c9eb571b9a3a2797f17c4ae25.gif
0
评论

【开源项目】一个基于环信IM开发的开源的私密社交APP-Baby 即时通信 Baby

seven。 发表了文章 • 333 次浏览 • 2017-08-31 14:49 • 来自相关话题

Baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。先上效果图




开屏页的登录和注册




编辑个人信息




相册页面




首页的Moment
版本更新:
 
version 1.6
加入Tinker 热修复更新部分依赖修复大量细节问题,加入部分注释

version 1.5
bug fixupdate sth

version 1.4
增加长按删除功能优化Rxbus订阅加载数据外国友人优化的一些细节等等

version 1.3
增加了评论功能优化了相册加载修复了一些内存泄漏等等

version 1.2
修复了一些Bug把登陆注册事件换了个Zip操作符更符合流的思想

version 1.1
修复了主页背景无法切换的问题修复了聊天推送的问题修了语音视频的问题做了一些细节修改
Download



安装包下载体验
 
github地址
github源码
 Development Environment & Library
 
MVP

这个项目是基于MVP框架写的(大体上,聊天那块直接用环信的了),大部分Base类参考FastAndroid里边的基类,参考这个很快就能布好基本的MVP架构。本来是有考虑过MVVM后来想想还是先学习一下MVP吧,看过几个MVVM项目感觉还是挺好用的,不过还是BETA版不知道有没有什么坑。

Material Design

早就手痒想体验一把V7包里边的各种控件了,特喜欢coordinatorlayout和collapsingtoolbarlayout的互动让Tollbar隐藏又现的感觉,但是看起来好看还是要点代价的,在这里捣鼓了不少时间,尤其是collapsingtoolbarlayout的Expanded固定让我Google了好久,因为用英文搜索可能我表达的不太好,最后竟然是一句nestedScroll(false)就可以了。。。。 还是感谢Stackoveflow里边的大腿吧。

Dagger2

依赖注入Dagger2,也是我早就想用的一个框架了,理由是各种配合Mvp十分方便和好用,渐渐也能体会到一次注入到处可用的快感。不过一个新技术真的学习成本,国内没什么中文文档介绍,看国外的看的云里雾里。原理看的明白,用起来好像不太知道如何使用,尤其是在@inject之后对象,也可以在别的地方Inject,原本是被Inject方后来也成了Module提供方。虽然到最后原理还不是特别明白这里,但还是不阻碍用起来的快感。

Realm

一开始被新技术吸引到的是不会放过任何新东西的包括Realm,不过进了坑不代表这个坑可以跳阿。由于我这次用到了leacncloud,Realm感觉会和LeanCloud的子类化冲突让你只能选择其中之一,不过这个也算了,Leancloud提供了类似Map的Put方法也可以接受就是麻烦了点。但是被坑到的地方是Realm所谓的自动数据同步竟然是一改就是改真实的数据,并不是数据的拷贝。。。感觉和我使用到要缓存的数据有点冲突,因为这个Moment里边的项是有点赞的,点赞要修改当前Recycleview的数据(修改数据要开事务)。修改数据后会出现一些很奇怪的现象,不在Recycleview当前Item会跳到当前Item,点赞的动画也会消失。。。真的是想破脑袋也解决不了,就直接跳坑了。最后感觉这个Realm在保存不跟服务器需要同步的数据会好点。

LeanCloud

用LeanCloud是因为在知乎太多吹它的人了而且它的确在BAAS这方面功能比较多(后来才发现即时通信没有语音和视频),所以就尝试使用了,SDK整体来说是不错的都挺好用的,满足了我对存储方面的要求。不过就在我开发的这几天,貌似稳定性没有想象中那么好,好几次上传个头像都会SocketTimeOut,查询也会有点慢,不过还好都在接受范围内(不过要是到了收费的标准我就接受不了)。

环信

baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。

Rxjava、RxAndroid

Rxjava我从第一眼看到了就喜欢上了(个人特喜欢那种通过.设置完成的感觉),接触也有几个月,一开始就和Retrofit 、Okhttp一起使用。学习成本还是要有的阿,看了不知道多少篇关于Rxjava使用的文章和例子,对里边的操作符也仅仅停留在那几个最常用的,其他一大堆好多都没用过,看来还用得不够。

Glide

Glide也是一个后来居上让我喜欢的图片加载库,一开始我喜欢picasso 是觉得轻巧而且好用有保证(主要是我偶像Jake Wharton主导,有加成),慢慢觉得Picasso对内存没有Glide来的友好,Glide在加载速度方面也领先,虽然整个库代码量是Picasso的几倍,但是比起重要的内存和用户体验来说还是Glide的领先一筹。

Butterknife、Ucrop等等

当让还有其他的一些润色的轮子啦,不过不是那么重要就不一一感谢啦。

Thanks
感谢Github、LeanCloud、环信、还有造那么多轮子给我们用的Square FaceBook Google的大大们。Thanks for improving my code m-ezzat.

Contacts
Email:379489343zhi@gmail.comQQ:379489343
 Copyright 2016 Roger ou

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. 查看全部
   Baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。
先上效果图

003.png

开屏页的登录和注册


001.png

编辑个人信息


002.png

相册页面


004.png

首页的Moment


版本更新:
 
version 1.6
  1. 加入Tinker 热修复
  2. 更新部分依赖
  3. 修复大量细节问题,加入部分注释


version 1.5
  1. bug fix
  2. update sth


version 1.4
  1. 增加长按删除功能
  2. 优化Rxbus订阅加载数据
  3. 外国友人优化的一些细节
  4. 等等


version 1.3
  1. 增加了评论功能
  2. 优化了相册加载
  3. 修复了一些内存泄漏
  4. 等等


version 1.2
  1. 修复了一些Bug
  2. 把登陆注册事件换了个Zip操作符更符合流的思想


version 1.1
  1. 修复了主页背景无法切换的问题
  2. 修复了聊天推送的问题
  3. 修了语音视频的问题
  4. 做了一些细节修改

Download

 
github地址

 Development Environment & Library
 
MVP

这个项目是基于MVP框架写的(大体上,聊天那块直接用环信的了),大部分Base类参考FastAndroid里边的基类,参考这个很快就能布好基本的MVP架构。本来是有考虑过MVVM后来想想还是先学习一下MVP吧,看过几个MVVM项目感觉还是挺好用的,不过还是BETA版不知道有没有什么坑。

Material Design

早就手痒想体验一把V7包里边的各种控件了,特喜欢coordinatorlayout和collapsingtoolbarlayout的互动让Tollbar隐藏又现的感觉,但是看起来好看还是要点代价的,在这里捣鼓了不少时间,尤其是collapsingtoolbarlayout的Expanded固定让我Google了好久,因为用英文搜索可能我表达的不太好,最后竟然是一句nestedScroll(false)就可以了。。。。 还是感谢Stackoveflow里边的大腿吧。

Dagger2

依赖注入Dagger2,也是我早就想用的一个框架了,理由是各种配合Mvp十分方便和好用,渐渐也能体会到一次注入到处可用的快感。不过一个新技术真的学习成本,国内没什么中文文档介绍,看国外的看的云里雾里。原理看的明白,用起来好像不太知道如何使用,尤其是在@inject之后对象,也可以在别的地方Inject,原本是被Inject方后来也成了Module提供方。虽然到最后原理还不是特别明白这里,但还是不阻碍用起来的快感。

Realm

一开始被新技术吸引到的是不会放过任何新东西的包括Realm,不过进了坑不代表这个坑可以跳阿。由于我这次用到了leacncloud,Realm感觉会和LeanCloud的子类化冲突让你只能选择其中之一,不过这个也算了,Leancloud提供了类似Map的Put方法也可以接受就是麻烦了点。但是被坑到的地方是Realm所谓的自动数据同步竟然是一改就是改真实的数据,并不是数据的拷贝。。。感觉和我使用到要缓存的数据有点冲突,因为这个Moment里边的项是有点赞的,点赞要修改当前Recycleview的数据(修改数据要开事务)。修改数据后会出现一些很奇怪的现象,不在Recycleview当前Item会跳到当前Item,点赞的动画也会消失。。。真的是想破脑袋也解决不了,就直接跳坑了。最后感觉这个Realm在保存不跟服务器需要同步的数据会好点。

LeanCloud

用LeanCloud是因为在知乎太多吹它的人了而且它的确在BAAS这方面功能比较多(后来才发现即时通信没有语音和视频),所以就尝试使用了,SDK整体来说是不错的都挺好用的,满足了我对存储方面的要求。不过就在我开发的这几天,貌似稳定性没有想象中那么好,好几次上传个头像都会SocketTimeOut,查询也会有点慢,不过还好都在接受范围内(不过要是到了收费的标准我就接受不了)。

环信

baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。

Rxjava、RxAndroid

Rxjava我从第一眼看到了就喜欢上了(个人特喜欢那种通过.设置完成的感觉),接触也有几个月,一开始就和Retrofit 、Okhttp一起使用。学习成本还是要有的阿,看了不知道多少篇关于Rxjava使用的文章和例子,对里边的操作符也仅仅停留在那几个最常用的,其他一大堆好多都没用过,看来还用得不够。

Glide

Glide也是一个后来居上让我喜欢的图片加载库,一开始我喜欢picasso 是觉得轻巧而且好用有保证(主要是我偶像Jake Wharton主导,有加成),慢慢觉得Picasso对内存没有Glide来的友好,Glide在加载速度方面也领先,虽然整个库代码量是Picasso的几倍,但是比起重要的内存和用户体验来说还是Glide的领先一筹。

Butterknife、Ucrop等等

当让还有其他的一些润色的轮子啦,不过不是那么重要就不一一感谢啦。

Thanks
  • 感谢Github、LeanCloud、环信、还有造那么多轮子给我们用的Square FaceBook Google的大大们。
  • Thanks for improving my code m-ezzat.


Contacts

 
Copyright 2016 Roger ou

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
6
评论

【新手快速入门】集成环信常见问题+解决方案汇总 常见问题

dujiepeng 发表了文章 • 5451 次浏览 • 2017-05-22 15:51 • 来自相关话题

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题环信推送的一些常见问题实现名片|红包|话题聊天室等自定义cell
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天java.lang.UnsatisfiedLinkError: 的问题android 端 app 后台被杀死收不到消息的解决方案
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
客服集成
IM-SDK和客服SDK并存开发指南—Android篇IM-SDK和客服SDK并存开发指南—iOS篇
 
开源项目
Android简版demoios简版demo凡信2.0:超仿微信的开源项目 凡信3.0:携直播和红包而来高仿微信:Github 3,515 Star方圆十里:环信编程大赛冠军项目泛聊:定一个小目标写一个QQSlack聊天机器人:一天时间做一个聊天机器人TV视频通话:在电视上视频通话视频通话:Android手机视频通话酷信:ios高仿微信公众号助手:与订阅用户聊天沟通
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇

 
Android篇

昵称头像篇

 
直播篇
  1. 一言不合你就搞个直播APP

 
客服集成
  1. IM-SDK和客服SDK并存开发指南—Android篇
  2. IM-SDK和客服SDK并存开发指南—iOS篇

 
开源项目

 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
 
1
回复

环信可以实现推送服务吗? iOS

baoshu 回复了问题 • 2 人关注 • 102 次浏览 • 2017-09-15 09:55 • 来自相关话题

0
评论

【环信征文】集成环信,并实现消息免打扰 iOS IM 消息免打扰 环信 即时通讯

cokeyer 发表了文章 • 27 次浏览 • 2017-09-14 11:27 • 来自相关话题

现在大多数社交app都有消息免打扰功能,因为环信SDK主要是对即时通讯模块进行封装,因此如果要实现消息免打扰功能则需要开发者自己对此功能逻辑进行处理。
先解释一下,本人项目中对消息免打扰功能的定义:正常情况下,我们收到的每一条聊天消息都会收到小红点、声音、震动的提示。如果对某个好友设置消息免打扰功能,则只提示小红点,声音和震动则不再提示。

下面简述结合环信SDK时,此功能的实现方法。
环信将即时聊天的所有功能分为四大模块进行管理:
//聊天模块:
[EMClient sharedClient].chatManager
//好友模块 :
[EMClient sharedClient].contactManager
//群组模块 :
[EMClient sharedClient].groupManager
//聊天室模块:
[EMClient sharedClient].roomManager消息免打扰的功能借助聊天模块[EMClient sharedClient].chatManager的API就能实现。
一般来说,在app内不管当前在哪个界面,只要收到消息都需要被判断是否需要免打扰,因此可以在appdelegate里写如下代码,app通常都有tabBarController,那么也可以让tabBarController来实现如下代码。
首先让控制器tabBarController遵守代理EMChatManagerDelegate
然后成为代理
[[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];

EMChatManagerDelegate中有一个代理方法
- (void)didReceiveMessages:(NSArray *)aMessages;实现此代理方法
- (void)didReceiveMessages:(NSArray *)aMessages{
[self setupUnreadMessageCount];
EMMessage *message = aMessages[0];
NSString *sendPerson = message.from;
BHNavigatiomController *imNaviCV = self.viewControllers[1];
BHConversationListController *converCV = imNaviCV.viewControllers[0];
[converCV refreshDataSource];//只要收到消息就从服务器拿
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
for (NSString *hx_name in AppEngine.IMDataCent.data_ExcuseFriendsData) {
if ([hx_name isEqualToString:sendPerson]) {
return;
}
}
switch (state) {
case UIApplicationStateActive:
[self playSoundAndVibration];
break;
case UIApplicationStateInactive:
[self playSoundAndVibration];
break;
case UIApplicationStateBackground:
[self showNotificationWithMessage:message];
break;
default:
break;
}
}方法中有一个aMessages参数。这个参数是一个消息组,每一个元素都是EMMessage类型的实例。
因为通常都只有一条信息。因此只需要取出EMMessage *message = aMessages[0];
EMMessage类中有许多属性,因此根据一条信息基本可以获取想知道的所有信息。这里我们只需要知道此消息的发送方即可,也就是发送方的环信idNSString *sendPerson = message.from;下面代码中有一AppEngine.IMDataCent.data_ExcuseFriendsData,这个数组里面装的都是已经被设置消息免打扰的好友的环信id,是请求自己服务器获得的,获取的代码最好在一打开app时,就及时获取到。然后根据对好友免打扰设置的操作,访问后台接口进行增删,并刷新数组与后端保持一致即可。
通过[hx_name isEqualToString:sendPerson],遍历免打扰数组与当前消息的发送方环信id,就可以知道是否需要免打扰了。
 
下面附上完整代码
 

// // BHTabBarController.m // xxxxxxx // // Created by LiBohan on 2017/8/24. // Copyright © 2017年 xxxxxx. All rights reserved. // //两次提示的默认间隔 static const CGFloat kDefaultPlaySoundInterval = 3.0; static NSString *kMessageType = @"MessageType"; static NSString *kConversationChatter = @"ConversationChatter"; static NSString *kGroupName = @"GroupName"; #import "BHTabBarController.h" #import <UserNotifications/UserNotifications.h> #import "BHConversationListController.h" @interface BHTabBarController ()<EMChatManagerDelegate> @property (strong, nonatomic) NSDate *lastPlaySoundDate; @end @implementation BHTabBarController - (void)viewDidLoad { [super viewDidLoad]; [DemoCallManager sharedManager].mainController = self; [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupUnreadMessageCount) name:@"setupUnreadMessageCount" object:nil]; } // 统计未读消息数 -(void)setupUnreadMessageCount { NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations]; NSInteger unreadCount = 0; for (EMConversation *conversation in conversations) { unreadCount += conversation.unreadMessagesCount; } NSArray *tabBarItems = self.tabBar.items; UITabBarItem *conlistTabBarItem = [tabBarItems objectAtIndex:1]; if (unreadCount > 0) { conlistTabBarItem.badgeValue = [NSString stringWithFormat:@"%i",(int)unreadCount]; }else{ conlistTabBarItem.badgeValue = nil; } // UIApplication *application = [UIApplication sharedApplication]; // [application setApplicationIconBadgeNumber:unreadCount]; } - (void)cmdMessagesDidReceive:(NSArray *)aCmdMessages { for (EMMessage *message in aCmdMessages) { EMCmdMessageBody *body = (EMCmdMessageBody *)message.body; NSLog(@"收到的action是 -- %@",body.action); if (body.action == nil) { return; } NSData *jsonData = [body.action dataUsingEncoding:NSUTF8StringEncoding]; NSError *err; NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err]; if(err) { NSLog(@"json解析失败:%@",err); return; } NSNumber *num = dic[@"count"]; NSString *str = [num stringValue]; [AppEngine.IMDataCent requestUpdateUnreadNumberWithUnreadNumber:str]; } } - (void)didReceiveMessages:(NSArray *)aMessages{ // [self refresh]; [self setupUnreadMessageCount]; EMMessage *message = aMessages[0]; NSString *sendPerson = message.from; BHNavigatiomController *imNaviCV = self.viewControllers[1]; BHConversationListController *converCV = imNaviCV.viewControllers[0]; // [converCV refresh]; [converCV refreshDataSource];//只要收到消息就从服务器拿 UIApplicationState state = [[UIApplication sharedApplication] applicationState]; for (NSString *hx_name in AppEngine.IMDataCent.data_ExcuseFriendsData) { if ([hx_name isEqualToString:sendPerson]) { return; } } switch (state) { case UIApplicationStateActive: [self playSoundAndVibration]; break; case UIApplicationStateInactive: [self playSoundAndVibration]; break; case UIApplicationStateBackground: [self showNotificationWithMessage:message]; break; default: break; } } - (void)playSoundAndVibration{ NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate]; if (timeInterval < kDefaultPlaySoundInterval) { //如果距离上次响铃和震动时间太短, 则跳过响铃 NSLog(@"skip ringing & vibration %@, %@", [NSDate date], self.lastPlaySoundDate); return; } //保存最后一次响铃时间 self.lastPlaySoundDate = [NSDate date]; // 收到消息时,播放音频 [[EMCDDeviceManager sharedInstance] playNewMessageSound]; // 收到消息时,震动 [[EMCDDeviceManager sharedInstance] playVibration]; } - (void)showNotificationWithMessage:(EMMessage *)message { EMPushOptions *options = [[EMClient sharedClient] pushOptions]; NSString *alertBody = nil; if (options.displayStyle == EMPushDisplayStyleMessageSummary) { EMMessageBody *messageBody = message.body; NSString *messageStr = nil; switch (messageBody.type) { case EMMessageBodyTypeText: { messageStr = ((EMTextMessageBody *)messageBody).text; } break; case EMMessageBodyTypeImage: { messageStr = NSLocalizedString(@"message.image", @"Image"); } break; case EMMessageBodyTypeLocation: { messageStr = NSLocalizedString(@"message.location", @"Location"); } break; case EMMessageBodyTypeVoice: { messageStr = NSLocalizedString(@"message.voice", @"Voice"); } break; case EMMessageBodyTypeVideo:{ messageStr = NSLocalizedString(@"message.video", @"Video"); } break; default: break; } do { // NSString *title = [[UserProfileManager sharedInstance] getNickNameWithUsername:message.from]; NSString *title = @"大佬"; if (message.chatType == EMChatTypeGroupChat) { NSDictionary *ext = message.ext; if (ext && ext[kGroupMessageAtList]) { id target = ext[kGroupMessageAtList]; if ([target isKindOfClass:[NSString class]]) { if ([kGroupMessageAtAll compare:target options:NSCaseInsensitiveSearch] == NSOrderedSame) { alertBody = [NSString stringWithFormat:@"%@%@", title, NSLocalizedString(@"group.atPushTitle", @" @ me in the group")]; break; } } else if ([target isKindOfClass:[NSArray class]]) { NSArray *atTargets = (NSArray*)target; if ([atTargets containsObject:[EMClient sharedClient].currentUsername]) { alertBody = [NSString stringWithFormat:@"%@%@", title, NSLocalizedString(@"group.atPushTitle", @" @ me in the group")]; break; } } } NSArray *groupArray = [[EMClient sharedClient].groupManager getJoinedGroups]; for (EMGroup *group in groupArray) { if ([group.groupId isEqualToString:message.conversationId]) { title = [NSString stringWithFormat:@"%@(%@)", message.from, group.subject]; break; } } } else if (message.chatType == EMChatTypeChatRoom) { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSString *key = [NSString stringWithFormat:@"OnceJoinedChatrooms_%@", [[EMClient sharedClient] currentUsername]]; NSMutableDictionary *chatrooms = [NSMutableDictionary dictionaryWithDictionary:[ud objectForKey:key]]; NSString *chatroomName = [chatrooms objectForKey:message.conversationId]; if (chatroomName) { title = [NSString stringWithFormat:@"%@(%@)", message.from, chatroomName]; } } alertBody = [NSString stringWithFormat:@"%@:%@", title, messageStr]; } while (0); } else{ // alertBody = NSLocalizedString(@"receiveMessage", @"you have a new message"); alertBody = @"您有一条消息"; } NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate]; BOOL playSound = NO; if (!self.lastPlaySoundDate || timeInterval >= kDefaultPlaySoundInterval) { self.lastPlaySoundDate = [NSDate date]; playSound = YES; } NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:[NSNumber numberWithInt:message.chatType] forKey:kMessageType]; [userInfo setObject:message.conversationId forKey:kConversationChatter]; //发送本地推送 if (NSClassFromString(@"UNUserNotificationCenter")) { UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.01 repeats:NO]; UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; if (playSound) { content.sound = [UNNotificationSound defaultSound]; } content.body =alertBody; content.userInfo = userInfo; UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:message.messageId content:content trigger:trigger]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; } else { UILocalNotification *notification = [[UILocalNotification alloc] init]; notification.fireDate = [NSDate date]; //触发通知的时间 notification.alertBody = alertBody; notification.alertAction = NSLocalizedString(@"open", @"Open"); notification.timeZone = [NSTimeZone defaultTimeZone]; if (playSound) { notification.soundName = UILocalNotificationDefaultSoundName; } notification.userInfo = userInfo; //发送通知 [[UIApplication sharedApplication] scheduleLocalNotification:notification]; } } - (void)messagesDidDeliver:(NSArray *)aMessages{ NSLog(@"sf"); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ @end
 
 
本人github:https://github.com/BHAreslee
本人简书:http://www.jianshu.com/u/bb53043aaa00
以上就是集成环信时暂时发现的问题,欢迎大家分享你们遇到的问题,也欢迎加入QQ群:372251359,一起讨论交流即时通讯的问题。
本人微信公众号:放心安慰剂





  查看全部
现在大多数社交app都有消息免打扰功能,因为环信SDK主要是对即时通讯模块进行封装,因此如果要实现消息免打扰功能则需要开发者自己对此功能逻辑进行处理。
先解释一下,本人项目中对消息免打扰功能的定义:正常情况下,我们收到的每一条聊天消息都会收到小红点、声音、震动的提示。如果对某个好友设置消息免打扰功能,则只提示小红点,声音和震动则不再提示。

下面简述结合环信SDK时,此功能的实现方法。
环信将即时聊天的所有功能分为四大模块进行管理:
//聊天模块:
[EMClient sharedClient].chatManager
//好友模块 :
[EMClient sharedClient].contactManager
//群组模块 :
[EMClient sharedClient].groupManager
//聊天室模块:
[EMClient sharedClient].roomManager
消息免打扰的功能借助聊天模块[EMClient sharedClient].chatManager的API就能实现。
一般来说,在app内不管当前在哪个界面,只要收到消息都需要被判断是否需要免打扰,因此可以在appdelegate里写如下代码,app通常都有tabBarController,那么也可以让tabBarController来实现如下代码。
首先让控制器tabBarController遵守代理EMChatManagerDelegate
然后成为代理
[[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];

EMChatManagerDelegate中有一个代理方法
- (void)didReceiveMessages:(NSArray *)aMessages;
实现此代理方法
- (void)didReceiveMessages:(NSArray *)aMessages{
[self setupUnreadMessageCount];
EMMessage *message = aMessages[0];
NSString *sendPerson = message.from;
BHNavigatiomController *imNaviCV = self.viewControllers[1];
BHConversationListController *converCV = imNaviCV.viewControllers[0];
[converCV refreshDataSource];//只要收到消息就从服务器拿
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
for (NSString *hx_name in AppEngine.IMDataCent.data_ExcuseFriendsData) {
if ([hx_name isEqualToString:sendPerson]) {
return;
}
}
switch (state) {
case UIApplicationStateActive:
[self playSoundAndVibration];
break;
case UIApplicationStateInactive:
[self playSoundAndVibration];
break;
case UIApplicationStateBackground:
[self showNotificationWithMessage:message];
break;
default:
break;
}
}
方法中有一个aMessages参数。这个参数是一个消息组,每一个元素都是EMMessage类型的实例。
因为通常都只有一条信息。因此只需要取出EMMessage *message = aMessages[0];
EMMessage类中有许多属性,因此根据一条信息基本可以获取想知道的所有信息。这里我们只需要知道此消息的发送方即可,也就是发送方的环信idNSString *sendPerson = message.from;下面代码中有一AppEngine.IMDataCent.data_ExcuseFriendsData,这个数组里面装的都是已经被设置消息免打扰的好友的环信id,是请求自己服务器获得的,获取的代码最好在一打开app时,就及时获取到。然后根据对好友免打扰设置的操作,访问后台接口进行增删,并刷新数组与后端保持一致即可。
通过[hx_name isEqualToString:sendPerson],遍历免打扰数组与当前消息的发送方环信id,就可以知道是否需要免打扰了。
 
下面附上完整代码
 

// // BHTabBarController.m // xxxxxxx // // Created by LiBohan on 2017/8/24. // Copyright © 2017年 xxxxxx. All rights reserved. // //两次提示的默认间隔 static const CGFloat kDefaultPlaySoundInterval = 3.0; static NSString *kMessageType = @"MessageType"; static NSString *kConversationChatter = @"ConversationChatter"; static NSString *kGroupName = @"GroupName"; #import "BHTabBarController.h" #import <UserNotifications/UserNotifications.h> #import "BHConversationListController.h" @interface BHTabBarController ()<EMChatManagerDelegate> @property (strong, nonatomic) NSDate *lastPlaySoundDate; @end @implementation BHTabBarController - (void)viewDidLoad { [super viewDidLoad]; [DemoCallManager sharedManager].mainController = self; [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupUnreadMessageCount) name:@"setupUnreadMessageCount" object:nil]; } // 统计未读消息数 -(void)setupUnreadMessageCount { NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations]; NSInteger unreadCount = 0; for (EMConversation *conversation in conversations) { unreadCount += conversation.unreadMessagesCount; } NSArray *tabBarItems = self.tabBar.items; UITabBarItem *conlistTabBarItem = [tabBarItems objectAtIndex:1]; if (unreadCount > 0) { conlistTabBarItem.badgeValue = [NSString stringWithFormat:@"%i",(int)unreadCount]; }else{ conlistTabBarItem.badgeValue = nil; } // UIApplication *application = [UIApplication sharedApplication]; // [application setApplicationIconBadgeNumber:unreadCount]; } - (void)cmdMessagesDidReceive:(NSArray *)aCmdMessages { for (EMMessage *message in aCmdMessages) { EMCmdMessageBody *body = (EMCmdMessageBody *)message.body; NSLog(@"收到的action是 -- %@",body.action); if (body.action == nil) { return; } NSData *jsonData = [body.action dataUsingEncoding:NSUTF8StringEncoding]; NSError *err; NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err]; if(err) { NSLog(@"json解析失败:%@",err); return; } NSNumber *num = dic[@"count"]; NSString *str = [num stringValue]; [AppEngine.IMDataCent requestUpdateUnreadNumberWithUnreadNumber:str]; } } - (void)didReceiveMessages:(NSArray *)aMessages{ // [self refresh]; [self setupUnreadMessageCount]; EMMessage *message = aMessages[0]; NSString *sendPerson = message.from; BHNavigatiomController *imNaviCV = self.viewControllers[1]; BHConversationListController *converCV = imNaviCV.viewControllers[0]; // [converCV refresh]; [converCV refreshDataSource];//只要收到消息就从服务器拿 UIApplicationState state = [[UIApplication sharedApplication] applicationState]; for (NSString *hx_name in AppEngine.IMDataCent.data_ExcuseFriendsData) { if ([hx_name isEqualToString:sendPerson]) { return; } } switch (state) { case UIApplicationStateActive: [self playSoundAndVibration]; break; case UIApplicationStateInactive: [self playSoundAndVibration]; break; case UIApplicationStateBackground: [self showNotificationWithMessage:message]; break; default: break; } } - (void)playSoundAndVibration{ NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate]; if (timeInterval < kDefaultPlaySoundInterval) { //如果距离上次响铃和震动时间太短, 则跳过响铃 NSLog(@"skip ringing & vibration %@, %@", [NSDate date], self.lastPlaySoundDate); return; } //保存最后一次响铃时间 self.lastPlaySoundDate = [NSDate date]; // 收到消息时,播放音频 [[EMCDDeviceManager sharedInstance] playNewMessageSound]; // 收到消息时,震动 [[EMCDDeviceManager sharedInstance] playVibration]; } - (void)showNotificationWithMessage:(EMMessage *)message { EMPushOptions *options = [[EMClient sharedClient] pushOptions]; NSString *alertBody = nil; if (options.displayStyle == EMPushDisplayStyleMessageSummary) { EMMessageBody *messageBody = message.body; NSString *messageStr = nil; switch (messageBody.type) { case EMMessageBodyTypeText: { messageStr = ((EMTextMessageBody *)messageBody).text; } break; case EMMessageBodyTypeImage: { messageStr = NSLocalizedString(@"message.image", @"Image"); } break; case EMMessageBodyTypeLocation: { messageStr = NSLocalizedString(@"message.location", @"Location"); } break; case EMMessageBodyTypeVoice: { messageStr = NSLocalizedString(@"message.voice", @"Voice"); } break; case EMMessageBodyTypeVideo:{ messageStr = NSLocalizedString(@"message.video", @"Video"); } break; default: break; } do { // NSString *title = [[UserProfileManager sharedInstance] getNickNameWithUsername:message.from]; NSString *title = @"大佬"; if (message.chatType == EMChatTypeGroupChat) { NSDictionary *ext = message.ext; if (ext && ext[kGroupMessageAtList]) { id target = ext[kGroupMessageAtList]; if ([target isKindOfClass:[NSString class]]) { if ([kGroupMessageAtAll compare:target options:NSCaseInsensitiveSearch] == NSOrderedSame) { alertBody = [NSString stringWithFormat:@"%@%@", title, NSLocalizedString(@"group.atPushTitle", @" @ me in the group")]; break; } } else if ([target isKindOfClass:[NSArray class]]) { NSArray *atTargets = (NSArray*)target; if ([atTargets containsObject:[EMClient sharedClient].currentUsername]) { alertBody = [NSString stringWithFormat:@"%@%@", title, NSLocalizedString(@"group.atPushTitle", @" @ me in the group")]; break; } } } NSArray *groupArray = [[EMClient sharedClient].groupManager getJoinedGroups]; for (EMGroup *group in groupArray) { if ([group.groupId isEqualToString:message.conversationId]) { title = [NSString stringWithFormat:@"%@(%@)", message.from, group.subject]; break; } } } else if (message.chatType == EMChatTypeChatRoom) { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSString *key = [NSString stringWithFormat:@"OnceJoinedChatrooms_%@", [[EMClient sharedClient] currentUsername]]; NSMutableDictionary *chatrooms = [NSMutableDictionary dictionaryWithDictionary:[ud objectForKey:key]]; NSString *chatroomName = [chatrooms objectForKey:message.conversationId]; if (chatroomName) { title = [NSString stringWithFormat:@"%@(%@)", message.from, chatroomName]; } } alertBody = [NSString stringWithFormat:@"%@:%@", title, messageStr]; } while (0); } else{ // alertBody = NSLocalizedString(@"receiveMessage", @"you have a new message"); alertBody = @"您有一条消息"; } NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate]; BOOL playSound = NO; if (!self.lastPlaySoundDate || timeInterval >= kDefaultPlaySoundInterval) { self.lastPlaySoundDate = [NSDate date]; playSound = YES; } NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:[NSNumber numberWithInt:message.chatType] forKey:kMessageType]; [userInfo setObject:message.conversationId forKey:kConversationChatter]; //发送本地推送 if (NSClassFromString(@"UNUserNotificationCenter")) { UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.01 repeats:NO]; UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; if (playSound) { content.sound = [UNNotificationSound defaultSound]; } content.body =alertBody; content.userInfo = userInfo; UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:message.messageId content:content trigger:trigger]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; } else { UILocalNotification *notification = [[UILocalNotification alloc] init]; notification.fireDate = [NSDate date]; //触发通知的时间 notification.alertBody = alertBody; notification.alertAction = NSLocalizedString(@"open", @"Open"); notification.timeZone = [NSTimeZone defaultTimeZone]; if (playSound) { notification.soundName = UILocalNotificationDefaultSoundName; } notification.userInfo = userInfo; //发送通知 [[UIApplication sharedApplication] scheduleLocalNotification:notification]; } } - (void)messagesDidDeliver:(NSArray *)aMessages{ NSLog(@"sf"); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ @end
 
 
本人github:https://github.com/BHAreslee
本人简书:http://www.jianshu.com/u/bb53043aaa00
以上就是集成环信时暂时发现的问题,欢迎大家分享你们遇到的问题,也欢迎加入QQ群:372251359,一起讨论交流即时通讯的问题。
本人微信公众号:放心安慰剂

qrcode_for_gh_bc92a063b4a2_430.jpg

 
0
评论

【环信征文】集成环信V3.3.4SDK遇到的两个问题 iOS 环信 即时通讯 IM

cokeyer 发表了文章 • 36 次浏览 • 2017-09-12 17:56 • 来自相关话题

公司又有了新项目,依然是含有即时通讯功能模块的项目。在经历了上个项目对环信sdk的集成后,对环信EaseUI有了大概的了解。这次果断还是集成环信,一回生二回熟,毕竟哪些地方有坑心里有数了。所以这次的项目集成起来就相对来说顺利很多。
项目集成的是环信V3.3.2版本,但今天下载了V3.3.4版本检查了一下,发现依然有不少V3.3.2版本遗留的问题没有修复,下面会列出一些项目中遇到的问题,并提供解决办法。
首先在会话界面自定义一个类比如BHChatViewController,继承自EaseMessageViewController类,基本上一个简单的界面就有了。因为是医疗类的项目,对话双方有可能是患者和患者,也有可能是患者和医生。项目需求是如果是患者和医生聊天,必须先经过预约,并只能在预约时间区间内才能和医生发送聊天消息、图片、语音、实时音视频。
以下就是EaseMessageViewController类的发送各种消息的方法
那么根据需要只需重写以下方法即可。/*!
@method
@brief 发送文本消息
@discussion
@param text 文本消息
@result
*/
- (void)sendTextMessage:(NSString *)text;

/*!
@method
@brief 发送文本消息
@discussion
@param text 文本消息
@param ext 扩展信息
@result
*/
- (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext;

/*!
@method
@brief 发送图片消息
@discussion
@param image 发送图片
@result
*/
- (void)sendImageMessage:(UIImage *)image;

/*!
@method
@brief 发送位置消息
@discussion
@param latitude 经度
@param longitude 纬度
@param address 地址
@result
*/
- (void)sendLocationMessageLatitude:(double)latitude
longitude:(double)longitude
andAddress:(NSString *)address;

/*!
@method
@brief 发送语音消息
@discussion
@param localPath 语音本地地址
@param duration 时长
@result
*/
- (void)sendVoiceMessageWithLocalPath:(NSString *)localPath
duration:(NSInteger)duration;

/*!
@method
@brief 发送视频消息
@discussion
@param url 视频url
@result
*/
- (void)sendVideoMessageWithURL:(NSURL *)url;我在对发送图片的操作进行处理的时候,发现当拍照发图时,走这个方法//当你拍照发图时,走这个方法
- (void)sendImageMessage:(UIImage *)image;但当从相册选择图片发送时,会发现,不走上面的方法了。
仔细检查代码发现走了EaseMessageViewController.m的如下方法





然而这个方法,环信并没有放在EaseMessageViewController.h成为公开方法。我们只需手动粘贴方法到.h,然后在自己的子类重写就可以了。

还有一个关于更多下图更多功能区域的问题





如上图所示,相册、拍照、视频等附加功能按钮,环信用EaseChatBarMoreView类来管理的。
如果需要增加功能按钮用这个方法/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;移除某个功能按钮用这个方法/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;修改一个功能按钮用这个方法/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;可当你添加按钮,或者修改按钮时,会发现按钮的名字设置不了
然后检查环信内部的实现,发现title的值在方法里根本就没用到?!
索性不用updateItemWithImage这个方法了,直接去改内部代码。
修改代码如下

到EaseChatBarMoreView.m修改- (void)setupSubviewsForType:(EMChatToolbarType)type方法。改动的部分在代码后面有标注。- (void)setupSubviewsForType:(EMChatToolbarType)type
{
//self.backgroundColor = [UIColor clearColor];
self.accessibilityIdentifier = @"more_view";

_scrollview = [[UIScrollView alloc] init];
_scrollview.pagingEnabled = YES;
_scrollview.showsHorizontalScrollIndicator = NO;
_scrollview.showsVerticalScrollIndicator = NO;
_scrollview.delegate = self;
[self addSubview:_scrollview];

_pageControl = [[UIPageControl alloc] init];
_pageControl.currentPage = 0;
_pageControl.numberOfPages = 1;
[self addSubview:_pageControl];

CGFloat insets = (self.frame.size.width - 4 * CHAT_BUTTON_SIZE) / 5;

_photoButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_photoButton setTitle:@"相册" forState:UIControlStateNormal];//改动
[_photoButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_photoButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_photoButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_photoButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
_photoButton.accessibilityIdentifier = @"image";
[_photoButton setFrame:CGRectMake(insets, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//改动
[_photoButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_photo"] forState:UIControlStateNormal];
[_photoButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_photoSelected"] forState:UIControlStateHighlighted];
[_photoButton addTarget:self action:@selector(photoAction) forControlEvents:UIControlEventTouchUpInside];
_photoButton.tag = MOREVIEW_BUTTON_TAG;
[_scrollview addSubview:_photoButton];

_locationButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_locationButton setTitle:@"位置" forState:UIControlStateNormal];//改动
[_locationButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_locationButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_locationButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_locationButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
_locationButton.accessibilityIdentifier = @"location";
[_locationButton setFrame:CGRectMake(insets * 2 + CHAT_BUTTON_SIZE, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_locationButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_location"] forState:UIControlStateNormal];
[_locationButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_locationSelected"] forState:UIControlStateHighlighted];
[_locationButton addTarget:self action:@selector(locationAction) forControlEvents:UIControlEventTouchUpInside];
_locationButton.tag = MOREVIEW_BUTTON_TAG + 1;
[_scrollview addSubview:_locationButton];

_takePicButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_takePicButton setTitle:@"拍照" forState:UIControlStateNormal];//改动
[_takePicButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_takePicButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_takePicButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_takePicButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_takePicButton setFrame:CGRectMake(insets * 3 + CHAT_BUTTON_SIZE * 2, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//改动
[_takePicButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_camera"] forState:UIControlStateNormal];
[_takePicButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_cameraSelected"] forState:UIControlStateHighlighted];
[_takePicButton addTarget:self action:@selector(takePicAction) forControlEvents:UIControlEventTouchUpInside];
_takePicButton.tag = MOREVIEW_BUTTON_TAG + 2;
_maxIndex = 2;
[_scrollview addSubview:_takePicButton];

CGRect frame = self.frame;
if (type == EMChatToolbarTypeChat) {
frame.size.height = 150;
_audioCallButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_audioCallButton setTitle:@"语音" forState:UIControlStateNormal];//改动
[_audioCallButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_audioCallButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_audioCallButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_audioCallButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_audioCallButton setFrame:CGRectMake(insets * 4 + CHAT_BUTTON_SIZE * 3, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_audioCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_audioCall"] forState:UIControlStateNormal];
[_audioCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_audioCallSelected"] forState:UIControlStateHighlighted];
[_audioCallButton addTarget:self action:@selector(takeAudioCallAction) forControlEvents:UIControlEventTouchUpInside];
_audioCallButton.tag = MOREVIEW_BUTTON_TAG + 3;
[_scrollview addSubview:_audioCallButton];

_videoCallButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_videoCallButton setTitle:@"视频" forState:UIControlStateNormal];//改动
[_videoCallButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_videoCallButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_videoCallButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_videoCallButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_videoCallButton setFrame:CGRectMake(insets, 10 * 2 + CHAT_BUTTON_SIZE + 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_videoCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_videoCall"] forState:UIControlStateNormal];
[_videoCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_videoCallSelected"] forState:UIControlStateHighlighted];
[_videoCallButton addTarget:self action:@selector(takeVideoCallAction) forControlEvents:UIControlEventTouchUpInside];
_videoCallButton.tag =MOREVIEW_BUTTON_TAG + 4;
_maxIndex = 4;
[_scrollview addSubview:_videoCallButton];
}
else if (type == EMChatToolbarTypeGroup)
{
frame.size.height = 80;
}
self.frame = frame;
_scrollview.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
_pageControl.frame = CGRectMake(0, CGRectGetHeight(frame) - 20, CGRectGetWidth(frame), 20);
_pageControl.hidden = _pageControl.numberOfPages<=1;
}


本人github:https://github.com/BHAreslee
本人简书:http://www.jianshu.com/u/bb53043aaa00
以上就是集成环信时暂时发现的问题,欢迎大家分享你们遇到的问题,也欢迎加入QQ群:372251359,一起讨论交流即时通讯的问题。
本人微信公众号:放心安慰剂 查看全部
公司又有了新项目,依然是含有即时通讯功能模块的项目。在经历了上个项目对环信sdk的集成后,对环信EaseUI有了大概的了解。这次果断还是集成环信,一回生二回熟,毕竟哪些地方有坑心里有数了。所以这次的项目集成起来就相对来说顺利很多。
项目集成的是环信V3.3.2版本,但今天下载了V3.3.4版本检查了一下,发现依然有不少V3.3.2版本遗留的问题没有修复,下面会列出一些项目中遇到的问题,并提供解决办法。
首先在会话界面自定义一个类比如BHChatViewController,继承自EaseMessageViewController类,基本上一个简单的界面就有了。因为是医疗类的项目,对话双方有可能是患者和患者,也有可能是患者和医生。项目需求是如果是患者和医生聊天,必须先经过预约,并只能在预约时间区间内才能和医生发送聊天消息、图片、语音、实时音视频。
以下就是EaseMessageViewController类的发送各种消息的方法
那么根据需要只需重写以下方法即可。
/*!
@method
@brief 发送文本消息
@discussion
@param text 文本消息
@result
*/
- (void)sendTextMessage:(NSString *)text;

/*!
@method
@brief 发送文本消息
@discussion
@param text 文本消息
@param ext 扩展信息
@result
*/
- (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext;

/*!
@method
@brief 发送图片消息
@discussion
@param image 发送图片
@result
*/
- (void)sendImageMessage:(UIImage *)image;

/*!
@method
@brief 发送位置消息
@discussion
@param latitude 经度
@param longitude 纬度
@param address 地址
@result
*/
- (void)sendLocationMessageLatitude:(double)latitude
longitude:(double)longitude
andAddress:(NSString *)address;

/*!
@method
@brief 发送语音消息
@discussion
@param localPath 语音本地地址
@param duration 时长
@result
*/
- (void)sendVoiceMessageWithLocalPath:(NSString *)localPath
duration:(NSInteger)duration;

/*!
@method
@brief 发送视频消息
@discussion
@param url 视频url
@result
*/
- (void)sendVideoMessageWithURL:(NSURL *)url;
我在对发送图片的操作进行处理的时候,发现当拍照发图时,走这个方法
//当你拍照发图时,走这个方法
- (void)sendImageMessage:(UIImage *)image;
但当从相册选择图片发送时,会发现,不走上面的方法了。
仔细检查代码发现走了EaseMessageViewController.m的如下方法

环信2.png

然而这个方法,环信并没有放在EaseMessageViewController.h成为公开方法。我们只需手动粘贴方法到.h,然后在自己的子类重写就可以了。

还有一个关于更多下图更多功能区域的问题

EaseChatBarMoreView.png

如上图所示,相册、拍照、视频等附加功能按钮,环信用EaseChatBarMoreView类来管理的。
如果需要增加功能按钮用这个方法
/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;
移除某个功能按钮用这个方法
/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;
修改一个功能按钮用这个方法
/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;
可当你添加按钮,或者修改按钮时,会发现按钮的名字设置不了
然后检查环信内部的实现,发现title的值在方法里根本就没用到?!
索性不用updateItemWithImage这个方法了,直接去改内部代码。
修改代码如下

到EaseChatBarMoreView.m修改- (void)setupSubviewsForType:(EMChatToolbarType)type方法。改动的部分在代码后面有标注。
- (void)setupSubviewsForType:(EMChatToolbarType)type
{
//self.backgroundColor = [UIColor clearColor];
self.accessibilityIdentifier = @"more_view";

_scrollview = [[UIScrollView alloc] init];
_scrollview.pagingEnabled = YES;
_scrollview.showsHorizontalScrollIndicator = NO;
_scrollview.showsVerticalScrollIndicator = NO;
_scrollview.delegate = self;
[self addSubview:_scrollview];

_pageControl = [[UIPageControl alloc] init];
_pageControl.currentPage = 0;
_pageControl.numberOfPages = 1;
[self addSubview:_pageControl];

CGFloat insets = (self.frame.size.width - 4 * CHAT_BUTTON_SIZE) / 5;

_photoButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_photoButton setTitle:@"相册" forState:UIControlStateNormal];//改动
[_photoButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_photoButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_photoButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_photoButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
_photoButton.accessibilityIdentifier = @"image";
[_photoButton setFrame:CGRectMake(insets, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//改动
[_photoButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_photo"] forState:UIControlStateNormal];
[_photoButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_photoSelected"] forState:UIControlStateHighlighted];
[_photoButton addTarget:self action:@selector(photoAction) forControlEvents:UIControlEventTouchUpInside];
_photoButton.tag = MOREVIEW_BUTTON_TAG;
[_scrollview addSubview:_photoButton];

_locationButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_locationButton setTitle:@"位置" forState:UIControlStateNormal];//改动
[_locationButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_locationButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_locationButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_locationButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
_locationButton.accessibilityIdentifier = @"location";
[_locationButton setFrame:CGRectMake(insets * 2 + CHAT_BUTTON_SIZE, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_locationButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_location"] forState:UIControlStateNormal];
[_locationButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_locationSelected"] forState:UIControlStateHighlighted];
[_locationButton addTarget:self action:@selector(locationAction) forControlEvents:UIControlEventTouchUpInside];
_locationButton.tag = MOREVIEW_BUTTON_TAG + 1;
[_scrollview addSubview:_locationButton];

_takePicButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_takePicButton setTitle:@"拍照" forState:UIControlStateNormal];//改动
[_takePicButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_takePicButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_takePicButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_takePicButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_takePicButton setFrame:CGRectMake(insets * 3 + CHAT_BUTTON_SIZE * 2, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//改动
[_takePicButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_camera"] forState:UIControlStateNormal];
[_takePicButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_cameraSelected"] forState:UIControlStateHighlighted];
[_takePicButton addTarget:self action:@selector(takePicAction) forControlEvents:UIControlEventTouchUpInside];
_takePicButton.tag = MOREVIEW_BUTTON_TAG + 2;
_maxIndex = 2;
[_scrollview addSubview:_takePicButton];

CGRect frame = self.frame;
if (type == EMChatToolbarTypeChat) {
frame.size.height = 150;
_audioCallButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_audioCallButton setTitle:@"语音" forState:UIControlStateNormal];//改动
[_audioCallButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_audioCallButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_audioCallButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_audioCallButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_audioCallButton setFrame:CGRectMake(insets * 4 + CHAT_BUTTON_SIZE * 3, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_audioCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_audioCall"] forState:UIControlStateNormal];
[_audioCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_audioCallSelected"] forState:UIControlStateHighlighted];
[_audioCallButton addTarget:self action:@selector(takeAudioCallAction) forControlEvents:UIControlEventTouchUpInside];
_audioCallButton.tag = MOREVIEW_BUTTON_TAG + 3;
[_scrollview addSubview:_audioCallButton];

_videoCallButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_videoCallButton setTitle:@"视频" forState:UIControlStateNormal];//改动
[_videoCallButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_videoCallButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_videoCallButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_videoCallButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_videoCallButton setFrame:CGRectMake(insets, 10 * 2 + CHAT_BUTTON_SIZE + 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_videoCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_videoCall"] forState:UIControlStateNormal];
[_videoCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_videoCallSelected"] forState:UIControlStateHighlighted];
[_videoCallButton addTarget:self action:@selector(takeVideoCallAction) forControlEvents:UIControlEventTouchUpInside];
_videoCallButton.tag =MOREVIEW_BUTTON_TAG + 4;
_maxIndex = 4;
[_scrollview addSubview:_videoCallButton];
}
else if (type == EMChatToolbarTypeGroup)
{
frame.size.height = 80;
}
self.frame = frame;
_scrollview.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
_pageControl.frame = CGRectMake(0, CGRectGetHeight(frame) - 20, CGRectGetWidth(frame), 20);
_pageControl.hidden = _pageControl.numberOfPages<=1;
}


本人github:https://github.com/BHAreslee
本人简书:http://www.jianshu.com/u/bb53043aaa00
以上就是集成环信时暂时发现的问题,欢迎大家分享你们遇到的问题,也欢迎加入QQ群:372251359,一起讨论交流即时通讯的问题。
本人微信公众号:放心安慰剂

qrcode_for_gh_bc92a063b4a2_430.jpg
0
回复

IOS 获取当前用户所有群组数据无效 iOS 环信_iOS

回复

尾声动听 发起了问题 • 1 人关注 • 65 次浏览 • 2017-09-12 14:21 • 来自相关话题

0
回复

收到消息的代理回调不会执行 环信 iOS

回复

Aries 发起了问题 • 1 人关注 • 66 次浏览 • 2017-09-12 10:28 • 来自相关话题

0
回复

环信带推送吗? iOS 推送

回复

爆肝 发起了问题 • 1 人关注 • 71 次浏览 • 2017-09-11 17:27 • 来自相关话题

1
最佳

环信 手动添加消息实现逻辑? iOS

wangyuzhang 回复了问题 • 2 人关注 • 150 次浏览 • 2017-08-30 21:09 • 来自相关话题

1
回复

环信3.3.4消息撤回的问题 iOS 环信_iOS

wangyuzhang 回复了问题 • 2 人关注 • 148 次浏览 • 2017-08-22 00:00 • 来自相关话题

0
回复

[NSBundle initWithURL:]: nil URL argument' 环信_iOS ios 3.2.3 iOS 环信 ios 导入sdk问题 iOS

回复

yong123 发起了问题 • 1 人关注 • 143 次浏览 • 2017-08-16 16:51 • 来自相关话题

2
回复

iOS _rr_open in libHyphenateVideoRecorderPlugin.a(rav_record.o) iOS 环信 iOS

yong123 回复了问题 • 2 人关注 • 149 次浏览 • 2017-08-16 16:47 • 来自相关话题

1
回复

'EMSDKFull.h' file not found iOS 环信

wangyuzhang 回复了问题 • 2 人关注 • 159 次浏览 • 2017-08-08 19:46 • 来自相关话题

2
回复

ios集成环信移动客服,点击发送emoji表情,只显示字符串,不显示emoji,求解 环信移动客服 iOS

pakrs12341 回复了问题 • 3 人关注 • 195 次浏览 • 2017-08-08 09:54 • 来自相关话题

1
回复

发送录音失败 发送录音失败 录音 iOS

dujiepeng 回复了问题 • 2 人关注 • 210 次浏览 • 2017-08-04 19:41 • 来自相关话题

1
回复
1
回复

iOS 反复接受同一条透传 iOS 透传 透传消息

wangyuzhang 回复了问题 • 2 人关注 • 166 次浏览 • 2017-07-31 20:13 • 来自相关话题

2
回复

发送语音一直失败 语音发送问题 iOS

dujiepeng 回复了问题 • 3 人关注 • 183 次浏览 • 2017-07-28 21:43 • 来自相关话题

1
回复

ios编译通过 运行直接crash iOS

wangyuzhang 回复了问题 • 2 人关注 • 201 次浏览 • 2017-07-20 19:18 • 来自相关话题

6
最佳

会话列表接受消息时没反应 要下拉刷新才出来 怎么解决 消息接受 iOS 环信_iOS

ljQwe 回复了问题 • 5 人关注 • 2171 次浏览 • 2017-07-12 16:16 • 来自相关话题

1
回复

请教环信3.2.1版本升级到3.3.2版本指南。 iOS 环信_iOS

木云落 回复了问题 • 2 人关注 • 238 次浏览 • 2017-07-06 14:23 • 来自相关话题

25
最佳

求一份 iOS 3.x 群聊demo 环信_iOS 群聊demo 群聊 iOS

撑住丶为心中狂野的目标 回复了问题 • 25 人关注 • 2289 次浏览 • 2017-07-04 22:00 • 来自相关话题

2
回复

ios 导入easeui 报错 iOS iOS 环信

KevinGong 回复了问题 • 3 人关注 • 239 次浏览 • 2017-06-22 19:04 • 来自相关话题

2
回复
1
回复

iOS pod导入环信SDK iOS pod SDK

lifei9241 回复了问题 • 2 人关注 • 254 次浏览 • 2017-06-20 16:08 • 来自相关话题

2
评论

环信进阶篇-实现名片|红包|话题聊天室等自定义cell cell自定义 cell iOS环信 iOS iOS 新手

不死小强 发表了文章 • 885 次浏览 • 2017-06-20 09:50 • 来自相关话题

    伴随着即时通讯成为了越来越多APP的刚需,匿名社交、阅后即焚、红包等新玩法层出不穷,基本的聊天方式越来越难以满足变态的需求。环信提供的自定义扩展属性功能非常的强大,能够帮助我们在cell中的各种需求做定制处理。这是分享一个之前做过的方法及实现,大家可以借鉴处理的过程及思路,如有不妥之处,请大家及时留言告知,谢谢。 
今天就给大家介绍下怎么对cell中的各种需求的定制处理


 类型一:在现有会话cell上修改UI效果

类似于上面给出的截图,我们有时候需要对环信官方给出的cell进行些许的调整。例如:项目中加入了不同于普通群聊或者聊天室的功能需求
点击话题聊天,大家加入聊天室,这里发出的各种就是不同于普通聊天,普通的聊天只需展示文字、地址、图片等等,但是这里的需求是得加上时间、私聊按钮,没砍需求之前是还有点赞和取消赞的按钮。
我们在普通聊天的基础上新建几个cell,文字、语音、图片、地图等等,不能和原有的普通cell混合起来,因为需求有普通聊天。

直接把普通聊天cell中的代码拷贝过来,再在此基础上进行cell的UI自定义处理,就拿文字聊天时的处理情况为例:

1、拷贝复制原有普通聊天cell内的代码

2、把需要的新增的UI控件初始化

3、适配各类控件

4、传值及赋值

5、新增按钮点击和本身cell的点击效果处理(别和cell上的点击效果混到一起)

6、耐心调整cell上UI效果

以上基本就是简单的自定义cell步骤了,有基础的小伙伴看下步骤应该就有思路了

类型二:类似于红包和名片Cell的UI效果


通常在我们项目中,并不只有文字、图片等等这些简单的聊天内容,有时候我们需要把自己的信息作为一张名片发给对方、发个红包给好朋友、发一个项目中的一个模块介绍给对方等等功能要求。
我们就拿雷哥的这张假名片为例:/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;
/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;
/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;*  消息体类型
typedef enum{
EMMessageBodyTypeText  = 1,    /*! \~chinese 文本类型 \~english Text */
EMMessageBodyTypeImage,        /*! \~chinese 图片类型 \~english Image */
EMMessageBodyTypeVideo,        /*! \~chinese 视频类型 \~english Video */
EMMessageBodyTypeLocation,      /*! \~chinese 位置类型 \~english Location */
EMMessageBodyTypeVoice,        /*! \~chinese 语音类型 \~english Voice */
EMMessageBodyTypeFile,          /*! \~chinese 文件类型 \~english File */
EMMessageBodyTypeCmd,          /*! \~chinese 命令类型 \~english Command */
}EMMessageBodyType;
如果环信把这个开放出来,或许我们就更加简单了我们只需自己修改成自己对应的类型即可。但是这个目前就想想,所以我们可以在以上类型中找一个出来,在它的基础上做些文章,变成我们想要的类型。

红包和名片最像什么。。。。对,不就和图片差不多嘛,不过小伙伴也不要以为只能拿图片来做文章,其他的我们都可以拿来用,这里就拿文字类型来作为例子(原理都一样)。




名片类型
这里我们只简要介绍怎么根据会话类型来显示名片,具体传值等怎么做,有基础的小伙伴应该都懂,不懂的小伙伴见文章底部。
 
我们需要在发送名片时,在拓展消息里面存一个名片的字段,这个字段可以被用来判断是名片、红包等等。名片、红包等等中内容,同样也存在拓展属性中(这里不做过多介绍)我们在展示自己的消息和接收到对方的消息时,在文字类型的基础上再进一步判断是什么类型,加载对应类型的视图,如果是红包就加载红包的view,如果是名片就展示名片view......





加载不同类型的cell
好了,以上就是我们所要介绍的两种不同类型cell的处理办法。


以下是补充自定义cell时遇到的各种情况及处理:
1、cell上语音、图片等原始点击和新增按钮点击冲突处理:

注释掉原有的点击方法,把原有的点击方法放到具体的控件上去,避免cell上多个控件点击的冲突

重点:记得把气泡上的点击权限打开_backgroundImageView.userInteractionEnabled = YES;





解决点击冲突
2、cell上语音气泡长度的改变,避免过段影响布局

我们只需把原有语音上的语音长度Label距语音图片控件调大一点距离就能自动把语音类气泡拉长。(其他类型一样处理原理)




语音气泡拉长
3、因新增控件导致在原有cell上高度的变化处理
/*! @method @brief 根据消息的内容,获取当前cell的高度 @discussion @param model 消息对象model @result 返回cell高度 */
+ (CGFloat)cellHeightWithModel:(id)model在原cell高度处理的情况下,根据各种类型的判断进行cell高度的自适应。




cell高度处理
4、文字类型气泡长度的处理

我暂时的处理方法:判断输入的文字长度,加入文字长度小于10,我会在后面自动补全5个空格,被动撑长气泡的长度。

假如小伙伴们有更好的建议也可以留言,谢谢! 查看全部
    伴随着即时通讯成为了越来越多APP的刚需,匿名社交、阅后即焚、红包等新玩法层出不穷,基本的聊天方式越来越难以满足变态的需求。环信提供的自定义扩展属性功能非常的强大,能够帮助我们在cell中的各种需求做定制处理。这是分享一个之前做过的方法及实现,大家可以借鉴处理的过程及思路,如有不妥之处,请大家及时留言告知,谢谢。 
今天就给大家介绍下怎么对cell中的各种需求的定制处理


 类型一:在现有会话cell上修改UI效果

类似于上面给出的截图,我们有时候需要对环信官方给出的cell进行些许的调整。例如:项目中加入了不同于普通群聊或者聊天室的功能需求
点击话题聊天,大家加入聊天室,这里发出的各种就是不同于普通聊天,普通的聊天只需展示文字、地址、图片等等,但是这里的需求是得加上时间、私聊按钮,没砍需求之前是还有点赞和取消赞的按钮。
我们在普通聊天的基础上新建几个cell,文字、语音、图片、地图等等,不能和原有的普通cell混合起来,因为需求有普通聊天。

直接把普通聊天cell中的代码拷贝过来,再在此基础上进行cell的UI自定义处理,就拿文字聊天时的处理情况为例:


1、拷贝复制原有普通聊天cell内的代码

2、把需要的新增的UI控件初始化

3、适配各类控件

4、传值及赋值

5、新增按钮点击和本身cell的点击效果处理(别和cell上的点击效果混到一起)

6、耐心调整cell上UI效果

以上基本就是简单的自定义cell步骤了,有基础的小伙伴看下步骤应该就有思路了


类型二:类似于红包和名片Cell的UI效果


通常在我们项目中,并不只有文字、图片等等这些简单的聊天内容,有时候我们需要把自己的信息作为一张名片发给对方、发个红包给好朋友、发一个项目中的一个模块介绍给对方等等功能要求。
我们就拿雷哥的这张假名片为例:
/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;
/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;
/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;
*  消息体类型
typedef enum{
EMMessageBodyTypeText  = 1,    /*! \~chinese 文本类型 \~english Text */
EMMessageBodyTypeImage,        /*! \~chinese 图片类型 \~english Image */
EMMessageBodyTypeVideo,        /*! \~chinese 视频类型 \~english Video */
EMMessageBodyTypeLocation,      /*! \~chinese 位置类型 \~english Location */
EMMessageBodyTypeVoice,        /*! \~chinese 语音类型 \~english Voice */
EMMessageBodyTypeFile,          /*! \~chinese 文件类型 \~english File */
EMMessageBodyTypeCmd,          /*! \~chinese 命令类型 \~english Command */
}EMMessageBodyType;
如果环信把这个开放出来,或许我们就更加简单了我们只需自己修改成自己对应的类型即可。但是这个目前就想想,所以我们可以在以上类型中找一个出来,在它的基础上做些文章,变成我们想要的类型。

红包和名片最像什么。。。。对,不就和图片差不多嘛,不过小伙伴也不要以为只能拿图片来做文章,其他的我们都可以拿来用,这里就拿文字类型来作为例子(原理都一样)。
01.jpg

名片类型
这里我们只简要介绍怎么根据会话类型来显示名片,具体传值等怎么做,有基础的小伙伴应该都懂,不懂的小伙伴见文章底部。
 
  1. 我们需要在发送名片时,在拓展消息里面存一个名片的字段,这个字段可以被用来判断是名片、红包等等。
  2. 名片、红包等等中内容,同样也存在拓展属性中(这里不做过多介绍)
  3. 我们在展示自己的消息和接收到对方的消息时,在文字类型的基础上再进一步判断是什么类型,加载对应类型的视图,如果是红包就加载红包的view,如果是名片就展示名片view......


02.jpg

加载不同类型的cell
好了,以上就是我们所要介绍的两种不同类型cell的处理办法。


以下是补充自定义cell时遇到的各种情况及处理:
1、cell上语音、图片等原始点击和新增按钮点击冲突处理:

注释掉原有的点击方法,把原有的点击方法放到具体的控件上去,避免cell上多个控件点击的冲突

重点:记得把气泡上的点击权限打开
_backgroundImageView.userInteractionEnabled = YES;


03.jpg

解决点击冲突
2、cell上语音气泡长度的改变,避免过段影响布局

我们只需把原有语音上的语音长度Label距语音图片控件调大一点距离就能自动把语音类气泡拉长。(其他类型一样处理原理)
04.jpg

语音气泡拉长
3、因新增控件导致在原有cell上高度的变化处理
/*! @method @brief 根据消息的内容,获取当前cell的高度 @discussion @param model        消息对象model @result 返回cell高度 */
+ (CGFloat)cellHeightWithModel:(id)model
在原cell高度处理的情况下,根据各种类型的判断进行cell高度的自适应。
05.jpg

cell高度处理
4、文字类型气泡长度的处理

我暂时的处理方法:判断输入的文字长度,加入文字长度小于10,我会在后面自动补全5个空格,被动撑长气泡的长度。

假如小伙伴们有更好的建议也可以留言,谢谢!
条新动态, 点击查看
zl

zl 回答了问题 • 2015-12-03 17:00 • 19 个回复 不感兴趣

iOS EaseMessageCell.h:48:44: Property has a previous declaration

赞同来自:

emoji的  表情。添加下相关库、
emoji的  表情。添加下相关库、
获取会话的最后一条消息,conversation.latestmessage.ext 然后获取出来之后去显示吧
 
获取会话的最后一条消息,conversation.latestmessage.ext 然后获取出来之后去显示吧
 
根控制器中这个回调方法没实现吧:/*!
 @method
 @brief 实时通话状态发生变化时的回调
 @param callSession 实时通话的实例
 @param reason   变化原因
 @param error    错误信息
 */
- ... 显示全部 »
根控制器中这个回调方法没实现吧:/*!
 @method
 @brief 实时通话状态发生变化时的回调
 @param callSession 实时通话的实例
 @param reason   变化原因
 @param error    错误信息
 */
- (void)callSessionStatusChanged:(EMCallSession *)callSession
                    changeReason:(EMCallStatusChangedReason)reason
                           error:(EMError *)error;
参考demo中怎么设置的,还需要注册代理:
[[EaseMob sharedInstance].callManager removeDelegate:self];
    // 注册为Call的Delegate
    [[EaseMob sharedInstance].callManager addDelegate:self delegateQueue:nil];
发送方看下didsendmessage回调方法中,message.ext有没有设置的值。
发送方看下didsendmessage回调方法中,message.ext有没有设置的值。
zl

zl 回答了问题 • 2016-02-22 17:46 • 4 个回复 不感兴趣

好友申请和入群申请

赞同来自:

是啊 。这个是回调,代理传值。sdk内部接收到好友请求,然后返回给你,就是通过这个方法接收。
是啊 。这个是回调,代理传值。sdk内部接收到好友请求,然后返回给你,就是通过这个方法接收。
sdk.a包括实时语音通话和实时视频,里面有lite.a里的功能。 lite.a有基本聊天功能。 这两个留一个就行了。不用实时语音通话和视频,就用lite.a就可以了
sdk.a包括实时语音通话和实时视频,里面有lite.a里的功能。 lite.a有基本聊天功能。 这两个留一个就行了。不用实时语音通话和视频,就用lite.a就可以了
zl

zl 回答了问题 • 2016-03-23 11:53 • 3 个回复 不感兴趣

与支付宝冲突

赞同来自:

other linker 设置下 -force_load + 支付宝的库的路径,并放在最前面。这里不需要再设置环信sdk。
other linker 设置下 -force_load + 支付宝的库的路径,并放在最前面。这里不需要再设置环信sdk。
环信沈冲

环信沈冲 回答了问题 • 2016-04-07 18:52 • 1 个回复 不感兴趣

iOS集成环信Demo 3.0在不同机型上运行问题

赞同来自:

在3.0demo中找到FixFopen.c导入项目中,并在pch文件中的首尾加上__OBJC__和endif
在3.0demo中找到FixFopen.c导入项目中,并在pch文件中的首尾加上__OBJC__和endif
donghai

donghai 回答了问题 • 2016-04-12 19:37 • 25 个回复 不感兴趣

求一份 iOS 3.x 群聊demo

赞同来自:

QQ邮箱给我,我给你发一份。
QQ邮箱给我,我给你发一份。
貌似应该是三方框架有重复, 解决了
貌似应该是三方框架有重复, 解决了
- (NSMutableArray *)loadDataSource

{

    NSMutableArray *ret = nil;

    NSArray *conversations = [[EaseMob sharedInstance].chat... 显示全部 »
- (NSMutableArray *)loadDataSource

{

    NSMutableArray *ret = nil;

    NSArray *conversations = [[EaseMob sharedInstance].chatManager conversations];




    NSArray* sorte = [conversations sortedArrayUsingComparator:

           ^(EMConversation *obj1, EMConversation* obj2){

               EMMessage *message1 = [obj1 latestMessage];

               EMMessage *message2 = [obj2 latestMessage];

               if(message1.timestamp > message2.timestamp) {

                   return(NSComparisonResult)NSOrderedAscending;

               }else {

                   return(NSComparisonResult)NSOrderedDescending;

               }

           }];

    

    ret = [[NSMutableArray alloc] initWithArray:sorte];

    return ret;

}
 
你在每次刷新的时候调这个方法就可以吧。  Demo有
lizilong

lizilong 回答了问题 • 2016-05-16 14:26 • 2 个回复 不感兴趣

iOS集成parse的报错,大神请进

赞同来自:

应该是缺少这两个依赖
Accounts.framework
Social.framework
 
应该是缺少这两个依赖
Accounts.framework
Social.framework
 
zhangyb

zhangyb 回答了问题 • 2016-06-20 19:37 • 1 个回复 不感兴趣

ios 8, ipV6, 环信demo,无法登录,无法用了

赞同来自:

iOS9.2以下,不支持ipv6
iOS9.2以下,不支持ipv6
feynmanren

feynmanren 回答了问题 • 2016-06-29 15:16 • 1 个回复 不感兴趣

环信3.0,登录admin帐户失败!

赞同来自:

自己回答吧, 需要自己手动创建admin账户。
自己回答吧, 需要自己手动创建admin账户。
mazhihua

mazhihua 回答了问题 • 2016-08-09 19:28 • 2 个回复 不感兴趣

iOS 角标问题 app 活跃状态 和 被杀死 情况下

赞同来自:

您好,这个角标是服务器设置的,改不了的,因为服务器推送的离线消息是从0 开始计算的,所以从1 开始重新累加了
您好,这个角标是服务器设置的,改不了的,因为服务器推送的离线消息是从0 开始计算的,所以从1 开始重新累加了
[[EaseSDKHelper shareHelper] hyphenateApplication:application
                    didFinishLaunchingWithOptions:launchOptions
    ... 显示全部 »
[[EaseSDKHelper shareHelper] hyphenateApplication:application
                    didFinishLaunchingWithOptions:launchOptions
                                           appkey:appkey
                                     apnsCertName:apnsCertName
                                      otherConfig:@{kSDKConfigEnableConsoleLogger:[NSNumber numberWithBool:YES]}];
Swift

Swift 回答了问题 • 2016-11-03 16:46 • 2 个回复 不感兴趣

ios如何添加Emoji哇

赞同来自:

已解决EaseMessageViewController在的viewdidload方法的
第一行添加:[self setupEmotion];
最后一行添加: EaseEmotionManager *manager= [[EaseEmotionManager ... 显示全部 »
已解决EaseMessageViewController在的viewdidload方法的
第一行添加:[self setupEmotion];
最后一行添加: EaseEmotionManager *manager= [[EaseEmotionManager alloc] initWithType:EMEmotionDefault emotionRow:3 emotionCol:7 emotions:[EaseEmoji allEmoji]]; [self.faceView setEmotionManagers:@[manager]];
然后神奇的出现了emoji
环信沈冲

环信沈冲 回答了问题 • 2016-12-26 14:32 • 2 个回复 不感兴趣

环信服务端集成,如何集成?

赞同来自:

客户端直接集成SDK即可,服务端可以根据自己需求调用相应的rest接口集成
客户端直接集成SDK即可,服务端可以根据自己需求调用相应的rest接口集成
beyond

beyond 回答了问题 • 2017-01-10 17:32 • 1 个回复 不感兴趣

iOS动态库sdk上架问题?

赞同来自:

你好,环信在V3.2.3 2016-12-29版本支持了动态SDK
可以参考环信动态库sdk上架问题解决方案 http://www.imgeek.org/article/825308639
你好,环信在V3.2.3 2016-12-29版本支持了动态SDK
可以参考环信动态库sdk上架问题解决方案 http://www.imgeek.org/article/825308639
donghai

donghai 回答了问题 • 2017-01-21 11:49 • 6 个回复 不感兴趣

关于ios登陆的问题

赞同来自:

环信demo在初始化SDK方法那里换你自己的appkey,别在宏定义那里换,报用户不存在就是你自己的appkey填的不对,或者登录环信的账号不对
环信demo在初始化SDK方法那里换你自己的appkey,别在宏定义那里换,报用户不存在就是你自己的appkey填的不对,或者登录环信的账号不对
环信沈冲

环信沈冲 回答了问题 • 2017-02-15 12:14 • 1 个回复 不感兴趣

iOS 是从哪一版开始不支持 iOS 7的?

赞同来自:

3.2.3动态库版本不支持iOS7
3.2.3动态库版本不支持iOS7
 插入消息方法换一下 [[EMClient sharedClient].chatManager importMessages:@[message1] completion:^(EMError *aError) {

                    }]... 显示全部 »
 插入消息方法换一下 [[EMClient sharedClient].chatManager importMessages:@[message1] completion:^(EMError *aError) {

                    }];换成 [conversation insertmessage:]
donghai

donghai 回答了问题 • 2017-03-31 18:10 • 1 个回复 不感兴趣

ios 环信最新版本 打包上线时候报错

赞同来自:

环信动态库分离上架:http://www.jianshu.com/p/f058b25163b8  看下这里
环信动态库分离上架:http://www.jianshu.com/p/f058b25163b8  看下这里
zl

zl 回答了问题 • 2017-04-20 15:21 • 2 个回复 不感兴趣

EMConversation中的ext保存后读取不出来

赞同来自:

具体操作代码贴一下。
具体操作代码贴一下。
wangyuzhang

wangyuzhang 回答了问题 • 2017-08-30 21:09 • 1 个回复 不感兴趣

环信 手动添加消息实现逻辑?

赞同来自:

发送消息的入口,需要根据你们的业务来决定,在哪里触发此消息的发送。
发送的消息创建时,EMMessage初始化参数 conversationId和to传入接收方的id即可,然后通过chatManager的接口来发送创建好的消息
- (void)sendMess... 显示全部 »
发送消息的入口,需要根据你们的业务来决定,在哪里触发此消息的发送。
发送的消息创建时,EMMessage初始化参数 conversationId和to传入接收方的id即可,然后通过chatManager的接口来发送创建好的消息
- (void)sendMessage:(EMMessage *)aMessage
           progress:(void (^)(int progress))aProgressBlock
         completion:(void (^)(EMMessage *message, EMError *error))aCompletionBlock;
0
回复

【环信官方外包一个项目】利用环信IM实现一个在线白板 环信

回复

fat1 发起了问题 • 1 人关注 • 84 次浏览 • 2017-09-18 12:21 • 来自相关话题

0
评论

环信公开课16期回放|环信智能鉴黄:用深度学习实现99%精准度的鉴黄服务 环信 环信公开课 环信智能鉴黄 鉴黄

beyond 发表了文章 • 136 次浏览 • 2017-09-04 17:01 • 来自相关话题

   8月31日,环信公开课以“智能鉴黄”为主题,为大家普及了内容审查工作的前世今生,以及环信智能鉴黄算法、技术实现和行业解决方案。
 
关于环信智能鉴黄:
   随着移动互联网的飞速发展和信息量的猛增,大量的色情、赌博、暴力等不良信息图片夹杂在信息流中,严重影响着整个互联网的体验和健康发展。各大互联网公司为了保持自己所提供的服务不触犯国家法律和法规,使得鉴黄工作变得尤为重要。环信智能鉴黄服务基于深度学习的智能鉴黄算法,识别精准度高达99%,可以帮助企业完成95%的图片内容审查工作,节省90%的时间及人工成本。
环信公开课第16期分享内容:


















































环信公开课第16期视频回放:





参加环信公开课还有大礼相送:
恭喜手机尾号8985的leoNN同学成为本期公开课幸运观众,获得环信定制版瑞士军团双肩背包。

环信智能鉴黄免费体验请联系环信小助手,微信huanxin-hh 查看全部
   8月31日,环信公开课以“智能鉴黄”为主题,为大家普及了内容审查工作的前世今生,以及环信智能鉴黄算法、技术实现和行业解决方案。
 
关于环信智能鉴黄:

   随着移动互联网的飞速发展和信息量的猛增,大量的色情、赌博、暴力等不良信息图片夹杂在信息流中,严重影响着整个互联网的体验和健康发展。各大互联网公司为了保持自己所提供的服务不触犯国家法律和法规,使得鉴黄工作变得尤为重要。环信智能鉴黄服务基于深度学习的智能鉴黄算法,识别精准度高达99%,可以帮助企业完成95%的图片内容审查工作,节省90%的时间及人工成本。


环信公开课第16期分享内容:
001.png


002.png


003.png


004.png


005.png


006.png


007.png


008.png


009.png


010.png


环信公开课第16期视频回放:






参加环信公开课还有大礼相送:
恭喜手机尾号8985的leoNN同学成为本期公开课幸运观众,获得环信定制版瑞士军团双肩背包。

环信智能鉴黄免费体验请联系环信小助手,微信huanxin-hh
3a36009c9eb571b9a3a2797f17c4ae25.gif
0
评论

【开源项目】一个基于环信IM开发的开源的私密社交APP-Baby 即时通信 Baby

seven。 发表了文章 • 333 次浏览 • 2017-08-31 14:49 • 来自相关话题

Baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。先上效果图




开屏页的登录和注册




编辑个人信息




相册页面




首页的Moment
版本更新:
 
version 1.6
加入Tinker 热修复更新部分依赖修复大量细节问题,加入部分注释

version 1.5
bug fixupdate sth

version 1.4
增加长按删除功能优化Rxbus订阅加载数据外国友人优化的一些细节等等

version 1.3
增加了评论功能优化了相册加载修复了一些内存泄漏等等

version 1.2
修复了一些Bug把登陆注册事件换了个Zip操作符更符合流的思想

version 1.1
修复了主页背景无法切换的问题修复了聊天推送的问题修了语音视频的问题做了一些细节修改
Download



安装包下载体验
 
github地址
github源码
 Development Environment & Library
 
MVP

这个项目是基于MVP框架写的(大体上,聊天那块直接用环信的了),大部分Base类参考FastAndroid里边的基类,参考这个很快就能布好基本的MVP架构。本来是有考虑过MVVM后来想想还是先学习一下MVP吧,看过几个MVVM项目感觉还是挺好用的,不过还是BETA版不知道有没有什么坑。

Material Design

早就手痒想体验一把V7包里边的各种控件了,特喜欢coordinatorlayout和collapsingtoolbarlayout的互动让Tollbar隐藏又现的感觉,但是看起来好看还是要点代价的,在这里捣鼓了不少时间,尤其是collapsingtoolbarlayout的Expanded固定让我Google了好久,因为用英文搜索可能我表达的不太好,最后竟然是一句nestedScroll(false)就可以了。。。。 还是感谢Stackoveflow里边的大腿吧。

Dagger2

依赖注入Dagger2,也是我早就想用的一个框架了,理由是各种配合Mvp十分方便和好用,渐渐也能体会到一次注入到处可用的快感。不过一个新技术真的学习成本,国内没什么中文文档介绍,看国外的看的云里雾里。原理看的明白,用起来好像不太知道如何使用,尤其是在@inject之后对象,也可以在别的地方Inject,原本是被Inject方后来也成了Module提供方。虽然到最后原理还不是特别明白这里,但还是不阻碍用起来的快感。

Realm

一开始被新技术吸引到的是不会放过任何新东西的包括Realm,不过进了坑不代表这个坑可以跳阿。由于我这次用到了leacncloud,Realm感觉会和LeanCloud的子类化冲突让你只能选择其中之一,不过这个也算了,Leancloud提供了类似Map的Put方法也可以接受就是麻烦了点。但是被坑到的地方是Realm所谓的自动数据同步竟然是一改就是改真实的数据,并不是数据的拷贝。。。感觉和我使用到要缓存的数据有点冲突,因为这个Moment里边的项是有点赞的,点赞要修改当前Recycleview的数据(修改数据要开事务)。修改数据后会出现一些很奇怪的现象,不在Recycleview当前Item会跳到当前Item,点赞的动画也会消失。。。真的是想破脑袋也解决不了,就直接跳坑了。最后感觉这个Realm在保存不跟服务器需要同步的数据会好点。

LeanCloud

用LeanCloud是因为在知乎太多吹它的人了而且它的确在BAAS这方面功能比较多(后来才发现即时通信没有语音和视频),所以就尝试使用了,SDK整体来说是不错的都挺好用的,满足了我对存储方面的要求。不过就在我开发的这几天,貌似稳定性没有想象中那么好,好几次上传个头像都会SocketTimeOut,查询也会有点慢,不过还好都在接受范围内(不过要是到了收费的标准我就接受不了)。

环信

baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。

Rxjava、RxAndroid

Rxjava我从第一眼看到了就喜欢上了(个人特喜欢那种通过.设置完成的感觉),接触也有几个月,一开始就和Retrofit 、Okhttp一起使用。学习成本还是要有的阿,看了不知道多少篇关于Rxjava使用的文章和例子,对里边的操作符也仅仅停留在那几个最常用的,其他一大堆好多都没用过,看来还用得不够。

Glide

Glide也是一个后来居上让我喜欢的图片加载库,一开始我喜欢picasso 是觉得轻巧而且好用有保证(主要是我偶像Jake Wharton主导,有加成),慢慢觉得Picasso对内存没有Glide来的友好,Glide在加载速度方面也领先,虽然整个库代码量是Picasso的几倍,但是比起重要的内存和用户体验来说还是Glide的领先一筹。

Butterknife、Ucrop等等

当让还有其他的一些润色的轮子啦,不过不是那么重要就不一一感谢啦。

Thanks
感谢Github、LeanCloud、环信、还有造那么多轮子给我们用的Square FaceBook Google的大大们。Thanks for improving my code m-ezzat.

Contacts
Email:379489343zhi@gmail.comQQ:379489343
 Copyright 2016 Roger ou

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. 查看全部
   Baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。
先上效果图

003.png

开屏页的登录和注册


001.png

编辑个人信息


002.png

相册页面


004.png

首页的Moment


版本更新:
 
version 1.6
  1. 加入Tinker 热修复
  2. 更新部分依赖
  3. 修复大量细节问题,加入部分注释


version 1.5
  1. bug fix
  2. update sth


version 1.4
  1. 增加长按删除功能
  2. 优化Rxbus订阅加载数据
  3. 外国友人优化的一些细节
  4. 等等


version 1.3
  1. 增加了评论功能
  2. 优化了相册加载
  3. 修复了一些内存泄漏
  4. 等等


version 1.2
  1. 修复了一些Bug
  2. 把登陆注册事件换了个Zip操作符更符合流的思想


version 1.1
  1. 修复了主页背景无法切换的问题
  2. 修复了聊天推送的问题
  3. 修了语音视频的问题
  4. 做了一些细节修改

Download

 
github地址

 Development Environment & Library
 
MVP

这个项目是基于MVP框架写的(大体上,聊天那块直接用环信的了),大部分Base类参考FastAndroid里边的基类,参考这个很快就能布好基本的MVP架构。本来是有考虑过MVVM后来想想还是先学习一下MVP吧,看过几个MVVM项目感觉还是挺好用的,不过还是BETA版不知道有没有什么坑。

Material Design

早就手痒想体验一把V7包里边的各种控件了,特喜欢coordinatorlayout和collapsingtoolbarlayout的互动让Tollbar隐藏又现的感觉,但是看起来好看还是要点代价的,在这里捣鼓了不少时间,尤其是collapsingtoolbarlayout的Expanded固定让我Google了好久,因为用英文搜索可能我表达的不太好,最后竟然是一句nestedScroll(false)就可以了。。。。 还是感谢Stackoveflow里边的大腿吧。

Dagger2

依赖注入Dagger2,也是我早就想用的一个框架了,理由是各种配合Mvp十分方便和好用,渐渐也能体会到一次注入到处可用的快感。不过一个新技术真的学习成本,国内没什么中文文档介绍,看国外的看的云里雾里。原理看的明白,用起来好像不太知道如何使用,尤其是在@inject之后对象,也可以在别的地方Inject,原本是被Inject方后来也成了Module提供方。虽然到最后原理还不是特别明白这里,但还是不阻碍用起来的快感。

Realm

一开始被新技术吸引到的是不会放过任何新东西的包括Realm,不过进了坑不代表这个坑可以跳阿。由于我这次用到了leacncloud,Realm感觉会和LeanCloud的子类化冲突让你只能选择其中之一,不过这个也算了,Leancloud提供了类似Map的Put方法也可以接受就是麻烦了点。但是被坑到的地方是Realm所谓的自动数据同步竟然是一改就是改真实的数据,并不是数据的拷贝。。。感觉和我使用到要缓存的数据有点冲突,因为这个Moment里边的项是有点赞的,点赞要修改当前Recycleview的数据(修改数据要开事务)。修改数据后会出现一些很奇怪的现象,不在Recycleview当前Item会跳到当前Item,点赞的动画也会消失。。。真的是想破脑袋也解决不了,就直接跳坑了。最后感觉这个Realm在保存不跟服务器需要同步的数据会好点。

LeanCloud

用LeanCloud是因为在知乎太多吹它的人了而且它的确在BAAS这方面功能比较多(后来才发现即时通信没有语音和视频),所以就尝试使用了,SDK整体来说是不错的都挺好用的,满足了我对存储方面的要求。不过就在我开发的这几天,貌似稳定性没有想象中那么好,好几次上传个头像都会SocketTimeOut,查询也会有点慢,不过还好都在接受范围内(不过要是到了收费的标准我就接受不了)。

环信

baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。

Rxjava、RxAndroid

Rxjava我从第一眼看到了就喜欢上了(个人特喜欢那种通过.设置完成的感觉),接触也有几个月,一开始就和Retrofit 、Okhttp一起使用。学习成本还是要有的阿,看了不知道多少篇关于Rxjava使用的文章和例子,对里边的操作符也仅仅停留在那几个最常用的,其他一大堆好多都没用过,看来还用得不够。

Glide

Glide也是一个后来居上让我喜欢的图片加载库,一开始我喜欢picasso 是觉得轻巧而且好用有保证(主要是我偶像Jake Wharton主导,有加成),慢慢觉得Picasso对内存没有Glide来的友好,Glide在加载速度方面也领先,虽然整个库代码量是Picasso的几倍,但是比起重要的内存和用户体验来说还是Glide的领先一筹。

Butterknife、Ucrop等等

当让还有其他的一些润色的轮子啦,不过不是那么重要就不一一感谢啦。

Thanks
  • 感谢Github、LeanCloud、环信、还有造那么多轮子给我们用的Square FaceBook Google的大大们。
  • Thanks for improving my code m-ezzat.


Contacts

 
Copyright 2016 Roger ou

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
6
评论

【新手快速入门】集成环信常见问题+解决方案汇总 常见问题

dujiepeng 发表了文章 • 5451 次浏览 • 2017-05-22 15:51 • 来自相关话题

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题环信推送的一些常见问题实现名片|红包|话题聊天室等自定义cell
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天java.lang.UnsatisfiedLinkError: 的问题android 端 app 后台被杀死收不到消息的解决方案
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
客服集成
IM-SDK和客服SDK并存开发指南—Android篇IM-SDK和客服SDK并存开发指南—iOS篇
 
开源项目
Android简版demoios简版demo凡信2.0:超仿微信的开源项目 凡信3.0:携直播和红包而来高仿微信:Github 3,515 Star方圆十里:环信编程大赛冠军项目泛聊:定一个小目标写一个QQSlack聊天机器人:一天时间做一个聊天机器人TV视频通话:在电视上视频通话视频通话:Android手机视频通话酷信:ios高仿微信公众号助手:与订阅用户聊天沟通
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇

 
Android篇

昵称头像篇

 
直播篇
  1. 一言不合你就搞个直播APP

 
客服集成
  1. IM-SDK和客服SDK并存开发指南—Android篇
  2. IM-SDK和客服SDK并存开发指南—iOS篇

 
开源项目

 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
 
2
评论

ios详细的证书创建和上传 iOS ios证书

o孟力o 发表了文章 • 8050 次浏览 • 2015-12-30 11:58 • 来自相关话题

为新手制定一个详细的创建推送证书流程
第一步:生成.certSigningRequest证书
打开钥匙串,在钥匙串导航栏找到,“钥匙串访问”这一项,在钥匙串访问找到“从证书颁发机构请求证书”这一项,如图




点击后进入到如图界面




用户电子邮件地址:写上你的邮件就行
常用名称:随便写
然后选择储存到磁盘,CA电子邮件地址不用写,点继续就生成了.certSigningRequest文件了,记得这个文件的储存位置,一会我们生成推送证书还要用到
第二步:创建推送证书
打开开发者中心,进入生成证书界面(应该找的到吧),进入之后,如果你要生成测试环境的推送证书,选择Certificates-Development,如果你要生成正式环境证书就选择Certificates-Priduction,这里我们以测试为例,点击右上角加号,如图




进入下个界面,如果你要生成测试环境推送证书,就选择红色部分,你要生成正式环境证书,就选择绿色部分,如图




点Continue,进入下个界面,让选择app id,提醒一下,选择app id要和你工程的Bundle Identifier对应,新手应该找的到Bundle Identifier吧,找不到看下面的图,我截了张图




选择完app id ,一直点击Continue,进入如图界面




让选择文件,还记得我们刚开始用钥匙串生成的.certSigningRequest在哪放着吧,就选择它就对了,选择完之后,接着往下面走,把证书下载下来之后,双击就放到钥匙串里面了,这时候推送证书就生成完了
第三步:生成p12文件
我们最终放到环信后台的证书是p12证书,打开钥匙串,找到我们刚才生成的证书,右键找到导出选项,如图




点击导出“Apple Development.....”之后进入下个界面,如图:




红色圈着的部分,储存为:里面要写上证书的名字,名字随便起,不过要记住这个名字,一会在环信后台上传证书还要用到这个名字,名字填完之后,点击储存,储存的位置要记住,一会还要用这个文件,这个文件就是我们要上传到环信的后台的p12文件,(储存的时候会让你输入密码,这个密码你要记住,一会上传到环信后台证书的时候要用到这个密码),这时候p12文件就制作完成了。
第四步:把p12文件上传到环信后台
打开我们的环信后台,找到 推送证书-ios,下面有上传证书的地方,如图




证书名称:就是我们刚才生成p12的名称,我的叫zhuma_test
证书:就是我们刚才生成的p12证书,找到传上去
证书密码:生成p12证书的时候输入的密码(不是瞎输的吧,能记得吗,记不得重新导p12文件吧)
证书类型:我生成的是开发环境的证书,就勾选开发环境
填完之后就可以上传了,上传成功,就ok了 查看全部


为新手制定一个详细的创建推送证书流程
第一步:生成.certSigningRequest证书
打开钥匙串,在钥匙串导航栏找到,“钥匙串访问”这一项,在钥匙串访问找到“从证书颁发机构请求证书”这一项,如图
p1.png

点击后进入到如图界面
p2.png

用户电子邮件地址:写上你的邮件就行
常用名称:随便写
然后选择储存到磁盘,CA电子邮件地址不用写,点继续就生成了.certSigningRequest文件了,记得这个文件的储存位置,一会我们生成推送证书还要用到
第二步:创建推送证书
打开开发者中心,进入生成证书界面(应该找的到吧),进入之后,如果你要生成测试环境的推送证书,选择Certificates-Development,如果你要生成正式环境证书就选择Certificates-Priduction,这里我们以测试为例,点击右上角加号,如图
p3.png

进入下个界面,如果你要生成测试环境推送证书,就选择红色部分,你要生成正式环境证书,就选择绿色部分,如图
p4.png

点Continue,进入下个界面,让选择app id,提醒一下,选择app id要和你工程的Bundle Identifier对应,新手应该找的到Bundle Identifier吧,找不到看下面的图,我截了张图
p10.png

选择完app id ,一直点击Continue,进入如图界面
p5.png

让选择文件,还记得我们刚开始用钥匙串生成的.certSigningRequest在哪放着吧,就选择它就对了,选择完之后,接着往下面走,把证书下载下来之后,双击就放到钥匙串里面了,这时候推送证书就生成完了
第三步:生成p12文件
我们最终放到环信后台的证书是p12证书,打开钥匙串,找到我们刚才生成的证书,右键找到导出选项,如图
p6.png

点击导出“Apple Development.....”之后进入下个界面,如图:
p7.png

红色圈着的部分,储存为:里面要写上证书的名字,名字随便起,不过要记住这个名字,一会在环信后台上传证书还要用到这个名字,名字填完之后,点击储存,储存的位置要记住,一会还要用这个文件,这个文件就是我们要上传到环信的后台的p12文件,(储存的时候会让你输入密码,这个密码你要记住,一会上传到环信后台证书的时候要用到这个密码),这时候p12文件就制作完成了。
第四步:把p12文件上传到环信后台
打开我们的环信后台,找到 推送证书-ios,下面有上传证书的地方,如图
p8.png

证书名称:就是我们刚才生成p12的名称,我的叫zhuma_test
证书:就是我们刚才生成的p12证书,找到传上去
证书密码:生成p12证书的时候输入的密码(不是瞎输的吧,能记得吗,记不得重新导p12文件吧)
证书类型:我生成的是开发环境的证书,就勾选开发环境
填完之后就可以上传了,上传成功,就ok了
0
回复

【环信官方外包一个项目】利用环信IM实现一个在线白板 环信

回复

fat1 发起了问题 • 1 人关注 • 84 次浏览 • 2017-09-18 12:21 • 来自相关话题

0
评论

环信公开课16期回放|环信智能鉴黄:用深度学习实现99%精准度的鉴黄服务 环信 环信公开课 环信智能鉴黄 鉴黄

beyond 发表了文章 • 136 次浏览 • 2017-09-04 17:01 • 来自相关话题

   8月31日,环信公开课以“智能鉴黄”为主题,为大家普及了内容审查工作的前世今生,以及环信智能鉴黄算法、技术实现和行业解决方案。
 
关于环信智能鉴黄:
   随着移动互联网的飞速发展和信息量的猛增,大量的色情、赌博、暴力等不良信息图片夹杂在信息流中,严重影响着整个互联网的体验和健康发展。各大互联网公司为了保持自己所提供的服务不触犯国家法律和法规,使得鉴黄工作变得尤为重要。环信智能鉴黄服务基于深度学习的智能鉴黄算法,识别精准度高达99%,可以帮助企业完成95%的图片内容审查工作,节省90%的时间及人工成本。
环信公开课第16期分享内容:


















































环信公开课第16期视频回放:





参加环信公开课还有大礼相送:
恭喜手机尾号8985的leoNN同学成为本期公开课幸运观众,获得环信定制版瑞士军团双肩背包。

环信智能鉴黄免费体验请联系环信小助手,微信huanxin-hh 查看全部
   8月31日,环信公开课以“智能鉴黄”为主题,为大家普及了内容审查工作的前世今生,以及环信智能鉴黄算法、技术实现和行业解决方案。
 
关于环信智能鉴黄:

   随着移动互联网的飞速发展和信息量的猛增,大量的色情、赌博、暴力等不良信息图片夹杂在信息流中,严重影响着整个互联网的体验和健康发展。各大互联网公司为了保持自己所提供的服务不触犯国家法律和法规,使得鉴黄工作变得尤为重要。环信智能鉴黄服务基于深度学习的智能鉴黄算法,识别精准度高达99%,可以帮助企业完成95%的图片内容审查工作,节省90%的时间及人工成本。


环信公开课第16期分享内容:
001.png


002.png


003.png


004.png


005.png


006.png


007.png


008.png


009.png


010.png


环信公开课第16期视频回放:






参加环信公开课还有大礼相送:
恭喜手机尾号8985的leoNN同学成为本期公开课幸运观众,获得环信定制版瑞士军团双肩背包。

环信智能鉴黄免费体验请联系环信小助手,微信huanxin-hh
3a36009c9eb571b9a3a2797f17c4ae25.gif
0
评论

【开源项目】一个基于环信IM开发的开源的私密社交APP-Baby 即时通信 Baby

seven。 发表了文章 • 333 次浏览 • 2017-08-31 14:49 • 来自相关话题

Baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。先上效果图




开屏页的登录和注册




编辑个人信息




相册页面




首页的Moment
版本更新:
 
version 1.6
加入Tinker 热修复更新部分依赖修复大量细节问题,加入部分注释

version 1.5
bug fixupdate sth

version 1.4
增加长按删除功能优化Rxbus订阅加载数据外国友人优化的一些细节等等

version 1.3
增加了评论功能优化了相册加载修复了一些内存泄漏等等

version 1.2
修复了一些Bug把登陆注册事件换了个Zip操作符更符合流的思想

version 1.1
修复了主页背景无法切换的问题修复了聊天推送的问题修了语音视频的问题做了一些细节修改
Download



安装包下载体验
 
github地址
github源码
 Development Environment & Library
 
MVP

这个项目是基于MVP框架写的(大体上,聊天那块直接用环信的了),大部分Base类参考FastAndroid里边的基类,参考这个很快就能布好基本的MVP架构。本来是有考虑过MVVM后来想想还是先学习一下MVP吧,看过几个MVVM项目感觉还是挺好用的,不过还是BETA版不知道有没有什么坑。

Material Design

早就手痒想体验一把V7包里边的各种控件了,特喜欢coordinatorlayout和collapsingtoolbarlayout的互动让Tollbar隐藏又现的感觉,但是看起来好看还是要点代价的,在这里捣鼓了不少时间,尤其是collapsingtoolbarlayout的Expanded固定让我Google了好久,因为用英文搜索可能我表达的不太好,最后竟然是一句nestedScroll(false)就可以了。。。。 还是感谢Stackoveflow里边的大腿吧。

Dagger2

依赖注入Dagger2,也是我早就想用的一个框架了,理由是各种配合Mvp十分方便和好用,渐渐也能体会到一次注入到处可用的快感。不过一个新技术真的学习成本,国内没什么中文文档介绍,看国外的看的云里雾里。原理看的明白,用起来好像不太知道如何使用,尤其是在@inject之后对象,也可以在别的地方Inject,原本是被Inject方后来也成了Module提供方。虽然到最后原理还不是特别明白这里,但还是不阻碍用起来的快感。

Realm

一开始被新技术吸引到的是不会放过任何新东西的包括Realm,不过进了坑不代表这个坑可以跳阿。由于我这次用到了leacncloud,Realm感觉会和LeanCloud的子类化冲突让你只能选择其中之一,不过这个也算了,Leancloud提供了类似Map的Put方法也可以接受就是麻烦了点。但是被坑到的地方是Realm所谓的自动数据同步竟然是一改就是改真实的数据,并不是数据的拷贝。。。感觉和我使用到要缓存的数据有点冲突,因为这个Moment里边的项是有点赞的,点赞要修改当前Recycleview的数据(修改数据要开事务)。修改数据后会出现一些很奇怪的现象,不在Recycleview当前Item会跳到当前Item,点赞的动画也会消失。。。真的是想破脑袋也解决不了,就直接跳坑了。最后感觉这个Realm在保存不跟服务器需要同步的数据会好点。

LeanCloud

用LeanCloud是因为在知乎太多吹它的人了而且它的确在BAAS这方面功能比较多(后来才发现即时通信没有语音和视频),所以就尝试使用了,SDK整体来说是不错的都挺好用的,满足了我对存储方面的要求。不过就在我开发的这几天,貌似稳定性没有想象中那么好,好几次上传个头像都会SocketTimeOut,查询也会有点慢,不过还好都在接受范围内(不过要是到了收费的标准我就接受不了)。

环信

baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。

Rxjava、RxAndroid

Rxjava我从第一眼看到了就喜欢上了(个人特喜欢那种通过.设置完成的感觉),接触也有几个月,一开始就和Retrofit 、Okhttp一起使用。学习成本还是要有的阿,看了不知道多少篇关于Rxjava使用的文章和例子,对里边的操作符也仅仅停留在那几个最常用的,其他一大堆好多都没用过,看来还用得不够。

Glide

Glide也是一个后来居上让我喜欢的图片加载库,一开始我喜欢picasso 是觉得轻巧而且好用有保证(主要是我偶像Jake Wharton主导,有加成),慢慢觉得Picasso对内存没有Glide来的友好,Glide在加载速度方面也领先,虽然整个库代码量是Picasso的几倍,但是比起重要的内存和用户体验来说还是Glide的领先一筹。

Butterknife、Ucrop等等

当让还有其他的一些润色的轮子啦,不过不是那么重要就不一一感谢啦。

Thanks
感谢Github、LeanCloud、环信、还有造那么多轮子给我们用的Square FaceBook Google的大大们。Thanks for improving my code m-ezzat.

Contacts
Email:379489343zhi@gmail.comQQ:379489343
 Copyright 2016 Roger ou

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. 查看全部
   Baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。
先上效果图

003.png

开屏页的登录和注册


001.png

编辑个人信息


002.png

相册页面


004.png

首页的Moment


版本更新:
 
version 1.6
  1. 加入Tinker 热修复
  2. 更新部分依赖
  3. 修复大量细节问题,加入部分注释


version 1.5
  1. bug fix
  2. update sth


version 1.4
  1. 增加长按删除功能
  2. 优化Rxbus订阅加载数据
  3. 外国友人优化的一些细节
  4. 等等


version 1.3
  1. 增加了评论功能
  2. 优化了相册加载
  3. 修复了一些内存泄漏
  4. 等等


version 1.2
  1. 修复了一些Bug
  2. 把登陆注册事件换了个Zip操作符更符合流的思想


version 1.1
  1. 修复了主页背景无法切换的问题
  2. 修复了聊天推送的问题
  3. 修了语音视频的问题
  4. 做了一些细节修改

Download

 
github地址

 Development Environment & Library
 
MVP

这个项目是基于MVP框架写的(大体上,聊天那块直接用环信的了),大部分Base类参考FastAndroid里边的基类,参考这个很快就能布好基本的MVP架构。本来是有考虑过MVVM后来想想还是先学习一下MVP吧,看过几个MVVM项目感觉还是挺好用的,不过还是BETA版不知道有没有什么坑。

Material Design

早就手痒想体验一把V7包里边的各种控件了,特喜欢coordinatorlayout和collapsingtoolbarlayout的互动让Tollbar隐藏又现的感觉,但是看起来好看还是要点代价的,在这里捣鼓了不少时间,尤其是collapsingtoolbarlayout的Expanded固定让我Google了好久,因为用英文搜索可能我表达的不太好,最后竟然是一句nestedScroll(false)就可以了。。。。 还是感谢Stackoveflow里边的大腿吧。

Dagger2

依赖注入Dagger2,也是我早就想用的一个框架了,理由是各种配合Mvp十分方便和好用,渐渐也能体会到一次注入到处可用的快感。不过一个新技术真的学习成本,国内没什么中文文档介绍,看国外的看的云里雾里。原理看的明白,用起来好像不太知道如何使用,尤其是在@inject之后对象,也可以在别的地方Inject,原本是被Inject方后来也成了Module提供方。虽然到最后原理还不是特别明白这里,但还是不阻碍用起来的快感。

Realm

一开始被新技术吸引到的是不会放过任何新东西的包括Realm,不过进了坑不代表这个坑可以跳阿。由于我这次用到了leacncloud,Realm感觉会和LeanCloud的子类化冲突让你只能选择其中之一,不过这个也算了,Leancloud提供了类似Map的Put方法也可以接受就是麻烦了点。但是被坑到的地方是Realm所谓的自动数据同步竟然是一改就是改真实的数据,并不是数据的拷贝。。。感觉和我使用到要缓存的数据有点冲突,因为这个Moment里边的项是有点赞的,点赞要修改当前Recycleview的数据(修改数据要开事务)。修改数据后会出现一些很奇怪的现象,不在Recycleview当前Item会跳到当前Item,点赞的动画也会消失。。。真的是想破脑袋也解决不了,就直接跳坑了。最后感觉这个Realm在保存不跟服务器需要同步的数据会好点。

LeanCloud

用LeanCloud是因为在知乎太多吹它的人了而且它的确在BAAS这方面功能比较多(后来才发现即时通信没有语音和视频),所以就尝试使用了,SDK整体来说是不错的都挺好用的,满足了我对存储方面的要求。不过就在我开发的这几天,貌似稳定性没有想象中那么好,好几次上传个头像都会SocketTimeOut,查询也会有点慢,不过还好都在接受范围内(不过要是到了收费的标准我就接受不了)。

环信

baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。

Rxjava、RxAndroid

Rxjava我从第一眼看到了就喜欢上了(个人特喜欢那种通过.设置完成的感觉),接触也有几个月,一开始就和Retrofit 、Okhttp一起使用。学习成本还是要有的阿,看了不知道多少篇关于Rxjava使用的文章和例子,对里边的操作符也仅仅停留在那几个最常用的,其他一大堆好多都没用过,看来还用得不够。

Glide

Glide也是一个后来居上让我喜欢的图片加载库,一开始我喜欢picasso 是觉得轻巧而且好用有保证(主要是我偶像Jake Wharton主导,有加成),慢慢觉得Picasso对内存没有Glide来的友好,Glide在加载速度方面也领先,虽然整个库代码量是Picasso的几倍,但是比起重要的内存和用户体验来说还是Glide的领先一筹。

Butterknife、Ucrop等等

当让还有其他的一些润色的轮子啦,不过不是那么重要就不一一感谢啦。

Thanks
  • 感谢Github、LeanCloud、环信、还有造那么多轮子给我们用的Square FaceBook Google的大大们。
  • Thanks for improving my code m-ezzat.


Contacts

 
Copyright 2016 Roger ou

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
6
评论

【新手快速入门】集成环信常见问题+解决方案汇总 常见问题

dujiepeng 发表了文章 • 5451 次浏览 • 2017-05-22 15:51 • 来自相关话题

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题环信推送的一些常见问题实现名片|红包|话题聊天室等自定义cell
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天java.lang.UnsatisfiedLinkError: 的问题android 端 app 后台被杀死收不到消息的解决方案
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
客服集成
IM-SDK和客服SDK并存开发指南—Android篇IM-SDK和客服SDK并存开发指南—iOS篇
 
开源项目
Android简版demoios简版demo凡信2.0:超仿微信的开源项目 凡信3.0:携直播和红包而来高仿微信:Github 3,515 Star方圆十里:环信编程大赛冠军项目泛聊:定一个小目标写一个QQSlack聊天机器人:一天时间做一个聊天机器人TV视频通话:在电视上视频通话视频通话:Android手机视频通话酷信:ios高仿微信公众号助手:与订阅用户聊天沟通
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇

 
Android篇

昵称头像篇

 
直播篇
  1. 一言不合你就搞个直播APP

 
客服集成
  1. IM-SDK和客服SDK并存开发指南—Android篇
  2. IM-SDK和客服SDK并存开发指南—iOS篇

 
开源项目

 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
 
1
回复

环信可以实现推送服务吗? iOS

回复

baoshu 回复了问题 • 2 人关注 • 102 次浏览 • 2017-09-15 09:55 • 来自相关话题

0
回复

IOS 获取当前用户所有群组数据无效 iOS 环信_iOS

回复

尾声动听 发起了问题 • 1 人关注 • 65 次浏览 • 2017-09-12 14:21 • 来自相关话题

0
回复

收到消息的代理回调不会执行 环信 iOS

回复

Aries 发起了问题 • 1 人关注 • 66 次浏览 • 2017-09-12 10:28 • 来自相关话题

0
回复

环信带推送吗? iOS 推送

回复

爆肝 发起了问题 • 1 人关注 • 71 次浏览 • 2017-09-11 17:27 • 来自相关话题

1
最佳

环信 手动添加消息实现逻辑? iOS

回复

wangyuzhang 回复了问题 • 2 人关注 • 150 次浏览 • 2017-08-30 21:09 • 来自相关话题

1
回复

环信3.3.4消息撤回的问题 iOS 环信_iOS

回复

wangyuzhang 回复了问题 • 2 人关注 • 148 次浏览 • 2017-08-22 00:00 • 来自相关话题

0
回复

[NSBundle initWithURL:]: nil URL argument' 环信_iOS ios 3.2.3 iOS 环信 ios 导入sdk问题 iOS

回复

yong123 发起了问题 • 1 人关注 • 143 次浏览 • 2017-08-16 16:51 • 来自相关话题

2
回复

iOS _rr_open in libHyphenateVideoRecorderPlugin.a(rav_record.o) iOS 环信 iOS

回复

yong123 回复了问题 • 2 人关注 • 149 次浏览 • 2017-08-16 16:47 • 来自相关话题

1
回复

'EMSDKFull.h' file not found iOS 环信

回复

wangyuzhang 回复了问题 • 2 人关注 • 159 次浏览 • 2017-08-08 19:46 • 来自相关话题

2
回复

ios集成环信移动客服,点击发送emoji表情,只显示字符串,不显示emoji,求解 环信移动客服 iOS

回复

pakrs12341 回复了问题 • 3 人关注 • 195 次浏览 • 2017-08-08 09:54 • 来自相关话题

1
回复

发送录音失败 发送录音失败 录音 iOS

回复

dujiepeng 回复了问题 • 2 人关注 • 210 次浏览 • 2017-08-04 19:41 • 来自相关话题

1
回复

iOS 反复接受同一条透传 iOS 透传 透传消息

回复

wangyuzhang 回复了问题 • 2 人关注 • 166 次浏览 • 2017-07-31 20:13 • 来自相关话题

2
回复

发送语音一直失败 语音发送问题 iOS

回复

dujiepeng 回复了问题 • 3 人关注 • 183 次浏览 • 2017-07-28 21:43 • 来自相关话题

1
回复

ios编译通过 运行直接crash iOS

回复

wangyuzhang 回复了问题 • 2 人关注 • 201 次浏览 • 2017-07-20 19:18 • 来自相关话题

6
最佳

会话列表接受消息时没反应 要下拉刷新才出来 怎么解决 消息接受 iOS 环信_iOS

回复

ljQwe 回复了问题 • 5 人关注 • 2171 次浏览 • 2017-07-12 16:16 • 来自相关话题

1
回复

请教环信3.2.1版本升级到3.3.2版本指南。 iOS 环信_iOS

回复

木云落 回复了问题 • 2 人关注 • 238 次浏览 • 2017-07-06 14:23 • 来自相关话题

25
最佳

求一份 iOS 3.x 群聊demo 环信_iOS 群聊demo 群聊 iOS

回复

撑住丶为心中狂野的目标 回复了问题 • 25 人关注 • 2289 次浏览 • 2017-07-04 22:00 • 来自相关话题

2
回复

ios 导入easeui 报错 iOS iOS 环信

回复

KevinGong 回复了问题 • 3 人关注 • 239 次浏览 • 2017-06-22 19:04 • 来自相关话题

1
回复

iOS pod导入环信SDK iOS pod SDK

回复

lifei9241 回复了问题 • 2 人关注 • 254 次浏览 • 2017-06-20 16:08 • 来自相关话题

1
回复

iOS 自己给自己发送消息 会重复 iOS iOS 环信

回复

木云落 回复了问题 • 2 人关注 • 229 次浏览 • 2017-06-19 11:00 • 来自相关话题

5
回复

环信cocoapods集成EaseUI 继承时报错 iOS swift EaseUI cocoapod

回复

爱我所爱 回复了问题 • 3 人关注 • 322 次浏览 • 2017-06-15 11:18 • 来自相关话题

1
回复

cocopod集成EaseUI出现300多个警告怎么回事 求解 iOS

回复

木云落 回复了问题 • 2 人关注 • 200 次浏览 • 2017-06-06 17:00 • 来自相关话题

0
回复

【环信官方外包一个项目】利用环信IM实现一个在线白板 环信

回复

fat1 发起了问题 • 1 人关注 • 84 次浏览 • 2017-09-18 12:21 • 来自相关话题

0
评论

环信公开课16期回放|环信智能鉴黄:用深度学习实现99%精准度的鉴黄服务 环信 环信公开课 环信智能鉴黄 鉴黄

beyond 发表了文章 • 136 次浏览 • 2017-09-04 17:01 • 来自相关话题

   8月31日,环信公开课以“智能鉴黄”为主题,为大家普及了内容审查工作的前世今生,以及环信智能鉴黄算法、技术实现和行业解决方案。
 
关于环信智能鉴黄:
   随着移动互联网的飞速发展和信息量的猛增,大量的色情、赌博、暴力等不良信息图片夹杂在信息流中,严重影响着整个互联网的体验和健康发展。各大互联网公司为了保持自己所提供的服务不触犯国家法律和法规,使得鉴黄工作变得尤为重要。环信智能鉴黄服务基于深度学习的智能鉴黄算法,识别精准度高达99%,可以帮助企业完成95%的图片内容审查工作,节省90%的时间及人工成本。
环信公开课第16期分享内容:


















































环信公开课第16期视频回放:





参加环信公开课还有大礼相送:
恭喜手机尾号8985的leoNN同学成为本期公开课幸运观众,获得环信定制版瑞士军团双肩背包。

环信智能鉴黄免费体验请联系环信小助手,微信huanxin-hh 查看全部
   8月31日,环信公开课以“智能鉴黄”为主题,为大家普及了内容审查工作的前世今生,以及环信智能鉴黄算法、技术实现和行业解决方案。
 
关于环信智能鉴黄:

   随着移动互联网的飞速发展和信息量的猛增,大量的色情、赌博、暴力等不良信息图片夹杂在信息流中,严重影响着整个互联网的体验和健康发展。各大互联网公司为了保持自己所提供的服务不触犯国家法律和法规,使得鉴黄工作变得尤为重要。环信智能鉴黄服务基于深度学习的智能鉴黄算法,识别精准度高达99%,可以帮助企业完成95%的图片内容审查工作,节省90%的时间及人工成本。


环信公开课第16期分享内容:
001.png


002.png


003.png


004.png


005.png


006.png


007.png


008.png


009.png


010.png


环信公开课第16期视频回放:






参加环信公开课还有大礼相送:
恭喜手机尾号8985的leoNN同学成为本期公开课幸运观众,获得环信定制版瑞士军团双肩背包。

环信智能鉴黄免费体验请联系环信小助手,微信huanxin-hh
3a36009c9eb571b9a3a2797f17c4ae25.gif
0
评论

【开源项目】一个基于环信IM开发的开源的私密社交APP-Baby 即时通信 Baby

seven。 发表了文章 • 333 次浏览 • 2017-08-31 14:49 • 来自相关话题

Baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。先上效果图




开屏页的登录和注册




编辑个人信息




相册页面




首页的Moment
版本更新:
 
version 1.6
加入Tinker 热修复更新部分依赖修复大量细节问题,加入部分注释

version 1.5
bug fixupdate sth

version 1.4
增加长按删除功能优化Rxbus订阅加载数据外国友人优化的一些细节等等

version 1.3
增加了评论功能优化了相册加载修复了一些内存泄漏等等

version 1.2
修复了一些Bug把登陆注册事件换了个Zip操作符更符合流的思想

version 1.1
修复了主页背景无法切换的问题修复了聊天推送的问题修了语音视频的问题做了一些细节修改
Download



安装包下载体验
 
github地址
github源码
 Development Environment & Library
 
MVP

这个项目是基于MVP框架写的(大体上,聊天那块直接用环信的了),大部分Base类参考FastAndroid里边的基类,参考这个很快就能布好基本的MVP架构。本来是有考虑过MVVM后来想想还是先学习一下MVP吧,看过几个MVVM项目感觉还是挺好用的,不过还是BETA版不知道有没有什么坑。

Material Design

早就手痒想体验一把V7包里边的各种控件了,特喜欢coordinatorlayout和collapsingtoolbarlayout的互动让Tollbar隐藏又现的感觉,但是看起来好看还是要点代价的,在这里捣鼓了不少时间,尤其是collapsingtoolbarlayout的Expanded固定让我Google了好久,因为用英文搜索可能我表达的不太好,最后竟然是一句nestedScroll(false)就可以了。。。。 还是感谢Stackoveflow里边的大腿吧。

Dagger2

依赖注入Dagger2,也是我早就想用的一个框架了,理由是各种配合Mvp十分方便和好用,渐渐也能体会到一次注入到处可用的快感。不过一个新技术真的学习成本,国内没什么中文文档介绍,看国外的看的云里雾里。原理看的明白,用起来好像不太知道如何使用,尤其是在@inject之后对象,也可以在别的地方Inject,原本是被Inject方后来也成了Module提供方。虽然到最后原理还不是特别明白这里,但还是不阻碍用起来的快感。

Realm

一开始被新技术吸引到的是不会放过任何新东西的包括Realm,不过进了坑不代表这个坑可以跳阿。由于我这次用到了leacncloud,Realm感觉会和LeanCloud的子类化冲突让你只能选择其中之一,不过这个也算了,Leancloud提供了类似Map的Put方法也可以接受就是麻烦了点。但是被坑到的地方是Realm所谓的自动数据同步竟然是一改就是改真实的数据,并不是数据的拷贝。。。感觉和我使用到要缓存的数据有点冲突,因为这个Moment里边的项是有点赞的,点赞要修改当前Recycleview的数据(修改数据要开事务)。修改数据后会出现一些很奇怪的现象,不在Recycleview当前Item会跳到当前Item,点赞的动画也会消失。。。真的是想破脑袋也解决不了,就直接跳坑了。最后感觉这个Realm在保存不跟服务器需要同步的数据会好点。

LeanCloud

用LeanCloud是因为在知乎太多吹它的人了而且它的确在BAAS这方面功能比较多(后来才发现即时通信没有语音和视频),所以就尝试使用了,SDK整体来说是不错的都挺好用的,满足了我对存储方面的要求。不过就在我开发的这几天,貌似稳定性没有想象中那么好,好几次上传个头像都会SocketTimeOut,查询也会有点慢,不过还好都在接受范围内(不过要是到了收费的标准我就接受不了)。

环信

baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。

Rxjava、RxAndroid

Rxjava我从第一眼看到了就喜欢上了(个人特喜欢那种通过.设置完成的感觉),接触也有几个月,一开始就和Retrofit 、Okhttp一起使用。学习成本还是要有的阿,看了不知道多少篇关于Rxjava使用的文章和例子,对里边的操作符也仅仅停留在那几个最常用的,其他一大堆好多都没用过,看来还用得不够。

Glide

Glide也是一个后来居上让我喜欢的图片加载库,一开始我喜欢picasso 是觉得轻巧而且好用有保证(主要是我偶像Jake Wharton主导,有加成),慢慢觉得Picasso对内存没有Glide来的友好,Glide在加载速度方面也领先,虽然整个库代码量是Picasso的几倍,但是比起重要的内存和用户体验来说还是Glide的领先一筹。

Butterknife、Ucrop等等

当让还有其他的一些润色的轮子啦,不过不是那么重要就不一一感谢啦。

Thanks
感谢Github、LeanCloud、环信、还有造那么多轮子给我们用的Square FaceBook Google的大大们。Thanks for improving my code m-ezzat.

Contacts
Email:379489343zhi@gmail.comQQ:379489343
 Copyright 2016 Roger ou

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. 查看全部
   Baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。
先上效果图

003.png

开屏页的登录和注册


001.png

编辑个人信息


002.png

相册页面


004.png

首页的Moment


版本更新:
 
version 1.6
  1. 加入Tinker 热修复
  2. 更新部分依赖
  3. 修复大量细节问题,加入部分注释


version 1.5
  1. bug fix
  2. update sth


version 1.4
  1. 增加长按删除功能
  2. 优化Rxbus订阅加载数据
  3. 外国友人优化的一些细节
  4. 等等


version 1.3
  1. 增加了评论功能
  2. 优化了相册加载
  3. 修复了一些内存泄漏
  4. 等等


version 1.2
  1. 修复了一些Bug
  2. 把登陆注册事件换了个Zip操作符更符合流的思想


version 1.1
  1. 修复了主页背景无法切换的问题
  2. 修复了聊天推送的问题
  3. 修了语音视频的问题
  4. 做了一些细节修改

Download

 
github地址

 Development Environment & Library
 
MVP

这个项目是基于MVP框架写的(大体上,聊天那块直接用环信的了),大部分Base类参考FastAndroid里边的基类,参考这个很快就能布好基本的MVP架构。本来是有考虑过MVVM后来想想还是先学习一下MVP吧,看过几个MVVM项目感觉还是挺好用的,不过还是BETA版不知道有没有什么坑。

Material Design

早就手痒想体验一把V7包里边的各种控件了,特喜欢coordinatorlayout和collapsingtoolbarlayout的互动让Tollbar隐藏又现的感觉,但是看起来好看还是要点代价的,在这里捣鼓了不少时间,尤其是collapsingtoolbarlayout的Expanded固定让我Google了好久,因为用英文搜索可能我表达的不太好,最后竟然是一句nestedScroll(false)就可以了。。。。 还是感谢Stackoveflow里边的大腿吧。

Dagger2

依赖注入Dagger2,也是我早就想用的一个框架了,理由是各种配合Mvp十分方便和好用,渐渐也能体会到一次注入到处可用的快感。不过一个新技术真的学习成本,国内没什么中文文档介绍,看国外的看的云里雾里。原理看的明白,用起来好像不太知道如何使用,尤其是在@inject之后对象,也可以在别的地方Inject,原本是被Inject方后来也成了Module提供方。虽然到最后原理还不是特别明白这里,但还是不阻碍用起来的快感。

Realm

一开始被新技术吸引到的是不会放过任何新东西的包括Realm,不过进了坑不代表这个坑可以跳阿。由于我这次用到了leacncloud,Realm感觉会和LeanCloud的子类化冲突让你只能选择其中之一,不过这个也算了,Leancloud提供了类似Map的Put方法也可以接受就是麻烦了点。但是被坑到的地方是Realm所谓的自动数据同步竟然是一改就是改真实的数据,并不是数据的拷贝。。。感觉和我使用到要缓存的数据有点冲突,因为这个Moment里边的项是有点赞的,点赞要修改当前Recycleview的数据(修改数据要开事务)。修改数据后会出现一些很奇怪的现象,不在Recycleview当前Item会跳到当前Item,点赞的动画也会消失。。。真的是想破脑袋也解决不了,就直接跳坑了。最后感觉这个Realm在保存不跟服务器需要同步的数据会好点。

LeanCloud

用LeanCloud是因为在知乎太多吹它的人了而且它的确在BAAS这方面功能比较多(后来才发现即时通信没有语音和视频),所以就尝试使用了,SDK整体来说是不错的都挺好用的,满足了我对存储方面的要求。不过就在我开发的这几天,貌似稳定性没有想象中那么好,好几次上传个头像都会SocketTimeOut,查询也会有点慢,不过还好都在接受范围内(不过要是到了收费的标准我就接受不了)。

环信

baby这个开源项目基于环信IM开发,刚开始选择IM的时候,看到知乎上黑环信的人挺多的,就亲自下载了几家IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。集成这方面环信做的真不错,尤其是有了EaseUi这个包,基本上一天就能集成完毕。项目运行至今没有出现过什么问题,发送消息挺稳定的。

Rxjava、RxAndroid

Rxjava我从第一眼看到了就喜欢上了(个人特喜欢那种通过.设置完成的感觉),接触也有几个月,一开始就和Retrofit 、Okhttp一起使用。学习成本还是要有的阿,看了不知道多少篇关于Rxjava使用的文章和例子,对里边的操作符也仅仅停留在那几个最常用的,其他一大堆好多都没用过,看来还用得不够。

Glide

Glide也是一个后来居上让我喜欢的图片加载库,一开始我喜欢picasso 是觉得轻巧而且好用有保证(主要是我偶像Jake Wharton主导,有加成),慢慢觉得Picasso对内存没有Glide来的友好,Glide在加载速度方面也领先,虽然整个库代码量是Picasso的几倍,但是比起重要的内存和用户体验来说还是Glide的领先一筹。

Butterknife、Ucrop等等

当让还有其他的一些润色的轮子啦,不过不是那么重要就不一一感谢啦。

Thanks
  • 感谢Github、LeanCloud、环信、还有造那么多轮子给我们用的Square FaceBook Google的大大们。
  • Thanks for improving my code m-ezzat.


Contacts

 
Copyright 2016 Roger ou

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
6
评论

【新手快速入门】集成环信常见问题+解决方案汇总 常见问题

dujiepeng 发表了文章 • 5451 次浏览 • 2017-05-22 15:51 • 来自相关话题

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题环信推送的一些常见问题实现名片|红包|话题聊天室等自定义cell
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天java.lang.UnsatisfiedLinkError: 的问题android 端 app 后台被杀死收不到消息的解决方案
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
客服集成
IM-SDK和客服SDK并存开发指南—Android篇IM-SDK和客服SDK并存开发指南—iOS篇
 
开源项目
Android简版demoios简版demo凡信2.0:超仿微信的开源项目 凡信3.0:携直播和红包而来高仿微信:Github 3,515 Star方圆十里:环信编程大赛冠军项目泛聊:定一个小目标写一个QQSlack聊天机器人:一天时间做一个聊天机器人TV视频通话:在电视上视频通话视频通话:Android手机视频通话酷信:ios高仿微信公众号助手:与订阅用户聊天沟通
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇

 
Android篇

昵称头像篇

 
直播篇
  1. 一言不合你就搞个直播APP

 
客服集成
  1. IM-SDK和客服SDK并存开发指南—Android篇
  2. IM-SDK和客服SDK并存开发指南—iOS篇

 
开源项目

 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
 
0
评论

【环信征文】集成环信,并实现消息免打扰 iOS IM 消息免打扰 环信 即时通讯

cokeyer 发表了文章 • 27 次浏览 • 2017-09-14 11:27 • 来自相关话题

现在大多数社交app都有消息免打扰功能,因为环信SDK主要是对即时通讯模块进行封装,因此如果要实现消息免打扰功能则需要开发者自己对此功能逻辑进行处理。
先解释一下,本人项目中对消息免打扰功能的定义:正常情况下,我们收到的每一条聊天消息都会收到小红点、声音、震动的提示。如果对某个好友设置消息免打扰功能,则只提示小红点,声音和震动则不再提示。

下面简述结合环信SDK时,此功能的实现方法。
环信将即时聊天的所有功能分为四大模块进行管理:
//聊天模块:
[EMClient sharedClient].chatManager
//好友模块 :
[EMClient sharedClient].contactManager
//群组模块 :
[EMClient sharedClient].groupManager
//聊天室模块:
[EMClient sharedClient].roomManager消息免打扰的功能借助聊天模块[EMClient sharedClient].chatManager的API就能实现。
一般来说,在app内不管当前在哪个界面,只要收到消息都需要被判断是否需要免打扰,因此可以在appdelegate里写如下代码,app通常都有tabBarController,那么也可以让tabBarController来实现如下代码。
首先让控制器tabBarController遵守代理EMChatManagerDelegate
然后成为代理
[[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];

EMChatManagerDelegate中有一个代理方法
- (void)didReceiveMessages:(NSArray *)aMessages;实现此代理方法
- (void)didReceiveMessages:(NSArray *)aMessages{
[self setupUnreadMessageCount];
EMMessage *message = aMessages[0];
NSString *sendPerson = message.from;
BHNavigatiomController *imNaviCV = self.viewControllers[1];
BHConversationListController *converCV = imNaviCV.viewControllers[0];
[converCV refreshDataSource];//只要收到消息就从服务器拿
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
for (NSString *hx_name in AppEngine.IMDataCent.data_ExcuseFriendsData) {
if ([hx_name isEqualToString:sendPerson]) {
return;
}
}
switch (state) {
case UIApplicationStateActive:
[self playSoundAndVibration];
break;
case UIApplicationStateInactive:
[self playSoundAndVibration];
break;
case UIApplicationStateBackground:
[self showNotificationWithMessage:message];
break;
default:
break;
}
}方法中有一个aMessages参数。这个参数是一个消息组,每一个元素都是EMMessage类型的实例。
因为通常都只有一条信息。因此只需要取出EMMessage *message = aMessages[0];
EMMessage类中有许多属性,因此根据一条信息基本可以获取想知道的所有信息。这里我们只需要知道此消息的发送方即可,也就是发送方的环信idNSString *sendPerson = message.from;下面代码中有一AppEngine.IMDataCent.data_ExcuseFriendsData,这个数组里面装的都是已经被设置消息免打扰的好友的环信id,是请求自己服务器获得的,获取的代码最好在一打开app时,就及时获取到。然后根据对好友免打扰设置的操作,访问后台接口进行增删,并刷新数组与后端保持一致即可。
通过[hx_name isEqualToString:sendPerson],遍历免打扰数组与当前消息的发送方环信id,就可以知道是否需要免打扰了。
 
下面附上完整代码
 

// // BHTabBarController.m // xxxxxxx // // Created by LiBohan on 2017/8/24. // Copyright © 2017年 xxxxxx. All rights reserved. // //两次提示的默认间隔 static const CGFloat kDefaultPlaySoundInterval = 3.0; static NSString *kMessageType = @"MessageType"; static NSString *kConversationChatter = @"ConversationChatter"; static NSString *kGroupName = @"GroupName"; #import "BHTabBarController.h" #import <UserNotifications/UserNotifications.h> #import "BHConversationListController.h" @interface BHTabBarController ()<EMChatManagerDelegate> @property (strong, nonatomic) NSDate *lastPlaySoundDate; @end @implementation BHTabBarController - (void)viewDidLoad { [super viewDidLoad]; [DemoCallManager sharedManager].mainController = self; [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupUnreadMessageCount) name:@"setupUnreadMessageCount" object:nil]; } // 统计未读消息数 -(void)setupUnreadMessageCount { NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations]; NSInteger unreadCount = 0; for (EMConversation *conversation in conversations) { unreadCount += conversation.unreadMessagesCount; } NSArray *tabBarItems = self.tabBar.items; UITabBarItem *conlistTabBarItem = [tabBarItems objectAtIndex:1]; if (unreadCount > 0) { conlistTabBarItem.badgeValue = [NSString stringWithFormat:@"%i",(int)unreadCount]; }else{ conlistTabBarItem.badgeValue = nil; } // UIApplication *application = [UIApplication sharedApplication]; // [application setApplicationIconBadgeNumber:unreadCount]; } - (void)cmdMessagesDidReceive:(NSArray *)aCmdMessages { for (EMMessage *message in aCmdMessages) { EMCmdMessageBody *body = (EMCmdMessageBody *)message.body; NSLog(@"收到的action是 -- %@",body.action); if (body.action == nil) { return; } NSData *jsonData = [body.action dataUsingEncoding:NSUTF8StringEncoding]; NSError *err; NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err]; if(err) { NSLog(@"json解析失败:%@",err); return; } NSNumber *num = dic[@"count"]; NSString *str = [num stringValue]; [AppEngine.IMDataCent requestUpdateUnreadNumberWithUnreadNumber:str]; } } - (void)didReceiveMessages:(NSArray *)aMessages{ // [self refresh]; [self setupUnreadMessageCount]; EMMessage *message = aMessages[0]; NSString *sendPerson = message.from; BHNavigatiomController *imNaviCV = self.viewControllers[1]; BHConversationListController *converCV = imNaviCV.viewControllers[0]; // [converCV refresh]; [converCV refreshDataSource];//只要收到消息就从服务器拿 UIApplicationState state = [[UIApplication sharedApplication] applicationState]; for (NSString *hx_name in AppEngine.IMDataCent.data_ExcuseFriendsData) { if ([hx_name isEqualToString:sendPerson]) { return; } } switch (state) { case UIApplicationStateActive: [self playSoundAndVibration]; break; case UIApplicationStateInactive: [self playSoundAndVibration]; break; case UIApplicationStateBackground: [self showNotificationWithMessage:message]; break; default: break; } } - (void)playSoundAndVibration{ NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate]; if (timeInterval < kDefaultPlaySoundInterval) { //如果距离上次响铃和震动时间太短, 则跳过响铃 NSLog(@"skip ringing & vibration %@, %@", [NSDate date], self.lastPlaySoundDate); return; } //保存最后一次响铃时间 self.lastPlaySoundDate = [NSDate date]; // 收到消息时,播放音频 [[EMCDDeviceManager sharedInstance] playNewMessageSound]; // 收到消息时,震动 [[EMCDDeviceManager sharedInstance] playVibration]; } - (void)showNotificationWithMessage:(EMMessage *)message { EMPushOptions *options = [[EMClient sharedClient] pushOptions]; NSString *alertBody = nil; if (options.displayStyle == EMPushDisplayStyleMessageSummary) { EMMessageBody *messageBody = message.body; NSString *messageStr = nil; switch (messageBody.type) { case EMMessageBodyTypeText: { messageStr = ((EMTextMessageBody *)messageBody).text; } break; case EMMessageBodyTypeImage: { messageStr = NSLocalizedString(@"message.image", @"Image"); } break; case EMMessageBodyTypeLocation: { messageStr = NSLocalizedString(@"message.location", @"Location"); } break; case EMMessageBodyTypeVoice: { messageStr = NSLocalizedString(@"message.voice", @"Voice"); } break; case EMMessageBodyTypeVideo:{ messageStr = NSLocalizedString(@"message.video", @"Video"); } break; default: break; } do { // NSString *title = [[UserProfileManager sharedInstance] getNickNameWithUsername:message.from]; NSString *title = @"大佬"; if (message.chatType == EMChatTypeGroupChat) { NSDictionary *ext = message.ext; if (ext && ext[kGroupMessageAtList]) { id target = ext[kGroupMessageAtList]; if ([target isKindOfClass:[NSString class]]) { if ([kGroupMessageAtAll compare:target options:NSCaseInsensitiveSearch] == NSOrderedSame) { alertBody = [NSString stringWithFormat:@"%@%@", title, NSLocalizedString(@"group.atPushTitle", @" @ me in the group")]; break; } } else if ([target isKindOfClass:[NSArray class]]) { NSArray *atTargets = (NSArray*)target; if ([atTargets containsObject:[EMClient sharedClient].currentUsername]) { alertBody = [NSString stringWithFormat:@"%@%@", title, NSLocalizedString(@"group.atPushTitle", @" @ me in the group")]; break; } } } NSArray *groupArray = [[EMClient sharedClient].groupManager getJoinedGroups]; for (EMGroup *group in groupArray) { if ([group.groupId isEqualToString:message.conversationId]) { title = [NSString stringWithFormat:@"%@(%@)", message.from, group.subject]; break; } } } else if (message.chatType == EMChatTypeChatRoom) { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSString *key = [NSString stringWithFormat:@"OnceJoinedChatrooms_%@", [[EMClient sharedClient] currentUsername]]; NSMutableDictionary *chatrooms = [NSMutableDictionary dictionaryWithDictionary:[ud objectForKey:key]]; NSString *chatroomName = [chatrooms objectForKey:message.conversationId]; if (chatroomName) { title = [NSString stringWithFormat:@"%@(%@)", message.from, chatroomName]; } } alertBody = [NSString stringWithFormat:@"%@:%@", title, messageStr]; } while (0); } else{ // alertBody = NSLocalizedString(@"receiveMessage", @"you have a new message"); alertBody = @"您有一条消息"; } NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate]; BOOL playSound = NO; if (!self.lastPlaySoundDate || timeInterval >= kDefaultPlaySoundInterval) { self.lastPlaySoundDate = [NSDate date]; playSound = YES; } NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:[NSNumber numberWithInt:message.chatType] forKey:kMessageType]; [userInfo setObject:message.conversationId forKey:kConversationChatter]; //发送本地推送 if (NSClassFromString(@"UNUserNotificationCenter")) { UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.01 repeats:NO]; UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; if (playSound) { content.sound = [UNNotificationSound defaultSound]; } content.body =alertBody; content.userInfo = userInfo; UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:message.messageId content:content trigger:trigger]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; } else { UILocalNotification *notification = [[UILocalNotification alloc] init]; notification.fireDate = [NSDate date]; //触发通知的时间 notification.alertBody = alertBody; notification.alertAction = NSLocalizedString(@"open", @"Open"); notification.timeZone = [NSTimeZone defaultTimeZone]; if (playSound) { notification.soundName = UILocalNotificationDefaultSoundName; } notification.userInfo = userInfo; //发送通知 [[UIApplication sharedApplication] scheduleLocalNotification:notification]; } } - (void)messagesDidDeliver:(NSArray *)aMessages{ NSLog(@"sf"); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ @end
 
 
本人github:https://github.com/BHAreslee
本人简书:http://www.jianshu.com/u/bb53043aaa00
以上就是集成环信时暂时发现的问题,欢迎大家分享你们遇到的问题,也欢迎加入QQ群:372251359,一起讨论交流即时通讯的问题。
本人微信公众号:放心安慰剂





  查看全部
现在大多数社交app都有消息免打扰功能,因为环信SDK主要是对即时通讯模块进行封装,因此如果要实现消息免打扰功能则需要开发者自己对此功能逻辑进行处理。
先解释一下,本人项目中对消息免打扰功能的定义:正常情况下,我们收到的每一条聊天消息都会收到小红点、声音、震动的提示。如果对某个好友设置消息免打扰功能,则只提示小红点,声音和震动则不再提示。

下面简述结合环信SDK时,此功能的实现方法。
环信将即时聊天的所有功能分为四大模块进行管理:
//聊天模块:
[EMClient sharedClient].chatManager
//好友模块 :
[EMClient sharedClient].contactManager
//群组模块 :
[EMClient sharedClient].groupManager
//聊天室模块:
[EMClient sharedClient].roomManager
消息免打扰的功能借助聊天模块[EMClient sharedClient].chatManager的API就能实现。
一般来说,在app内不管当前在哪个界面,只要收到消息都需要被判断是否需要免打扰,因此可以在appdelegate里写如下代码,app通常都有tabBarController,那么也可以让tabBarController来实现如下代码。
首先让控制器tabBarController遵守代理EMChatManagerDelegate
然后成为代理
[[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];

EMChatManagerDelegate中有一个代理方法
- (void)didReceiveMessages:(NSArray *)aMessages;
实现此代理方法
- (void)didReceiveMessages:(NSArray *)aMessages{
[self setupUnreadMessageCount];
EMMessage *message = aMessages[0];
NSString *sendPerson = message.from;
BHNavigatiomController *imNaviCV = self.viewControllers[1];
BHConversationListController *converCV = imNaviCV.viewControllers[0];
[converCV refreshDataSource];//只要收到消息就从服务器拿
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
for (NSString *hx_name in AppEngine.IMDataCent.data_ExcuseFriendsData) {
if ([hx_name isEqualToString:sendPerson]) {
return;
}
}
switch (state) {
case UIApplicationStateActive:
[self playSoundAndVibration];
break;
case UIApplicationStateInactive:
[self playSoundAndVibration];
break;
case UIApplicationStateBackground:
[self showNotificationWithMessage:message];
break;
default:
break;
}
}
方法中有一个aMessages参数。这个参数是一个消息组,每一个元素都是EMMessage类型的实例。
因为通常都只有一条信息。因此只需要取出EMMessage *message = aMessages[0];
EMMessage类中有许多属性,因此根据一条信息基本可以获取想知道的所有信息。这里我们只需要知道此消息的发送方即可,也就是发送方的环信idNSString *sendPerson = message.from;下面代码中有一AppEngine.IMDataCent.data_ExcuseFriendsData,这个数组里面装的都是已经被设置消息免打扰的好友的环信id,是请求自己服务器获得的,获取的代码最好在一打开app时,就及时获取到。然后根据对好友免打扰设置的操作,访问后台接口进行增删,并刷新数组与后端保持一致即可。
通过[hx_name isEqualToString:sendPerson],遍历免打扰数组与当前消息的发送方环信id,就可以知道是否需要免打扰了。
 
下面附上完整代码
 

// // BHTabBarController.m // xxxxxxx // // Created by LiBohan on 2017/8/24. // Copyright © 2017年 xxxxxx. All rights reserved. // //两次提示的默认间隔 static const CGFloat kDefaultPlaySoundInterval = 3.0; static NSString *kMessageType = @"MessageType"; static NSString *kConversationChatter = @"ConversationChatter"; static NSString *kGroupName = @"GroupName"; #import "BHTabBarController.h" #import <UserNotifications/UserNotifications.h> #import "BHConversationListController.h" @interface BHTabBarController ()<EMChatManagerDelegate> @property (strong, nonatomic) NSDate *lastPlaySoundDate; @end @implementation BHTabBarController - (void)viewDidLoad { [super viewDidLoad]; [DemoCallManager sharedManager].mainController = self; [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupUnreadMessageCount) name:@"setupUnreadMessageCount" object:nil]; } // 统计未读消息数 -(void)setupUnreadMessageCount { NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations]; NSInteger unreadCount = 0; for (EMConversation *conversation in conversations) { unreadCount += conversation.unreadMessagesCount; } NSArray *tabBarItems = self.tabBar.items; UITabBarItem *conlistTabBarItem = [tabBarItems objectAtIndex:1]; if (unreadCount > 0) { conlistTabBarItem.badgeValue = [NSString stringWithFormat:@"%i",(int)unreadCount]; }else{ conlistTabBarItem.badgeValue = nil; } // UIApplication *application = [UIApplication sharedApplication]; // [application setApplicationIconBadgeNumber:unreadCount]; } - (void)cmdMessagesDidReceive:(NSArray *)aCmdMessages { for (EMMessage *message in aCmdMessages) { EMCmdMessageBody *body = (EMCmdMessageBody *)message.body; NSLog(@"收到的action是 -- %@",body.action); if (body.action == nil) { return; } NSData *jsonData = [body.action dataUsingEncoding:NSUTF8StringEncoding]; NSError *err; NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err]; if(err) { NSLog(@"json解析失败:%@",err); return; } NSNumber *num = dic[@"count"]; NSString *str = [num stringValue]; [AppEngine.IMDataCent requestUpdateUnreadNumberWithUnreadNumber:str]; } } - (void)didReceiveMessages:(NSArray *)aMessages{ // [self refresh]; [self setupUnreadMessageCount]; EMMessage *message = aMessages[0]; NSString *sendPerson = message.from; BHNavigatiomController *imNaviCV = self.viewControllers[1]; BHConversationListController *converCV = imNaviCV.viewControllers[0]; // [converCV refresh]; [converCV refreshDataSource];//只要收到消息就从服务器拿 UIApplicationState state = [[UIApplication sharedApplication] applicationState]; for (NSString *hx_name in AppEngine.IMDataCent.data_ExcuseFriendsData) { if ([hx_name isEqualToString:sendPerson]) { return; } } switch (state) { case UIApplicationStateActive: [self playSoundAndVibration]; break; case UIApplicationStateInactive: [self playSoundAndVibration]; break; case UIApplicationStateBackground: [self showNotificationWithMessage:message]; break; default: break; } } - (void)playSoundAndVibration{ NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate]; if (timeInterval < kDefaultPlaySoundInterval) { //如果距离上次响铃和震动时间太短, 则跳过响铃 NSLog(@"skip ringing & vibration %@, %@", [NSDate date], self.lastPlaySoundDate); return; } //保存最后一次响铃时间 self.lastPlaySoundDate = [NSDate date]; // 收到消息时,播放音频 [[EMCDDeviceManager sharedInstance] playNewMessageSound]; // 收到消息时,震动 [[EMCDDeviceManager sharedInstance] playVibration]; } - (void)showNotificationWithMessage:(EMMessage *)message { EMPushOptions *options = [[EMClient sharedClient] pushOptions]; NSString *alertBody = nil; if (options.displayStyle == EMPushDisplayStyleMessageSummary) { EMMessageBody *messageBody = message.body; NSString *messageStr = nil; switch (messageBody.type) { case EMMessageBodyTypeText: { messageStr = ((EMTextMessageBody *)messageBody).text; } break; case EMMessageBodyTypeImage: { messageStr = NSLocalizedString(@"message.image", @"Image"); } break; case EMMessageBodyTypeLocation: { messageStr = NSLocalizedString(@"message.location", @"Location"); } break; case EMMessageBodyTypeVoice: { messageStr = NSLocalizedString(@"message.voice", @"Voice"); } break; case EMMessageBodyTypeVideo:{ messageStr = NSLocalizedString(@"message.video", @"Video"); } break; default: break; } do { // NSString *title = [[UserProfileManager sharedInstance] getNickNameWithUsername:message.from]; NSString *title = @"大佬"; if (message.chatType == EMChatTypeGroupChat) { NSDictionary *ext = message.ext; if (ext && ext[kGroupMessageAtList]) { id target = ext[kGroupMessageAtList]; if ([target isKindOfClass:[NSString class]]) { if ([kGroupMessageAtAll compare:target options:NSCaseInsensitiveSearch] == NSOrderedSame) { alertBody = [NSString stringWithFormat:@"%@%@", title, NSLocalizedString(@"group.atPushTitle", @" @ me in the group")]; break; } } else if ([target isKindOfClass:[NSArray class]]) { NSArray *atTargets = (NSArray*)target; if ([atTargets containsObject:[EMClient sharedClient].currentUsername]) { alertBody = [NSString stringWithFormat:@"%@%@", title, NSLocalizedString(@"group.atPushTitle", @" @ me in the group")]; break; } } } NSArray *groupArray = [[EMClient sharedClient].groupManager getJoinedGroups]; for (EMGroup *group in groupArray) { if ([group.groupId isEqualToString:message.conversationId]) { title = [NSString stringWithFormat:@"%@(%@)", message.from, group.subject]; break; } } } else if (message.chatType == EMChatTypeChatRoom) { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSString *key = [NSString stringWithFormat:@"OnceJoinedChatrooms_%@", [[EMClient sharedClient] currentUsername]]; NSMutableDictionary *chatrooms = [NSMutableDictionary dictionaryWithDictionary:[ud objectForKey:key]]; NSString *chatroomName = [chatrooms objectForKey:message.conversationId]; if (chatroomName) { title = [NSString stringWithFormat:@"%@(%@)", message.from, chatroomName]; } } alertBody = [NSString stringWithFormat:@"%@:%@", title, messageStr]; } while (0); } else{ // alertBody = NSLocalizedString(@"receiveMessage", @"you have a new message"); alertBody = @"您有一条消息"; } NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate]; BOOL playSound = NO; if (!self.lastPlaySoundDate || timeInterval >= kDefaultPlaySoundInterval) { self.lastPlaySoundDate = [NSDate date]; playSound = YES; } NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:[NSNumber numberWithInt:message.chatType] forKey:kMessageType]; [userInfo setObject:message.conversationId forKey:kConversationChatter]; //发送本地推送 if (NSClassFromString(@"UNUserNotificationCenter")) { UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.01 repeats:NO]; UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; if (playSound) { content.sound = [UNNotificationSound defaultSound]; } content.body =alertBody; content.userInfo = userInfo; UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:message.messageId content:content trigger:trigger]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; } else { UILocalNotification *notification = [[UILocalNotification alloc] init]; notification.fireDate = [NSDate date]; //触发通知的时间 notification.alertBody = alertBody; notification.alertAction = NSLocalizedString(@"open", @"Open"); notification.timeZone = [NSTimeZone defaultTimeZone]; if (playSound) { notification.soundName = UILocalNotificationDefaultSoundName; } notification.userInfo = userInfo; //发送通知 [[UIApplication sharedApplication] scheduleLocalNotification:notification]; } } - (void)messagesDidDeliver:(NSArray *)aMessages{ NSLog(@"sf"); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ @end
 
 
本人github:https://github.com/BHAreslee
本人简书:http://www.jianshu.com/u/bb53043aaa00
以上就是集成环信时暂时发现的问题,欢迎大家分享你们遇到的问题,也欢迎加入QQ群:372251359,一起讨论交流即时通讯的问题。
本人微信公众号:放心安慰剂

qrcode_for_gh_bc92a063b4a2_430.jpg

 
0
评论

【环信征文】集成环信V3.3.4SDK遇到的两个问题 iOS 环信 即时通讯 IM

cokeyer 发表了文章 • 36 次浏览 • 2017-09-12 17:56 • 来自相关话题

公司又有了新项目,依然是含有即时通讯功能模块的项目。在经历了上个项目对环信sdk的集成后,对环信EaseUI有了大概的了解。这次果断还是集成环信,一回生二回熟,毕竟哪些地方有坑心里有数了。所以这次的项目集成起来就相对来说顺利很多。
项目集成的是环信V3.3.2版本,但今天下载了V3.3.4版本检查了一下,发现依然有不少V3.3.2版本遗留的问题没有修复,下面会列出一些项目中遇到的问题,并提供解决办法。
首先在会话界面自定义一个类比如BHChatViewController,继承自EaseMessageViewController类,基本上一个简单的界面就有了。因为是医疗类的项目,对话双方有可能是患者和患者,也有可能是患者和医生。项目需求是如果是患者和医生聊天,必须先经过预约,并只能在预约时间区间内才能和医生发送聊天消息、图片、语音、实时音视频。
以下就是EaseMessageViewController类的发送各种消息的方法
那么根据需要只需重写以下方法即可。/*!
@method
@brief 发送文本消息
@discussion
@param text 文本消息
@result
*/
- (void)sendTextMessage:(NSString *)text;

/*!
@method
@brief 发送文本消息
@discussion
@param text 文本消息
@param ext 扩展信息
@result
*/
- (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext;

/*!
@method
@brief 发送图片消息
@discussion
@param image 发送图片
@result
*/
- (void)sendImageMessage:(UIImage *)image;

/*!
@method
@brief 发送位置消息
@discussion
@param latitude 经度
@param longitude 纬度
@param address 地址
@result
*/
- (void)sendLocationMessageLatitude:(double)latitude
longitude:(double)longitude
andAddress:(NSString *)address;

/*!
@method
@brief 发送语音消息
@discussion
@param localPath 语音本地地址
@param duration 时长
@result
*/
- (void)sendVoiceMessageWithLocalPath:(NSString *)localPath
duration:(NSInteger)duration;

/*!
@method
@brief 发送视频消息
@discussion
@param url 视频url
@result
*/
- (void)sendVideoMessageWithURL:(NSURL *)url;我在对发送图片的操作进行处理的时候,发现当拍照发图时,走这个方法//当你拍照发图时,走这个方法
- (void)sendImageMessage:(UIImage *)image;但当从相册选择图片发送时,会发现,不走上面的方法了。
仔细检查代码发现走了EaseMessageViewController.m的如下方法





然而这个方法,环信并没有放在EaseMessageViewController.h成为公开方法。我们只需手动粘贴方法到.h,然后在自己的子类重写就可以了。

还有一个关于更多下图更多功能区域的问题





如上图所示,相册、拍照、视频等附加功能按钮,环信用EaseChatBarMoreView类来管理的。
如果需要增加功能按钮用这个方法/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;移除某个功能按钮用这个方法/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;修改一个功能按钮用这个方法/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;可当你添加按钮,或者修改按钮时,会发现按钮的名字设置不了
然后检查环信内部的实现,发现title的值在方法里根本就没用到?!
索性不用updateItemWithImage这个方法了,直接去改内部代码。
修改代码如下

到EaseChatBarMoreView.m修改- (void)setupSubviewsForType:(EMChatToolbarType)type方法。改动的部分在代码后面有标注。- (void)setupSubviewsForType:(EMChatToolbarType)type
{
//self.backgroundColor = [UIColor clearColor];
self.accessibilityIdentifier = @"more_view";

_scrollview = [[UIScrollView alloc] init];
_scrollview.pagingEnabled = YES;
_scrollview.showsHorizontalScrollIndicator = NO;
_scrollview.showsVerticalScrollIndicator = NO;
_scrollview.delegate = self;
[self addSubview:_scrollview];

_pageControl = [[UIPageControl alloc] init];
_pageControl.currentPage = 0;
_pageControl.numberOfPages = 1;
[self addSubview:_pageControl];

CGFloat insets = (self.frame.size.width - 4 * CHAT_BUTTON_SIZE) / 5;

_photoButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_photoButton setTitle:@"相册" forState:UIControlStateNormal];//改动
[_photoButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_photoButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_photoButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_photoButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
_photoButton.accessibilityIdentifier = @"image";
[_photoButton setFrame:CGRectMake(insets, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//改动
[_photoButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_photo"] forState:UIControlStateNormal];
[_photoButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_photoSelected"] forState:UIControlStateHighlighted];
[_photoButton addTarget:self action:@selector(photoAction) forControlEvents:UIControlEventTouchUpInside];
_photoButton.tag = MOREVIEW_BUTTON_TAG;
[_scrollview addSubview:_photoButton];

_locationButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_locationButton setTitle:@"位置" forState:UIControlStateNormal];//改动
[_locationButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_locationButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_locationButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_locationButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
_locationButton.accessibilityIdentifier = @"location";
[_locationButton setFrame:CGRectMake(insets * 2 + CHAT_BUTTON_SIZE, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_locationButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_location"] forState:UIControlStateNormal];
[_locationButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_locationSelected"] forState:UIControlStateHighlighted];
[_locationButton addTarget:self action:@selector(locationAction) forControlEvents:UIControlEventTouchUpInside];
_locationButton.tag = MOREVIEW_BUTTON_TAG + 1;
[_scrollview addSubview:_locationButton];

_takePicButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_takePicButton setTitle:@"拍照" forState:UIControlStateNormal];//改动
[_takePicButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_takePicButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_takePicButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_takePicButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_takePicButton setFrame:CGRectMake(insets * 3 + CHAT_BUTTON_SIZE * 2, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//改动
[_takePicButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_camera"] forState:UIControlStateNormal];
[_takePicButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_cameraSelected"] forState:UIControlStateHighlighted];
[_takePicButton addTarget:self action:@selector(takePicAction) forControlEvents:UIControlEventTouchUpInside];
_takePicButton.tag = MOREVIEW_BUTTON_TAG + 2;
_maxIndex = 2;
[_scrollview addSubview:_takePicButton];

CGRect frame = self.frame;
if (type == EMChatToolbarTypeChat) {
frame.size.height = 150;
_audioCallButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_audioCallButton setTitle:@"语音" forState:UIControlStateNormal];//改动
[_audioCallButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_audioCallButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_audioCallButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_audioCallButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_audioCallButton setFrame:CGRectMake(insets * 4 + CHAT_BUTTON_SIZE * 3, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_audioCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_audioCall"] forState:UIControlStateNormal];
[_audioCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_audioCallSelected"] forState:UIControlStateHighlighted];
[_audioCallButton addTarget:self action:@selector(takeAudioCallAction) forControlEvents:UIControlEventTouchUpInside];
_audioCallButton.tag = MOREVIEW_BUTTON_TAG + 3;
[_scrollview addSubview:_audioCallButton];

_videoCallButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_videoCallButton setTitle:@"视频" forState:UIControlStateNormal];//改动
[_videoCallButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_videoCallButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_videoCallButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_videoCallButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_videoCallButton setFrame:CGRectMake(insets, 10 * 2 + CHAT_BUTTON_SIZE + 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_videoCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_videoCall"] forState:UIControlStateNormal];
[_videoCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_videoCallSelected"] forState:UIControlStateHighlighted];
[_videoCallButton addTarget:self action:@selector(takeVideoCallAction) forControlEvents:UIControlEventTouchUpInside];
_videoCallButton.tag =MOREVIEW_BUTTON_TAG + 4;
_maxIndex = 4;
[_scrollview addSubview:_videoCallButton];
}
else if (type == EMChatToolbarTypeGroup)
{
frame.size.height = 80;
}
self.frame = frame;
_scrollview.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
_pageControl.frame = CGRectMake(0, CGRectGetHeight(frame) - 20, CGRectGetWidth(frame), 20);
_pageControl.hidden = _pageControl.numberOfPages<=1;
}


本人github:https://github.com/BHAreslee
本人简书:http://www.jianshu.com/u/bb53043aaa00
以上就是集成环信时暂时发现的问题,欢迎大家分享你们遇到的问题,也欢迎加入QQ群:372251359,一起讨论交流即时通讯的问题。
本人微信公众号:放心安慰剂 查看全部
公司又有了新项目,依然是含有即时通讯功能模块的项目。在经历了上个项目对环信sdk的集成后,对环信EaseUI有了大概的了解。这次果断还是集成环信,一回生二回熟,毕竟哪些地方有坑心里有数了。所以这次的项目集成起来就相对来说顺利很多。
项目集成的是环信V3.3.2版本,但今天下载了V3.3.4版本检查了一下,发现依然有不少V3.3.2版本遗留的问题没有修复,下面会列出一些项目中遇到的问题,并提供解决办法。
首先在会话界面自定义一个类比如BHChatViewController,继承自EaseMessageViewController类,基本上一个简单的界面就有了。因为是医疗类的项目,对话双方有可能是患者和患者,也有可能是患者和医生。项目需求是如果是患者和医生聊天,必须先经过预约,并只能在预约时间区间内才能和医生发送聊天消息、图片、语音、实时音视频。
以下就是EaseMessageViewController类的发送各种消息的方法
那么根据需要只需重写以下方法即可。
/*!
@method
@brief 发送文本消息
@discussion
@param text 文本消息
@result
*/
- (void)sendTextMessage:(NSString *)text;

/*!
@method
@brief 发送文本消息
@discussion
@param text 文本消息
@param ext 扩展信息
@result
*/
- (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext;

/*!
@method
@brief 发送图片消息
@discussion
@param image 发送图片
@result
*/
- (void)sendImageMessage:(UIImage *)image;

/*!
@method
@brief 发送位置消息
@discussion
@param latitude 经度
@param longitude 纬度
@param address 地址
@result
*/
- (void)sendLocationMessageLatitude:(double)latitude
longitude:(double)longitude
andAddress:(NSString *)address;

/*!
@method
@brief 发送语音消息
@discussion
@param localPath 语音本地地址
@param duration 时长
@result
*/
- (void)sendVoiceMessageWithLocalPath:(NSString *)localPath
duration:(NSInteger)duration;

/*!
@method
@brief 发送视频消息
@discussion
@param url 视频url
@result
*/
- (void)sendVideoMessageWithURL:(NSURL *)url;
我在对发送图片的操作进行处理的时候,发现当拍照发图时,走这个方法
//当你拍照发图时,走这个方法
- (void)sendImageMessage:(UIImage *)image;
但当从相册选择图片发送时,会发现,不走上面的方法了。
仔细检查代码发现走了EaseMessageViewController.m的如下方法

环信2.png

然而这个方法,环信并没有放在EaseMessageViewController.h成为公开方法。我们只需手动粘贴方法到.h,然后在自己的子类重写就可以了。

还有一个关于更多下图更多功能区域的问题

EaseChatBarMoreView.png

如上图所示,相册、拍照、视频等附加功能按钮,环信用EaseChatBarMoreView类来管理的。
如果需要增加功能按钮用这个方法
/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;
移除某个功能按钮用这个方法
/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;
修改一个功能按钮用这个方法
/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;
可当你添加按钮,或者修改按钮时,会发现按钮的名字设置不了
然后检查环信内部的实现,发现title的值在方法里根本就没用到?!
索性不用updateItemWithImage这个方法了,直接去改内部代码。
修改代码如下

到EaseChatBarMoreView.m修改- (void)setupSubviewsForType:(EMChatToolbarType)type方法。改动的部分在代码后面有标注。
- (void)setupSubviewsForType:(EMChatToolbarType)type
{
//self.backgroundColor = [UIColor clearColor];
self.accessibilityIdentifier = @"more_view";

_scrollview = [[UIScrollView alloc] init];
_scrollview.pagingEnabled = YES;
_scrollview.showsHorizontalScrollIndicator = NO;
_scrollview.showsVerticalScrollIndicator = NO;
_scrollview.delegate = self;
[self addSubview:_scrollview];

_pageControl = [[UIPageControl alloc] init];
_pageControl.currentPage = 0;
_pageControl.numberOfPages = 1;
[self addSubview:_pageControl];

CGFloat insets = (self.frame.size.width - 4 * CHAT_BUTTON_SIZE) / 5;

_photoButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_photoButton setTitle:@"相册" forState:UIControlStateNormal];//改动
[_photoButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_photoButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_photoButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_photoButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
_photoButton.accessibilityIdentifier = @"image";
[_photoButton setFrame:CGRectMake(insets, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//改动
[_photoButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_photo"] forState:UIControlStateNormal];
[_photoButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_photoSelected"] forState:UIControlStateHighlighted];
[_photoButton addTarget:self action:@selector(photoAction) forControlEvents:UIControlEventTouchUpInside];
_photoButton.tag = MOREVIEW_BUTTON_TAG;
[_scrollview addSubview:_photoButton];

_locationButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_locationButton setTitle:@"位置" forState:UIControlStateNormal];//改动
[_locationButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_locationButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_locationButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_locationButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
_locationButton.accessibilityIdentifier = @"location";
[_locationButton setFrame:CGRectMake(insets * 2 + CHAT_BUTTON_SIZE, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_locationButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_location"] forState:UIControlStateNormal];
[_locationButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_locationSelected"] forState:UIControlStateHighlighted];
[_locationButton addTarget:self action:@selector(locationAction) forControlEvents:UIControlEventTouchUpInside];
_locationButton.tag = MOREVIEW_BUTTON_TAG + 1;
[_scrollview addSubview:_locationButton];

_takePicButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_takePicButton setTitle:@"拍照" forState:UIControlStateNormal];//改动
[_takePicButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_takePicButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_takePicButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_takePicButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_takePicButton setFrame:CGRectMake(insets * 3 + CHAT_BUTTON_SIZE * 2, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//改动
[_takePicButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_camera"] forState:UIControlStateNormal];
[_takePicButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_cameraSelected"] forState:UIControlStateHighlighted];
[_takePicButton addTarget:self action:@selector(takePicAction) forControlEvents:UIControlEventTouchUpInside];
_takePicButton.tag = MOREVIEW_BUTTON_TAG + 2;
_maxIndex = 2;
[_scrollview addSubview:_takePicButton];

CGRect frame = self.frame;
if (type == EMChatToolbarTypeChat) {
frame.size.height = 150;
_audioCallButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_audioCallButton setTitle:@"语音" forState:UIControlStateNormal];//改动
[_audioCallButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_audioCallButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_audioCallButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_audioCallButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_audioCallButton setFrame:CGRectMake(insets * 4 + CHAT_BUTTON_SIZE * 3, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_audioCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_audioCall"] forState:UIControlStateNormal];
[_audioCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_audioCallSelected"] forState:UIControlStateHighlighted];
[_audioCallButton addTarget:self action:@selector(takeAudioCallAction) forControlEvents:UIControlEventTouchUpInside];
_audioCallButton.tag = MOREVIEW_BUTTON_TAG + 3;
[_scrollview addSubview:_audioCallButton];

_videoCallButton =[UIButton buttonWithType:UIButtonTypeCustom];
[_videoCallButton setTitle:@"视频" forState:UIControlStateNormal];//改动
[_videoCallButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
_videoCallButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
_videoCallButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
_videoCallButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
[_videoCallButton setFrame:CGRectMake(insets, 10 * 2 + CHAT_BUTTON_SIZE + 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
[_videoCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_videoCall"] forState:UIControlStateNormal];
[_videoCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_videoCallSelected"] forState:UIControlStateHighlighted];
[_videoCallButton addTarget:self action:@selector(takeVideoCallAction) forControlEvents:UIControlEventTouchUpInside];
_videoCallButton.tag =MOREVIEW_BUTTON_TAG + 4;
_maxIndex = 4;
[_scrollview addSubview:_videoCallButton];
}
else if (type == EMChatToolbarTypeGroup)
{
frame.size.height = 80;
}
self.frame = frame;
_scrollview.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
_pageControl.frame = CGRectMake(0, CGRectGetHeight(frame) - 20, CGRectGetWidth(frame), 20);
_pageControl.hidden = _pageControl.numberOfPages<=1;
}


本人github:https://github.com/BHAreslee
本人简书:http://www.jianshu.com/u/bb53043aaa00
以上就是集成环信时暂时发现的问题,欢迎大家分享你们遇到的问题,也欢迎加入QQ群:372251359,一起讨论交流即时通讯的问题。
本人微信公众号:放心安慰剂

qrcode_for_gh_bc92a063b4a2_430.jpg
2
评论

环信进阶篇-实现名片|红包|话题聊天室等自定义cell cell自定义 cell iOS环信 iOS iOS 新手

不死小强 发表了文章 • 885 次浏览 • 2017-06-20 09:50 • 来自相关话题

    伴随着即时通讯成为了越来越多APP的刚需,匿名社交、阅后即焚、红包等新玩法层出不穷,基本的聊天方式越来越难以满足变态的需求。环信提供的自定义扩展属性功能非常的强大,能够帮助我们在cell中的各种需求做定制处理。这是分享一个之前做过的方法及实现,大家可以借鉴处理的过程及思路,如有不妥之处,请大家及时留言告知,谢谢。 
今天就给大家介绍下怎么对cell中的各种需求的定制处理


 类型一:在现有会话cell上修改UI效果

类似于上面给出的截图,我们有时候需要对环信官方给出的cell进行些许的调整。例如:项目中加入了不同于普通群聊或者聊天室的功能需求
点击话题聊天,大家加入聊天室,这里发出的各种就是不同于普通聊天,普通的聊天只需展示文字、地址、图片等等,但是这里的需求是得加上时间、私聊按钮,没砍需求之前是还有点赞和取消赞的按钮。
我们在普通聊天的基础上新建几个cell,文字、语音、图片、地图等等,不能和原有的普通cell混合起来,因为需求有普通聊天。

直接把普通聊天cell中的代码拷贝过来,再在此基础上进行cell的UI自定义处理,就拿文字聊天时的处理情况为例:

1、拷贝复制原有普通聊天cell内的代码

2、把需要的新增的UI控件初始化

3、适配各类控件

4、传值及赋值

5、新增按钮点击和本身cell的点击效果处理(别和cell上的点击效果混到一起)

6、耐心调整cell上UI效果

以上基本就是简单的自定义cell步骤了,有基础的小伙伴看下步骤应该就有思路了

类型二:类似于红包和名片Cell的UI效果


通常在我们项目中,并不只有文字、图片等等这些简单的聊天内容,有时候我们需要把自己的信息作为一张名片发给对方、发个红包给好朋友、发一个项目中的一个模块介绍给对方等等功能要求。
我们就拿雷哥的这张假名片为例:/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;
/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;
/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;*  消息体类型
typedef enum{
EMMessageBodyTypeText  = 1,    /*! \~chinese 文本类型 \~english Text */
EMMessageBodyTypeImage,        /*! \~chinese 图片类型 \~english Image */
EMMessageBodyTypeVideo,        /*! \~chinese 视频类型 \~english Video */
EMMessageBodyTypeLocation,      /*! \~chinese 位置类型 \~english Location */
EMMessageBodyTypeVoice,        /*! \~chinese 语音类型 \~english Voice */
EMMessageBodyTypeFile,          /*! \~chinese 文件类型 \~english File */
EMMessageBodyTypeCmd,          /*! \~chinese 命令类型 \~english Command */
}EMMessageBodyType;
如果环信把这个开放出来,或许我们就更加简单了我们只需自己修改成自己对应的类型即可。但是这个目前就想想,所以我们可以在以上类型中找一个出来,在它的基础上做些文章,变成我们想要的类型。

红包和名片最像什么。。。。对,不就和图片差不多嘛,不过小伙伴也不要以为只能拿图片来做文章,其他的我们都可以拿来用,这里就拿文字类型来作为例子(原理都一样)。




名片类型
这里我们只简要介绍怎么根据会话类型来显示名片,具体传值等怎么做,有基础的小伙伴应该都懂,不懂的小伙伴见文章底部。
 
我们需要在发送名片时,在拓展消息里面存一个名片的字段,这个字段可以被用来判断是名片、红包等等。名片、红包等等中内容,同样也存在拓展属性中(这里不做过多介绍)我们在展示自己的消息和接收到对方的消息时,在文字类型的基础上再进一步判断是什么类型,加载对应类型的视图,如果是红包就加载红包的view,如果是名片就展示名片view......





加载不同类型的cell
好了,以上就是我们所要介绍的两种不同类型cell的处理办法。


以下是补充自定义cell时遇到的各种情况及处理:
1、cell上语音、图片等原始点击和新增按钮点击冲突处理:

注释掉原有的点击方法,把原有的点击方法放到具体的控件上去,避免cell上多个控件点击的冲突

重点:记得把气泡上的点击权限打开_backgroundImageView.userInteractionEnabled = YES;





解决点击冲突
2、cell上语音气泡长度的改变,避免过段影响布局

我们只需把原有语音上的语音长度Label距语音图片控件调大一点距离就能自动把语音类气泡拉长。(其他类型一样处理原理)




语音气泡拉长
3、因新增控件导致在原有cell上高度的变化处理
/*! @method @brief 根据消息的内容,获取当前cell的高度 @discussion @param model 消息对象model @result 返回cell高度 */
+ (CGFloat)cellHeightWithModel:(id)model在原cell高度处理的情况下,根据各种类型的判断进行cell高度的自适应。




cell高度处理
4、文字类型气泡长度的处理

我暂时的处理方法:判断输入的文字长度,加入文字长度小于10,我会在后面自动补全5个空格,被动撑长气泡的长度。

假如小伙伴们有更好的建议也可以留言,谢谢! 查看全部
    伴随着即时通讯成为了越来越多APP的刚需,匿名社交、阅后即焚、红包等新玩法层出不穷,基本的聊天方式越来越难以满足变态的需求。环信提供的自定义扩展属性功能非常的强大,能够帮助我们在cell中的各种需求做定制处理。这是分享一个之前做过的方法及实现,大家可以借鉴处理的过程及思路,如有不妥之处,请大家及时留言告知,谢谢。 
今天就给大家介绍下怎么对cell中的各种需求的定制处理


 类型一:在现有会话cell上修改UI效果

类似于上面给出的截图,我们有时候需要对环信官方给出的cell进行些许的调整。例如:项目中加入了不同于普通群聊或者聊天室的功能需求
点击话题聊天,大家加入聊天室,这里发出的各种就是不同于普通聊天,普通的聊天只需展示文字、地址、图片等等,但是这里的需求是得加上时间、私聊按钮,没砍需求之前是还有点赞和取消赞的按钮。
我们在普通聊天的基础上新建几个cell,文字、语音、图片、地图等等,不能和原有的普通cell混合起来,因为需求有普通聊天。

直接把普通聊天cell中的代码拷贝过来,再在此基础上进行cell的UI自定义处理,就拿文字聊天时的处理情况为例:


1、拷贝复制原有普通聊天cell内的代码

2、把需要的新增的UI控件初始化

3、适配各类控件

4、传值及赋值

5、新增按钮点击和本身cell的点击效果处理(别和cell上的点击效果混到一起)

6、耐心调整cell上UI效果

以上基本就是简单的自定义cell步骤了,有基础的小伙伴看下步骤应该就有思路了


类型二:类似于红包和名片Cell的UI效果


通常在我们项目中,并不只有文字、图片等等这些简单的聊天内容,有时候我们需要把自己的信息作为一张名片发给对方、发个红包给好朋友、发一个项目中的一个模块介绍给对方等等功能要求。
我们就拿雷哥的这张假名片为例:
/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;
/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;
/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;
*  消息体类型
typedef enum{
EMMessageBodyTypeText  = 1,    /*! \~chinese 文本类型 \~english Text */
EMMessageBodyTypeImage,        /*! \~chinese 图片类型 \~english Image */
EMMessageBodyTypeVideo,        /*! \~chinese 视频类型 \~english Video */
EMMessageBodyTypeLocation,      /*! \~chinese 位置类型 \~english Location */
EMMessageBodyTypeVoice,        /*! \~chinese 语音类型 \~english Voice */
EMMessageBodyTypeFile,          /*! \~chinese 文件类型 \~english File */
EMMessageBodyTypeCmd,          /*! \~chinese 命令类型 \~english Command */
}EMMessageBodyType;
如果环信把这个开放出来,或许我们就更加简单了我们只需自己修改成自己对应的类型即可。但是这个目前就想想,所以我们可以在以上类型中找一个出来,在它的基础上做些文章,变成我们想要的类型。

红包和名片最像什么。。。。对,不就和图片差不多嘛,不过小伙伴也不要以为只能拿图片来做文章,其他的我们都可以拿来用,这里就拿文字类型来作为例子(原理都一样)。
01.jpg

名片类型
这里我们只简要介绍怎么根据会话类型来显示名片,具体传值等怎么做,有基础的小伙伴应该都懂,不懂的小伙伴见文章底部。
 
  1. 我们需要在发送名片时,在拓展消息里面存一个名片的字段,这个字段可以被用来判断是名片、红包等等。
  2. 名片、红包等等中内容,同样也存在拓展属性中(这里不做过多介绍)
  3. 我们在展示自己的消息和接收到对方的消息时,在文字类型的基础上再进一步判断是什么类型,加载对应类型的视图,如果是红包就加载红包的view,如果是名片就展示名片view......


02.jpg

加载不同类型的cell
好了,以上就是我们所要介绍的两种不同类型cell的处理办法。


以下是补充自定义cell时遇到的各种情况及处理:
1、cell上语音、图片等原始点击和新增按钮点击冲突处理:

注释掉原有的点击方法,把原有的点击方法放到具体的控件上去,避免cell上多个控件点击的冲突

重点:记得把气泡上的点击权限打开
_backgroundImageView.userInteractionEnabled = YES;


03.jpg

解决点击冲突
2、cell上语音气泡长度的改变,避免过段影响布局

我们只需把原有语音上的语音长度Label距语音图片控件调大一点距离就能自动把语音类气泡拉长。(其他类型一样处理原理)
04.jpg

语音气泡拉长
3、因新增控件导致在原有cell上高度的变化处理
/*! @method @brief 根据消息的内容,获取当前cell的高度 @discussion @param model        消息对象model @result 返回cell高度 */
+ (CGFloat)cellHeightWithModel:(id)model
在原cell高度处理的情况下,根据各种类型的判断进行cell高度的自适应。
05.jpg

cell高度处理
4、文字类型气泡长度的处理

我暂时的处理方法:判断输入的文字长度,加入文字长度小于10,我会在后面自动补全5个空格,被动撑长气泡的长度。

假如小伙伴们有更好的建议也可以留言,谢谢!
0
评论

环信推送的一些常见问题 iOS

木云落 发表了文章 • 1794 次浏览 • 2017-06-07 13:15 • 来自相关话题

原文地址 :  http://blog.csdn.net/jyt199011302/article/details/72829520

参考资料
APNS证书创建和上传到环信后台 : http://www.imgeek.org/article/825308748 
APNS离线推送文档 : http://docs.easemob.com/im/300iosclientintegration/75apns
离线推送的集成代码这里就不一一介绍了, 上面文档中写的 很明白了, 接下来说下比较容易误会的几点
一. 离线推送
如果app集成时添加- (void)applicationDidEnterBackground:(UIApplication *)application {
[[EMClient sharedClient] applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[EMClient sharedClient] applicationWillEnterForeground:application];
}
App后台静默后,能够保持长连接3分钟左右。超过3分钟,长连接会断开,当前登录的账号,在服务端被认为离线。消息会存入离线消息空间,之后接收的消息会在再次登录后,连接上服务器,然后通过长连接把消息取走,投递给此用户。如果app配置了推送证书,上传了推送证书并且集成了推送功能,服务器会给接收方发一个APNs推送,则会对离线消息进行APNs推送提示消息内容,通知接收方有一条新消息。
如果想自定义推送的alert,可以在发消息的时候,在消息扩展中添加相应的字段。文档见:APNS内容解析

离线推送 : 当app被杀死或者进入后台三分钟之后
消息回调 : app在前台及app进入后台三分钟之内

注意 : 环信支持推送消息,只是目前还不能根据标签推送给特定用户组,也暂不支持推送模板。CMD消息没有推送,好友请求也没有推送

收不到离线推送时可以从下面几个方面找下原因

1.测试apns推送的时候,接受消息方的app是杀掉状态吗,或者进入后台三分钟以后
2.看看你环信后台上传的证书名称与工程中初始化SDK那里填的证书名 是不是相同的
3.配置证书时候填的id与你工程中的bundle id 是否相同
4.devicetoken有没有传给环信SDK。即查看管理后台中,对应 IM 账户下是否有您刚刚写的证书名。(如果没有,请检查您是否得到了 deviceToken)
5.确认Xcode环境是否配置正确 ,Build Settings---signing,看Debug对应的是不是开发的,Release对应的是不是生产的
6.在确认xcode运行环境是否正确 (Product-->Scheme-->Edit Scheme, 开发证书选Debug,生产证书选Release)
7.证书制作上传过程是否有问题,配置证书的时候是否设置了密码,正确的步骤可以参考:http://www.imgeek.org/article/825308748。另外可以用推送工具进行验证。
8.如果以上都没有问题,可以尝试重新制作上传一下推送证书。
对照这些检查一下,基本上就是这些原因
如果上面几点都符合的话,看下重新登录之后是否可以收到之前收不到推送消息
可以的,话提供一下AppKey,证书名 (查下证书是否被封)以及收不到的推送消息的消息id及发送方和接收方log
log导出请看这篇文章: http://www.imgeek.org/article/825308785 然后转成txt格式上传到工单上,同时注明上述配置都正确 , 环信这边来查下消息推送记录

注意 :后台没有证书名 是指用户列表后面没有显示证书名。这个证书名是SDK初始化的时候传的字符串,用户登录之后会进行绑定。
如果用户没有绑定证书名的话,肯定收不到推送的。这个证书名是用户登录之后绑定的,要确认下初始化SDK的时候有没有传。 options.apnsCertName = apnsCertName;

==============常见问题==============

Q : iOS apns离线推送证书apns的离线推送可以和友盟(极光)推送共用一个证书吗?
A : 环信的推送只要和后台上传的证书对应就可以实现,其他的不关心。
首先苹果推送证书的生成都是统一的方式,这个不区分是极光(友盟)推送证书还是个推证书等等。使用的推送证书只要按照正确的苹果推送证书生成流程创建,都可以使用。
环信添加推送证书可以看http://www.imgeek.org/article/825308748 不是要求必须重新生成推送证书 
Q : 好友申请通知的离线推送?
A : 我们的好友体系,添加好友的申请不支持离线推送。
如果你们是使用App本身的好友体系,可以在app的添加好友业务上向被添加的好友发送文本消息,在EMMessage的ext中设置自定义字段,来区分此条文本消息是否用于好友申请提示,由此来判断处理UI的显示。

Q : iOS的杀死进程远程推送和服务端有关么
A : 如果客户端把远程通知给关了肯定就收不到通知,我们服务器会检测客户端是否有deviceToken,有的话才会把消息发送到deviceToken对应的设备上

Q : 每个项目创建了一个开发的推送证书一个生产的推送证书。这俩证书什么时候要做切换?
A : 在App上传AppStore前需要修改App内初始化SDK设置的推送证书名,EMOptions的apnsCertName。
注意,这里的值需要和在Console管理后天上传时设置的证书名一致。

Q : 绑定devicetoken的时候是否需要先登录到环信?
A : 绑定是需要登录过之后才进行的,
 - (EMError *)bindDeviceToken:(NSData *)aDeviceToken; 是把deviceToken传给SDK。调用登录,SDK会进行绑定。也可以调用 - (void)registerForRemoteNotificationsWithDeviceToken:(NSData *)aDeviceToken
completion:(void (^)(EMError *aError))aCompletionBlock;自己绑定
需要判断是否已经登录,如果已经有登录的账号,再登录会返回 已登录的错误。

Q : 离线推送在客户端怎么设置显示详情?
A : EMPushOptions *pushOptions = [[EMClient sharedClient] pushOptions];
pushOptions.displayStyle = EMPushDisplayStyleMessageSummary;
可以设置离线推送消息显示具体内容还是只显示-您收到一条消息
要设置在登录成功之后,然后要用服务器拉取一遍APNS 属性EMError *error = nil;
EMPushOptions *options = [[EMClient sharedClient] getPushOptionsFromServerWithError:&error];
然后在修改displayStyle

Q : 两个APP通信,如果只希望其中一类APP能收到推送,而另一端的APP不希望收到推送,是不是不希望收到推送的APP不配置证书就好了?
A : 两个App的推送证书都是在同一appkey下单独配置的,如果不希望收到推送,可以对此App不配置推送证书,同时在App代码中注释掉注册远程通知的相关代码。
bundle id对应的证书也可以取消push的功能,针对App不使用任何远程推送服务,包括其他第三方的,如果App还需要其他第三方的推送服务,请忽略这句话。

Q : 不配置推送证书的APP是不是只有刷新的情况下才会显示新的消息?不刷新的情况下APP是看不到新的消息?
A : 不配置推送功能的App,只有在用户登录成功后,才能通过长连接的接收消息回掉中拿到消息体。

Q : 在开发环境下收到了离线推送消息,但是在生成环境下没有收到?
A : 看一下SDK初始化时,是否设置的apnsCerName与生产环境证书上传时填写的证书名一致,还有是否为adhoc打包成ipa文件安装测试的。

Q :发送消息1,2,3,4,5 对方收到推送 2 1 5 顺序不对而且丢失, app角标也不对
A : 1.首先,苹果不保证所有远程推送的到达率。这个可以看苹果官方文档。
Because the delivery of remote notifications is not guaranteed, never include sensitive data or data that can be retrieved by other means in your payload.
2.我们只保证,把离线消息执行远程推送,发给苹果服务器。苹果服务器是否能够百分之百把所有推送送达到指定移动端,这个根据苹果的策略,当 APNs 向你发送了多条推送,你的设备在 APNs 那里下线了,这时 APNs 到你的手机的链路上有多条任务堆积,APNs 的处理方式是,只保留最后一条消息推送给你,然后告知你推送数。那么其他消息会被APNs丢弃。
3.我们保证的是离线消息,当用户重新登录时,可以都接收到。
4. 如果需要找后台查询离线消息(前提接收方已绑定deviceToken)是否成功,需要提供离线消息的messageId,接收方环信id
5.我们的推送角标,是接收方的在服务端的离线消息数。
https://developer.apple.com/li ... 8-SW1
这是官方文档





Q11 :ios是怎么判断离线了 然后发推送的啊 有时候把应用杀掉后 半天收不到推送
A : rest可以查用户的状态,推送前提是此用户有devicetoken已经绑定成功
如果账号所有配置都没问题,杀掉后,其他人发的消息,过几秒就能看到推送
Q12 : Q11不管用会是什么原因呢
A : 配置,还有账号在我们这绑的deviceToken

Q : 多个app共用一个appkey 推送证书怎么配置呢
A : 后台可以上传多套推送证书。

Q :程序关闭后推送了一个消息,点击后怎样获取到环信传过来的数据
A : 需要用户点击横幅后,重新启动App,这时从- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中,获取字典launchOptions,UIApplicationLaunchOptionsRemoteNotificationKey这个key下的数据,就是aps的字典数据

二 . 消息回调
app的长连接存在的时候,环信服务器检测您为在线状态,是不会给app推送消息的。app端在线的情况下,消息会通过长连接直接收取(didreceivemessage),收到消息,SDK会通过回调通知给上层。app通过收消息的回调拿到消息对象,然后解析并展示UI。
目前我们不支持App切后台后,可以一直执行。
我们SDK在切后台后,实现[[EMClient sharedClient] applicationDidEnterBackground:application];,会保持,直到被系统释放iOS目前其他方式应该都无法去实现一直保持App的活跃状态了。

==============常见问题==============
Q : 本地推送声音设置在哪 ?一条消息推送两声
A : 推送声音设置的要自己实现,具体可参考demo里的ChatDemoHelper类和MainViewController类里的
- (void)showNotificationWithMessage:(EMMessage *)message方法,该方法中有发送本地推送做的一系列操作,本地通知怎么做的,本地通知触发几次,一条消息推送几声,一下接收到多条消息响几声,都需要用户自己实现.

Q : 视频通话,推送怎么实现?
iOS 3.2.3之后,如果在实时通话接收方不在线时,发送提醒。
A : 1.在发起实时音视频通话前,需要设置EMCallOptions对象属性isSendPushIfOffline为YES;
2.遵守协议EMCallBuilderDelegate,实现其中的- (void)callRemoteOffline:(NSString *)aRemoteName 委托方法。
3.在第2步的方法中向 aRemoteName用户发送单聊消息。
如果被叫方已注册远程通知且绑定deviceToken,会收到对应消息的APNs推送,点击横幅来唤醒App。
上面是接收方离线的情况。如果接收方长连接还未断开,只是App切到后台,需要在回调- (void)callDidReceive:(EMCallSession *)aSession中判断当前App是否在后台,如果是弹出本地通知。

Q : EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions]; //当对方不在线时,是否给对方发送离线消息和推送,并等待对方回应 options.isSendPushIfOffline = YES; [[EMClient sharedClient].callManager setCallOptions:options];isSendPushIfOffline设置为YES后,A用户呼叫B用户,B用户处于离线状态,但B用户没有收到推送。
A : 1. 先确接收方杀掉App后,文本消息是否能收到APNs推送。
2.在1点确认App杀掉可以收到推送前提下,确认实时音视频发送方代码执行顺序如下:(1) EMCallOptions *callOptions = [[EMClient sharedClient].callManager getCallOptions];
callOptions.isSendPushIfOffline = YES;
callOptions.offlineMessageText = @"提示文本";//可选
[[EMClient sharedClient].callManager setCallOptions:callOptions];
(2) callManager调用- (void)startVideoCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;
或者- (void)startVoiceCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

Q : 推送的提示音可不可以自定义啊。
A: 推送的提示音目前不支持自定义,本地通知的你们以自己去设置。
 
Q : app压后台,立刻收到聊天推送来的信息,点击通知栏信息,捕获不到唤起程序事件
A : App切后台后,长连接为断开前,当前弹出的横幅是本地通知,那么此时唤醒时间是本地通知的回调- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;
iOS10后- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler;

Q : 环信在离线状态下能收消息,但是程序运行状态按home按键进入后台的时候无法接受消息,怎么处理
A : App切入后台一段时间内,长连接还未断开,这时候接收消息都是通过SDK的接收消息回调(EMChatManagerDelegate)来收消息,不会执行APNs推送。
如果是需要弹出横幅提醒,需要在接收消息的回调方法中,判断[[UIApplication sharedApplication] applicationState]为UIApplicationStateBackground,然后实现本地通知。可以参考demo中的处理(ChatDemoHelper的- (void)didReceiveMessages:(NSArray *)aMessages)
[attach]7500[/attach][size=13]


[/size] 查看全部
原文地址 :  http://blog.csdn.net/jyt199011302/article/details/72829520

参考资料
APNS证书创建和上传到环信后台 : http://www.imgeek.org/article/825308748 
APNS离线推送文档 : http://docs.easemob.com/im/300iosclientintegration/75apns
离线推送的集成代码这里就不一一介绍了, 上面文档中写的 很明白了, 接下来说下比较容易误会的几点
一. 离线推送
如果app集成时添加
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[EMClient sharedClient] applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[EMClient sharedClient] applicationWillEnterForeground:application];
}

App后台静默后,能够保持长连接3分钟左右。超过3分钟,长连接会断开,当前登录的账号,在服务端被认为离线。消息会存入离线消息空间,之后接收的消息会在再次登录后,连接上服务器,然后通过长连接把消息取走,投递给此用户。如果app配置了推送证书,上传了推送证书并且集成了推送功能,服务器会给接收方发一个APNs推送,则会对离线消息进行APNs推送提示消息内容,通知接收方有一条新消息。
如果想自定义推送的alert,可以在发消息的时候,在消息扩展中添加相应的字段。文档见:APNS内容解析

离线推送 : 当app被杀死或者进入后台三分钟之后
消息回调 : app在前台及app进入后台三分钟之内

注意 : 环信支持推送消息,只是目前还不能根据标签推送给特定用户组,也暂不支持推送模板。CMD消息没有推送,好友请求也没有推送

收不到离线推送时可以从下面几个方面找下原因

1.测试apns推送的时候,接受消息方的app是杀掉状态吗,或者进入后台三分钟以后
2.看看你环信后台上传的证书名称与工程中初始化SDK那里填的证书名 是不是相同的
3.配置证书时候填的id与你工程中的bundle id 是否相同
4.devicetoken有没有传给环信SDK。即查看管理后台中,对应 IM 账户下是否有您刚刚写的证书名。(如果没有,请检查您是否得到了 deviceToken)
5.确认Xcode环境是否配置正确 ,Build Settings---signing,看Debug对应的是不是开发的,Release对应的是不是生产的
6.在确认xcode运行环境是否正确 (Product-->Scheme-->Edit Scheme, 开发证书选Debug,生产证书选Release)
7.证书制作上传过程是否有问题,配置证书的时候是否设置了密码,正确的步骤可以参考:http://www.imgeek.org/article/825308748。另外可以用推送工具进行验证。
8.如果以上都没有问题,可以尝试重新制作上传一下推送证书。
对照这些检查一下,基本上就是这些原因
如果上面几点都符合的话,看下重新登录之后是否可以收到之前收不到推送消息
可以的,话提供一下AppKey,证书名 (查下证书是否被封)以及收不到的推送消息的消息id及发送方和接收方log
log导出请看这篇文章: http://www.imgeek.org/article/825308785 然后转成txt格式上传到工单上,同时注明上述配置都正确 , 环信这边来查下消息推送记录

注意 :后台没有证书名 是指用户列表后面没有显示证书名。这个证书名是SDK初始化的时候传的字符串,用户登录之后会进行绑定。
如果用户没有绑定证书名的话,肯定收不到推送的。这个证书名是用户登录之后绑定的,要确认下初始化SDK的时候有没有传。 options.apnsCertName = apnsCertName;

==============常见问题==============

Q : iOS apns离线推送证书apns的离线推送可以和友盟(极光)推送共用一个证书吗?
A : 环信的推送只要和后台上传的证书对应就可以实现,其他的不关心。
首先苹果推送证书的生成都是统一的方式,这个不区分是极光(友盟)推送证书还是个推证书等等。使用的推送证书只要按照正确的苹果推送证书生成流程创建,都可以使用。
环信添加推送证书可以看http://www.imgeek.org/article/825308748 不是要求必须重新生成推送证书 
Q : 好友申请通知的离线推送?
A : 我们的好友体系,添加好友的申请不支持离线推送。
如果你们是使用App本身的好友体系,可以在app的添加好友业务上向被添加的好友发送文本消息,在EMMessage的ext中设置自定义字段,来区分此条文本消息是否用于好友申请提示,由此来判断处理UI的显示。

Q : iOS的杀死进程远程推送和服务端有关么
A : 如果客户端把远程通知给关了肯定就收不到通知,我们服务器会检测客户端是否有deviceToken,有的话才会把消息发送到deviceToken对应的设备上

Q : 每个项目创建了一个开发的推送证书一个生产的推送证书。这俩证书什么时候要做切换?
A : 在App上传AppStore前需要修改App内初始化SDK设置的推送证书名,EMOptions的apnsCertName。
注意,这里的值需要和在Console管理后天上传时设置的证书名一致。

Q : 绑定devicetoken的时候是否需要先登录到环信?
A : 绑定是需要登录过之后才进行的,
 - (EMError *)bindDeviceToken:(NSData *)aDeviceToken; 是把deviceToken传给SDK。调用登录,SDK会进行绑定。也可以调用 - (void)registerForRemoteNotificationsWithDeviceToken:(NSData *)aDeviceToken
completion:(void (^)(EMError *aError))aCompletionBlock;自己绑定
需要判断是否已经登录,如果已经有登录的账号,再登录会返回 已登录的错误。

Q : 离线推送在客户端怎么设置显示详情?
A :
 EMPushOptions *pushOptions = [[EMClient sharedClient] pushOptions];
pushOptions.displayStyle = EMPushDisplayStyleMessageSummary;

可以设置离线推送消息显示具体内容还是只显示-您收到一条消息
要设置在登录成功之后,然后要用服务器拉取一遍APNS 属性
EMError *error = nil;
EMPushOptions *options = [[EMClient sharedClient] getPushOptionsFromServerWithError:&error];

然后在修改displayStyle

Q : 两个APP通信,如果只希望其中一类APP能收到推送,而另一端的APP不希望收到推送,是不是不希望收到推送的APP不配置证书就好了?
A : 两个App的推送证书都是在同一appkey下单独配置的,如果不希望收到推送,可以对此App不配置推送证书,同时在App代码中注释掉注册远程通知的相关代码。
bundle id对应的证书也可以取消push的功能,针对App不使用任何远程推送服务,包括其他第三方的,如果App还需要其他第三方的推送服务,请忽略这句话。

Q : 不配置推送证书的APP是不是只有刷新的情况下才会显示新的消息?不刷新的情况下APP是看不到新的消息?
A : 不配置推送功能的App,只有在用户登录成功后,才能通过长连接的接收消息回掉中拿到消息体。

Q : 在开发环境下收到了离线推送消息,但是在生成环境下没有收到?
A : 看一下SDK初始化时,是否设置的apnsCerName与生产环境证书上传时填写的证书名一致,还有是否为adhoc打包成ipa文件安装测试的。

Q :发送消息1,2,3,4,5 对方收到推送 2 1 5 顺序不对而且丢失, app角标也不对
A : 1.首先,苹果不保证所有远程推送的到达率。这个可以看苹果官方文档。
Because the delivery of remote notifications is not guaranteed, never include sensitive data or data that can be retrieved by other means in your payload.
2.我们只保证,把离线消息执行远程推送,发给苹果服务器。苹果服务器是否能够百分之百把所有推送送达到指定移动端,这个根据苹果的策略,当 APNs 向你发送了多条推送,你的设备在 APNs 那里下线了,这时 APNs 到你的手机的链路上有多条任务堆积,APNs 的处理方式是,只保留最后一条消息推送给你,然后告知你推送数。那么其他消息会被APNs丢弃。
3.我们保证的是离线消息,当用户重新登录时,可以都接收到。
4. 如果需要找后台查询离线消息(前提接收方已绑定deviceToken)是否成功,需要提供离线消息的messageId,接收方环信id
5.我们的推送角标,是接收方的在服务端的离线消息数。
https://developer.apple.com/li ... 8-SW1
这是官方文档
20170605122458543-1.jpeg


Q11 :ios是怎么判断离线了 然后发推送的啊 有时候把应用杀掉后 半天收不到推送
A : rest可以查用户的状态,推送前提是此用户有devicetoken已经绑定成功
如果账号所有配置都没问题,杀掉后,其他人发的消息,过几秒就能看到推送
Q12 : Q11不管用会是什么原因呢
A : 配置,还有账号在我们这绑的deviceToken

Q : 多个app共用一个appkey 推送证书怎么配置呢
A : 后台可以上传多套推送证书。

Q :程序关闭后推送了一个消息,点击后怎样获取到环信传过来的数据
A : 需要用户点击横幅后,重新启动App,这时从- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中,获取字典launchOptions,UIApplicationLaunchOptionsRemoteNotificationKey这个key下的数据,就是aps的字典数据

二 . 消息回调
app的长连接存在的时候,环信服务器检测您为在线状态,是不会给app推送消息的。app端在线的情况下,消息会通过长连接直接收取(didreceivemessage),收到消息,SDK会通过回调通知给上层。app通过收消息的回调拿到消息对象,然后解析并展示UI。
目前我们不支持App切后台后,可以一直执行。
我们SDK在切后台后,实现[[EMClient sharedClient] applicationDidEnterBackground:application];,会保持,直到被系统释放iOS目前其他方式应该都无法去实现一直保持App的活跃状态了。

==============常见问题==============
Q : 本地推送声音设置在哪 ?一条消息推送两声
A : 推送声音设置的要自己实现,具体可参考demo里的ChatDemoHelper类和MainViewController类里的
- (void)showNotificationWithMessage:(EMMessage *)message方法,该方法中有发送本地推送做的一系列操作,本地通知怎么做的,本地通知触发几次,一条消息推送几声,一下接收到多条消息响几声,都需要用户自己实现.

Q : 视频通话,推送怎么实现?
iOS 3.2.3之后,如果在实时通话接收方不在线时,发送提醒。
A : 1.在发起实时音视频通话前,需要设置EMCallOptions对象属性isSendPushIfOffline为YES;
2.遵守协议EMCallBuilderDelegate,实现其中的- (void)callRemoteOffline:(NSString *)aRemoteName 委托方法。
3.在第2步的方法中向 aRemoteName用户发送单聊消息。
如果被叫方已注册远程通知且绑定deviceToken,会收到对应消息的APNs推送,点击横幅来唤醒App。
上面是接收方离线的情况。如果接收方长连接还未断开,只是App切到后台,需要在回调- (void)callDidReceive:(EMCallSession *)aSession中判断当前App是否在后台,如果是弹出本地通知。

Q : EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions]; //当对方不在线时,是否给对方发送离线消息和推送,并等待对方回应 options.isSendPushIfOffline = YES; [[EMClient sharedClient].callManager setCallOptions:options];isSendPushIfOffline设置为YES后,A用户呼叫B用户,B用户处于离线状态,但B用户没有收到推送。
A : 1. 先确接收方杀掉App后,文本消息是否能收到APNs推送。
2.在1点确认App杀掉可以收到推送前提下,确认实时音视频发送方代码执行顺序如下:
(1) EMCallOptions *callOptions = [[EMClient sharedClient].callManager getCallOptions];
callOptions.isSendPushIfOffline = YES;
callOptions.offlineMessageText = @"提示文本";//可选
[[EMClient sharedClient].callManager setCallOptions:callOptions];

(2) callManager调用
- (void)startVideoCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

或者
- (void)startVoiceCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

Q : 推送的提示音可不可以自定义啊。
A: 推送的提示音目前不支持自定义,本地通知的你们以自己去设置。
 
Q : app压后台,立刻收到聊天推送来的信息,点击通知栏信息,捕获不到唤起程序事件
A : App切后台后,长连接为断开前,当前弹出的横幅是本地通知,那么此时唤醒时间是本地通知的回调
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;

iOS10后
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler;

Q : 环信在离线状态下能收消息,但是程序运行状态按home按键进入后台的时候无法接受消息,怎么处理
A : App切入后台一段时间内,长连接还未断开,这时候接收消息都是通过SDK的接收消息回调(EMChatManagerDelegate)来收消息,不会执行APNs推送。
如果是需要弹出横幅提醒,需要在接收消息的回调方法中,判断[[UIApplication sharedApplication] applicationState]为UIApplicationStateBackground,然后实现本地通知。可以参考demo中的处理
(ChatDemoHelper的- (void)didReceiveMessages:(NSArray *)aMessages)
[attach]7500[/attach][size=13]


[/size]
0
评论

IM-SDK和客服SDK并存开发指南—iOS篇 环信移动客服 iOS

beyond 发表了文章 • 1295 次浏览 • 2017-06-06 10:47 • 来自相关话题

一、SDK 介绍
 
环信客服访客端SDK 基于 IM SDK 3.x , 如果同时使用客服访客端SDK和IM  SDK,只需要在初始化、登录、登出操作时使用客服访客端 SDK 提供的相应 API ,IM 的其他API 均不受影响。UI 部分集成需要分别导入HelpDeskUI 和 EaseUI(也可以自定义UI)。
 下面详细介绍IM 和 客服共存的开发步骤。
二、注意事项
 
开发过程中 初始化、登录和登出,务必使用客服访客端SDK 的API。IM SDK 和客服SDK 都包括了模拟器的CPU 架构,在上传到app store时需要剔除模拟器的CPU 架构,保留armv7、arm64。
 
三、资源准备
 
到环信官网下载客服访客端的开源的商城Demo源码 + SDK,下载链接:http://www.easemob.com/download/cs  选择“iOS客服访客端SDK”下载(如下图)。


到环信官网下载IM的开源的Demo源码 + SDK ,下载链接:http://www.easemob.com/download/im 选择iOS SDK(如下图)。  



四、资源简介
两个SDK 均包括基础版和实时音视频版,根据需求的不同需要导入不同的SDK组合。基础版命名在名称后边添加Lite标识。从官网下载的客服访客端SDK包括以下目录: helpdeskdemo-ios、HelpDeskFramework、HelpDeskUI和HyphenateFramework,分别表示:
- helpdeskdemo-ios 为包含实时音视频的Demo,可直接运行。- HelpDeskFramework 为客服访客端SDK,HelpDesk.framework包含实时音视频、HelpDeskLite.framework不包含实时音视频。- HelpDeskUI 为环信用户提供的单聊UI,可在集成的时候视情况使用。- HyphenateFramework 为客服SDK依赖库(IM SDK),Hyphenate.framework和HelpDesk.framework一起导入,HyphenateLite.framework和HelpDeskLite.framework一起导入。

下载的IM SDK+Demo,包含五个文件夹,并且和客服SDK 中都有 IM 的framework文件,为了保持版本的匹配,我们只使用其中的 EaseUI 而不使用IM SDK中 的framework文件。

五、集成步骤
 
参考客服访客端SDK文档集成客服的访客端SDK,文档地址:http://docs.easemob.com/cs/300visitoraccess/iossdk。将IM SDK 中的EaseUI 导入到工程中,需要在导入访客端客服SDK的地方导入#import "EaseUI.h"   [重要提示:IM 的初始化、登录、登出操作需要使用客服的相关API]在pch文件中引入
#ifdef __OBJC__
//包含实时音视频功能
#import <HelpDesk/HelpDesk.h>
//若不包含实时音视频,则替换为
#import <HelpDeskLite/HelpDeskLite.h>

#import "HelpDeskUI.h"
#import "EaseUI.h"
#endif
   4. 然后将EaseUI或者HelpDeskUI中的 FixFopen.c文件删除(重复冲突)。
   5. 由于HelpDeskUI 和 EaseUI 中使用了 第三方库,如果工程中出现三方重复的问题,可将HelpDeskUI 和 EaseUI  中的重复文件删除,如果部分接口已经升级或弃用可自行升级、调整。
 
六、工程设置
 
在General -> Embedded Binaries 中导入HelpDesk.framework和Hyphenate.framework(这是包含实时音视频的,如果不包含实时音视频则导入HelpDeskLite.framework和HyphenateLite.framework)。在Build Settings -> Linking -> Other Linker Flags 中增加 -ObjC项,注意区分大小写。客服 访客端SDK暂不支持bitcode,所以需要将Build Settings -> Build Options -> Enable Bitcode 设为NO。为了减少不必要的警告,可将Build Settings -> Documentation Comments 设为 NO。

提供的兼容Demo介绍:
 
Demo是在客服的商城Demo上修改,在左上角添加了一个聊天室的按钮,点击按钮会根据appkey随机创建一个账号并登录,登录成功后会进入聊天室列表界面,点击某个聊天室可以在聊天室中聊天。Demo中客服部分功能还是和原商城Demo功能一致。Demo中为了演示因此采用随机注册账号的方式,对于用户场景中,可以先注册好这些账号和自己的账号绑定,这样每次咨询客服就都是同一个人了,也可以显示这个访客曾经的聊天记录。
 
Demo源码地址:http://kefu-prod-apk.oss-cn-ha ... o.zip
  查看全部
一、SDK 介绍
 
  1. 环信客服访客端SDK 基于 IM SDK 3.x , 如果同时使用客服访客端SDK和IM  SDK,只需要在初始化、登录、登出操作时使用客服访客端 SDK 提供的相应 API ,IM 的其他API 均不受影响。
  2. UI 部分集成需要分别导入HelpDeskUI 和 EaseUI(也可以自定义UI)。

 下面详细介绍IM 和 客服共存的开发步骤。
二、注意事项
 
  1. 开发过程中 初始化、登录和登出,务必使用客服访客端SDK 的API。
  2. IM SDK 和客服SDK 都包括了模拟器的CPU 架构,在上传到app store时需要剔除模拟器的CPU 架构,保留armv7、arm64。

 
三、资源准备
 
  1. 到环信官网下载客服访客端的开源的商城Demo源码 + SDK,下载链接:http://www.easemob.com/download/cs  选择“iOS客服访客端SDK”下载(如下图)。
    01.jpg
  2. 到环信官网下载IM的开源的Demo源码 + SDK ,下载链接:http://www.easemob.com/download/im 选择iOS SDK(如下图)。  
    02.png

四、资源简介
  1. 两个SDK 均包括基础版和实时音视频版,根据需求的不同需要导入不同的SDK组合。基础版命名在名称后边添加Lite标识。
  2. 从官网下载的客服访客端SDK包括以下目录: helpdeskdemo-ios、HelpDeskFramework、HelpDeskUI和HyphenateFramework,分别表示:

  • - helpdeskdemo-ios 为包含实时音视频的Demo,可直接运行。
  • - HelpDeskFramework 为客服访客端SDK,HelpDesk.framework包含实时音视频、HelpDeskLite.framework不包含实时音视频。
  • - HelpDeskUI 为环信用户提供的单聊UI,可在集成的时候视情况使用。
  • - HyphenateFramework 为客服SDK依赖库(IM SDK),Hyphenate.framework和HelpDesk.framework一起导入,HyphenateLite.framework和HelpDeskLite.framework一起导入。


下载的IM SDK+Demo,包含五个文件夹,并且和客服SDK 中都有 IM 的framework文件,为了保持版本的匹配,我们只使用其中的 EaseUI 而不使用IM SDK中 的framework文件。

五、集成步骤
 
  1. 参考客服访客端SDK文档集成客服的访客端SDK,文档地址:http://docs.easemob.com/cs/300visitoraccess/iossdk
  2. 将IM SDK 中的EaseUI 导入到工程中,需要在导入访客端客服SDK的地方导入#import "EaseUI.h"   [重要提示:IM 的初始化、登录、登出操作需要使用客服的相关API]
  3. 在pch文件中引入

 #ifdef __OBJC__
//包含实时音视频功能
#import <HelpDesk/HelpDesk.h>
//若不包含实时音视频,则替换为
#import <HelpDeskLite/HelpDeskLite.h>

#import "HelpDeskUI.h"
#import "EaseUI.h"
#endif

   4. 然后将EaseUI或者HelpDeskUI中的 FixFopen.c文件删除(重复冲突)。
   5. 由于HelpDeskUI 和 EaseUI 中使用了 第三方库,如果工程中出现三方重复的问题,可将HelpDeskUI 和 EaseUI  中的重复文件删除,如果部分接口已经升级或弃用可自行升级、调整。
 
六、工程设置
 
  1. 在General -> Embedded Binaries 中导入HelpDesk.framework和Hyphenate.framework(这是包含实时音视频的,如果不包含实时音视频则导入HelpDeskLite.framework和HyphenateLite.framework)。
  2. 在Build Settings -> Linking -> Other Linker Flags 中增加 -ObjC项,注意区分大小写。
  3. 客服 访客端SDK暂不支持bitcode,所以需要将Build Settings -> Build Options -> Enable Bitcode 设为NO。
  4. 为了减少不必要的警告,可将Build Settings -> Documentation Comments 设为 NO。


提供的兼容Demo介绍:
 
  1. Demo是在客服的商城Demo上修改,在左上角添加了一个聊天室的按钮,点击按钮会根据appkey随机创建一个账号并登录,登录成功后会进入聊天室列表界面,点击某个聊天室可以在聊天室中聊天。
  2. Demo中客服部分功能还是和原商城Demo功能一致。
  3. Demo中为了演示因此采用随机注册账号的方式,对于用户场景中,可以先注册好这些账号和自己的账号绑定,这样每次咨询客服就都是同一个人了,也可以显示这个访客曾经的聊天记录。

 
Demo源码地址:http://kefu-prod-apk.oss-cn-ha ... o.zip
 
2
评论

集成环信遇到的相关问题整理 iOS

木云落 发表了文章 • 2916 次浏览 • 2017-05-22 16:36 • 来自相关话题

最近在整理这段时间被别人问到引入环信可能会出现的问题,记得的也不太多,想到一个就在这里记录一个吧,如果有遇到过本文中没有列出来的,可以问我,我会一一解答的
原文地址: http://blog.csdn.net/jyt199011 ... 83995

1. pod引入的Hyphenate里面的.h文件中和手动下载的sdk相比会缺少Hyphenate.h 。
A :  主要是pod 问题 本地仓库太旧了, 终端行pod repo update, 之后在pod search 'Hyphenate' 如果可以找到3.3.0版本, 就可以下载了 podfile 里面 platform 要指定8.0

2. iOS SDK 从低版本 升到3.3.0 后运行报错 (集成动态库版本报错)
dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate
  Referenced from: /Users/white/Library/Developer/CoreSimulator/Devices/BE0DDC26-96AE-4396-A6C5-48DC6938042B/data/Containers/Bundle/Application/4F9F570A-44B5-4F81-AD19-F7AA38D26E40/SYSchoolProject.app/SYSchoolProject
  Reason: image not found




A : 在Build setting -> General这里加上。 还有这里也加上 改不能成optional,
注意 : 改成optional之后会导致初始化为null






3.在AppDelegate中执行[EaseMob sharedInstance]崩溃
A : other link flags添加“-ObjC”选项(注意:O和C大写)


4. pod导入EaseUI 时报错 
A : 先进入Podfile文件中,添加pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git' ,保存退出之后执行pod update即可 ,如果还是失败,可以升级一下pod版本





5.‘Hyphenate/EMSDK.h’ file no found
A : 换下引用#import <HyphenateLite/HyphenateLite.h>
     或者#import <Hyphenate/Hyphenate.h>
     如果此方法不行, 可以试试选中你的项目中的Pods -> EaseUI->Build Phases->Link Binary With Libraries ,点➕->Add Other ,找到工程里面,Pods里面的Hyphenate文件夹下面的Hyphenate.framework 点击open,重新编译就好了






6. 




A :  可以参考问题2的基础上, 再看下相对路径那里


7.集成动态库上传AppStore出现问题, 打包上线时报错
ERROR ITMS-90087: "Unsupported Architectures. The executable for xiantaiApp.app/Frameworks/Hyphenate.framework contains unsupported architectures '[x86_64, i386]'."
A :  遇到这个问题的小伙伴一定是没有认真看咱们环信的官方文档,
由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交审核

在SDK当前路径下执行以下命令删除i386 x86_64两个平台
实时音视频版本Hyphenate.frameworklipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7 lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64 lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate mv Hyphenate Hyphenate.framework/
 
不包含实时音视频版本HyphenateLite.frameworklipo HyphenateLite.framework/HyphenateLite -thin armv7 -output HyphenateLite_armv7 lipo HyphenateLite.framework/HyphenateLite -thin arm64 -output HyphenateLite_arm64 lipo -create HyphenateLite_armv7 HyphenateLite_arm64 -output HyphenateLite mv HyphenateLite HyphenateLite.framework/
拿实时音视频版本版本为例 : 执行完以上命令如图所示




运行完毕后得到的Hyphenate.framework就是最后的结果,拖进工程,编译打包上架。




注意 : 
1. 最后得到的包必须真机编译运行,并且工程要设置编译二进制文件General->Embedded Bunaries.
2. 删除i386、x86_64平台后,SDK会无法支持模拟器编译,只需要在上传AppStore时在进行删除,上传后,替换为删除前的SDK,建议先分别把i386、x86_64、arm64、armv7各平台的包拆分到本地,上传App Store时合并arm64、armv7平台,并移入Hyphenate.framework内。上传后,重新把各平台包合并移入动态库


打包时还有可能报这个错误
ERROR ITMS-90535: "Unexpected CFBundleExecutable Key. The bundle at 'Payload/xiantaiApp.app/EaseUIResource.bundle' does not contain a bundle executable. If this bundle intentionally does not contain an executable, consider removing the CFBundleExecutable key from its Info.plist and using a CFBundlePackageType of BNDL. If this bundle is part of a third-party framework, consider contacting the developer of the framework for an update to address this issue."
A :  ​从EaseUIResource.bundle中找到info.plist删掉CFBundleExecutable,或者整个info.plist删掉



8.ios apns推送是什么原因导致这个错误
注册deviceToken失败:application:didFailToRegisterForRemoteNotificationsWithError: Error Domain=NSCocoaErrorDomain Code=3000 "未找到应用程序的“aps-environment”的授权字符串" UserInfo={NSLocalizedDescription=未找到应用程序的“aps-environment”的授权字符串}
A: 工程配置没有打开推送功能。

9.运行demo报这个错误




A: 没有存储空间了。
 
 
10. SDK3.3.1 以上版本手动导入EaseUI报错
A : 由于demo是用pod集成的,所以直接引入demo中的EaseUI会缺少相关文件,可以直接拖入附件中的EaseUI
如果引入之后报如下图的错误








其实碰到上面这个问题还是很好解决的,这个是因为用到了UIKit里的类,但是只导入了Foundation框架,这个错误在其他类里也会出现,我们可以手动修改Founfation为UIKit,但是我不建议这么做,第一这个做法的工程量比较大, 在其他类里面也要导入,二,不利于移植,当以后环信更新的时候我们还是需要做同样的操作,这里我的做法的创建一个pch文件,在pch文件里面导入UIKit。解决办法:建一个PCH文件在里面添加如下代码:





以上应该会正常了,但是如果集成的是不包含实时音视频的SDK, 您导入的EaseUI不是Lite版的,  那么此时还会报跟第六点一样的错误 , 需要导入EaseUILite 版本,所以会找不到Hyphenate/Hyphenate.h,如果是手动集成,建议在xcode设置一下Build Settings> GCC_PREPROCESSOR_DEFINITIONS >ENABLE_LITE=1,这样easeui就去找HyphenateLite/HyphenateLite.h
也可以通过pod集成,文档上针对easeui集成Full版本和Lite版本sdk特殊的说明http://docs.easemob.com/im/300 ... guide
 
或者不想导入Lite版的 , 只想引入EaseUI 
这时需要把 #import <Hyphenate/Hyphenate.h>注释掉,然后把报错地方的Hyphenate换成HyphenateLite就可以了
 
 
11. 




A : 可以删除或者重命名Podfile.lock文件,重新执行pod install命令 查看全部
最近在整理这段时间被别人问到引入环信可能会出现的问题,记得的也不太多,想到一个就在这里记录一个吧,如果有遇到过本文中没有列出来的,可以问我,我会一一解答的
原文地址: http://blog.csdn.net/jyt199011 ... 83995

1. pod引入的Hyphenate里面的.h文件中和手动下载的sdk相比会缺少Hyphenate.h 。
A :  主要是pod 问题 本地仓库太旧了, 终端行pod repo update, 之后在pod search 'Hyphenate' 如果可以找到3.3.0版本, 就可以下载了 podfile 里面 platform 要指定8.0

2. iOS SDK 从低版本 升到3.3.0 后运行报错 (集成动态库版本报错)
dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate
  Referenced from: /Users/white/Library/Developer/CoreSimulator/Devices/BE0DDC26-96AE-4396-A6C5-48DC6938042B/data/Containers/Bundle/Application/4F9F570A-44B5-4F81-AD19-F7AA38D26E40/SYSchoolProject.app/SYSchoolProject
  Reason: image not found
20170330110241533.jpeg

A : 在Build setting -> General这里加上。 还有这里也加上 改不能成optional,
注意 : 改成optional之后会导致初始化为null
20170330104308569.jpeg



3.在AppDelegate中执行[EaseMob sharedInstance]崩溃
A : other link flags添加“-ObjC”选项(注意:O和C大写)


4. pod导入EaseUI 时报错 
A : 先进入Podfile文件中,添加pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git' ,保存退出之后执行pod update即可 ,如果还是失败,可以升级一下pod版本
屏幕快照_2017-05-22_上午10.27_.07_.png


5.‘Hyphenate/EMSDK.h’ file no found
A : 换下引用#import <HyphenateLite/HyphenateLite.h>
     或者#import <Hyphenate/Hyphenate.h>
     如果此方法不行, 可以试试选中你的项目中的Pods -> EaseUI->Build Phases->Link Binary With Libraries ,点➕->Add Other ,找到工程里面,Pods里面的Hyphenate文件夹下面的Hyphenate.framework 点击open,重新编译就好了
20170331200729906.jpeg



6. 
20170331110834145.jpeg

A :  可以参考问题2的基础上, 再看下相对路径那里


7.集成动态库上传AppStore出现问题, 打包上线时报错
ERROR ITMS-90087: "Unsupported Architectures. The executable for xiantaiApp.app/Frameworks/Hyphenate.framework contains unsupported architectures '[x86_64, i386]'."
A :  遇到这个问题的小伙伴一定是没有认真看咱们环信的官方文档,
由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交审核

在SDK当前路径下执行以下命令删除i386 x86_64两个平台
实时音视频版本Hyphenate.frameworklipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7 lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64 lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate mv Hyphenate Hyphenate.framework/
 
不包含实时音视频版本HyphenateLite.frameworklipo HyphenateLite.framework/HyphenateLite -thin armv7 -output HyphenateLite_armv7 lipo HyphenateLite.framework/HyphenateLite -thin arm64 -output HyphenateLite_arm64 lipo -create HyphenateLite_armv7 HyphenateLite_arm64 -output HyphenateLite mv HyphenateLite HyphenateLite.framework/
拿实时音视频版本版本为例 : 执行完以上命令如图所示
20170401112052481.png

运行完毕后得到的Hyphenate.framework就是最后的结果,拖进工程,编译打包上架。
20170401112216045.png

注意 : 
1. 最后得到的包必须真机编译运行,并且工程要设置编译二进制文件General->Embedded Bunaries.
2. 删除i386、x86_64平台后,SDK会无法支持模拟器编译,只需要在上传AppStore时在进行删除,上传后,替换为删除前的SDK,建议先分别把i386、x86_64、arm64、armv7各平台的包拆分到本地,上传App Store时合并arm64、armv7平台,并移入Hyphenate.framework内。上传后,重新把各平台包合并移入动态库


打包时还有可能报这个错误
ERROR ITMS-90535: "Unexpected CFBundleExecutable Key. The bundle at 'Payload/xiantaiApp.app/EaseUIResource.bundle' does not contain a bundle executable. If this bundle intentionally does not contain an executable, consider removing the CFBundleExecutable key from its Info.plist and using a CFBundlePackageType of BNDL. If this bundle is part of a third-party framework, consider contacting the developer of the framework for an update to address this issue."
A :  ​从EaseUIResource.bundle中找到info.plist删掉CFBundleExecutable,或者整个info.plist删掉



8.ios apns推送是什么原因导致这个错误
注册deviceToken失败:application:didFailToRegisterForRemoteNotificationsWithError: Error Domain=NSCocoaErrorDomain Code=3000 "未找到应用程序的“aps-environment”的授权字符串" UserInfo={NSLocalizedDescription=未找到应用程序的“aps-environment”的授权字符串}
A: 工程配置没有打开推送功能。

9.运行demo报这个错误
20170519110027739.png

A: 没有存储空间了。
 
 
10. SDK3.3.1 以上版本手动导入EaseUI报错
A : 由于demo是用pod集成的,所以直接引入demo中的EaseUI会缺少相关文件,可以直接拖入附件中的EaseUI
如果引入之后报如下图的错误
10.1_.png

10.2_.png

其实碰到上面这个问题还是很好解决的,这个是因为用到了UIKit里的类,但是只导入了Foundation框架,这个错误在其他类里也会出现,我们可以手动修改Founfation为UIKit,但是我不建议这么做,第一这个做法的工程量比较大, 在其他类里面也要导入,二,不利于移植,当以后环信更新的时候我们还是需要做同样的操作,这里我的做法的创建一个pch文件,在pch文件里面导入UIKit。解决办法:建一个PCH文件在里面添加如下代码:
10.3_.png


以上应该会正常了,但是如果集成的是不包含实时音视频的SDK, 您导入的EaseUI不是Lite版的,  那么此时还会报跟第六点一样的错误 , 需要导入EaseUILite 版本,所以会找不到Hyphenate/Hyphenate.h,如果是手动集成,建议在xcode设置一下Build Settings> GCC_PREPROCESSOR_DEFINITIONS >ENABLE_LITE=1,这样easeui就去找HyphenateLite/HyphenateLite.h
也可以通过pod集成,文档上针对easeui集成Full版本和Lite版本sdk特殊的说明http://docs.easemob.com/im/300 ... guide
 
或者不想导入Lite版的 , 只想引入EaseUI 
这时需要把 #import <Hyphenate/Hyphenate.h>注释掉,然后把报错地方的Hyphenate换成HyphenateLite就可以了
 
 
11. 
1.png

A : 可以删除或者重命名Podfile.lock文件,重新执行pod install命令
0
评论

环信聊天游客身份和正常用户身份的切换 环信聊天游客身份与正常用户身份的切换 iOS

奋斗的蜗牛 发表了文章 • 280 次浏览 • 2017-05-10 20:39 • 来自相关话题

 
   最近搞环信聊天,需求是游客身份也可以进行聊天,当用户注册了我们的APP后也需要把游客身份切换过来进行聊天,首先我们的环信注册,登录全都放前段处理了,下面就按照我们的需求逻辑来如何切换游客。
 
   1.APP用户的注册,也就注册环信,APP的登录返回的有用户ID,这个时候并没有让他登录环信,只是保存了返回的ID,下面就是用ID来判断该用户是否注册过环信的依据
 
大致说明一下,代码中用到一个类来保证uuid不会改变的状态,为防止app卸载后uuid的改变,我们把他存储到钥匙串里面来保存

下面用图来表示
我们先来看下整个身份切换实现的逻辑图





下面就上代码了,第一步从图中第一步来说判断userID是否存在

这个地方是在点击聊天按钮开始判断的
 -(void)releaseInfo:(UIButton*)sender{
NSString*Hxusername=[userdic objectForKey:@"useid"];//获取保存的userID
NSString*phonestr=  [[NSUserDefaults standardUserDefaults]objectForKey:@"phonenum"];
NSString*chatid=[[phonestr md5String]substringFromIndex:16];//这个是获取客服的欢信ID
//单例里面处理用户是否登录,以及游客随机分配uuid来注册环信IM号
DataManager*datamage= [DataManager shareDataManager];
//判断用户ID是否存在,也就证明是否注册过环信
if (Hxusername.length>0) {
if ([datamage loginKefuSDK])//判断用户是否登录
{//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}else{
//游客身份的判断
if ([datamage customelogin]) {
//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}
}上面这是按钮方法里面的数据下面来说,DataManager*datamage= [DataManager shareDataManager];这个单利的方法DataManager.h
@interface DataManager : NSObject
-(BOOL)customelogin;//判断游客之前是否有登录
-(void)requestchattphone;//获取美容院客服聊天的对象电话
@endDataManager.m


@implementation DataManager+(instancetype)shareDataManager{
static DataManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[DataManager alloc] init];
});
return manager;
}//userID存在的时候 登录IM
- (BOOL)loginKefuSDK {
NSDictionary*userdic=[[NSUserDefaults standardUserDefaults]objectForKey:@"userMessage"];//接受用户是否登录
NSString*loguser=[NSString stringWithFormat:@"%@",[userdic objectForKey:@"useid"] ];
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
if ([loguser isEqualToString:client.currentUsername])//当前登录用户的ID和即将要登录人的ID是否一样
{
return YES;
}else
{
EMError *error = [[EMClient sharedClient] logout:YES];
if (!error) {
NSLog(@"退出成功");
}
}
}//这里APP用户登录环信的密码统统是123456
EMError *error = [[EMClient sharedClient] loginWithUsername:loguser password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}//游客身份的登录方法
-(BOOL)customelogin
{
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
return YES;
}//该用户没有注册,来用改设备UUID来给用户注册环信,并登录环信
if (![self registerIMuser]) {
return NO;
}
EMError *error = [[EMClient sharedClient] loginWithUsername:self.Hxusername password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}- (BOOL)registerIMuser { //举个栗子。注册建议在服务端创建环信id与自己app的账号一一对应,\
而不要放到APP中,可以在登录自己APP时从返回的结果中获取环信账号再登录环信服务器
EMError *error = nil;
NSString *newUser = [self getrandomUsername];
self.Hxusername = newUser;
error = [[EMClient sharedClient] registerWithUsername:newUser password:@"123456"];
if (error &&  error.code != EMErrorUserAlreadyExist) {
NSLog(@"注册失败;error code:%d,error description :%@",error.code,error.errorDescription);
return NO;
}return YES;
}
//创建一个随机的用户名,这里是设备UUID来代替的​- (NSString *)getrandomUsername {
//第一种方法:
/*NSString *username = nil;
UIDevice *device = [UIDevice currentDevice];//创建设备对象
NSString *deviceUID = [[NSString alloc] initWithString:[[device identifierForVendor] UUIDString]];
if ([deviceUID length] == 0) {
CFUUIDRef uuid = CFUUIDCreate(NULL);
if (uuid)
{
deviceUID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
}
username = [deviceUID stringByReplacingOccurrencesOfString:@"-" withString:@""];
username = [username stringByAppendingString:[NSString stringWithFormat:@"%u",arc4random()0000]];
return username;*/
//第二种方法
//加上build ID是为了保证设备的唯一性,如果这里的buildID换了,设备的uuid也会变,这里的解决办法也就是放倒了钥匙串里面,不会因卸载程序,程序升级设备的标识会改变
NSString *SERVICE_NAME = NAVI_TEST_BUNDLE_ID;//最好用程序的bundle id
NSString * str =  [SFHFKeychainUtils getPasswordForUsername:@"UUID" andServiceName:SERVICE_NAME error:nil];  // 从keychain获取数据
if ([str length]<=0)
{
str  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];  // 保存UUID作为手机唯一标识符[SFHFKeychainUtils storeUsername:@"UUID"   andPassword:str    forServiceName:SERVICE_NAME updateExisting:1  error:nil];  // 往keychain添加数据
}
str = [str stringByReplacingOccurrencesOfString:@"-" withString:@""];
return str;
}在这里用到了一个类来处理的UUID不变(APP卸载后不会改变)
SFHFKeychainUtils.h
#import@interface SFHFKeychainUtils : NSObject
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@endSFHFKeychainUtils.m​#import "SFHFKeychainUtils.h"
static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
@interface SFHFKeychainUtils (PrivateMethods)
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@end
#endif
@implementation SFHFKeychainUtils
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { if (!username || !serviceName) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; return nil; }      SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];      if (*error || !item) { return nil; }
// from Advanced Mac OS X Programming, ch. 16
UInt32 length;
char *password;
SecKeychainAttribute attributes[8];
SecKeychainAttributeList list;
attributes[0].tag = kSecAccountItemAttr;
attributes[1].tag = kSecDescriptionItemAttr;
attributes[2].tag = kSecLabelItemAttr;
attributes[3].tag = kSecModDateItemAttr;
list.count = 4;
list.attr = attributes;
OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
if (status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return nil;
}
NSString *passwordString = nil;
if (password != NULL)
{ char passwordBuffer[1024];
if (length > 1023) {length = 1023;
}
strncpy(passwordBuffer, password, length);
passwordBuffer[length] = '\0';
passwordString = [NSString stringWithCString:passwordBuffer];
}SecKeychainItemFreeContent(&list, password);CFRelease(item);return passwordString;
}
+ (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {
if (!username || !password || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return;
}
OSStatus status = noErr;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
*error = nil;
if (item) {
status = SecKeychainItemModifyAttributesAndData(item,
NULL,
strlen([password UTF8String]),
[password UTF8String]);
CFRelease(item);
}
else {
status = SecKeychainAddGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
strlen([password UTF8String]),
[password UTF8String],
NULL);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];
return;
}
*error = nil;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
OSStatus status;
if (item) {
status = SecKeychainItemDelete(item);
CFRelease(item);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return nil;
}
*error = nil;
SecKeychainItemRef item;
OSStatus status = SecKeychainFindGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
NULL,
NULL,
&item);
if (status != noErr) {
if (status != errSecItemNotFound) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
return item;
}
#else
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return nil;
}
if (error != nil) {
*error = nil;
}
// Set up a query dictionary with the base query attributes: item type (generic), username, and service
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil];
NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];
// First do a query for attributes, in case we already have a Keychain item with no password data set.
// One likely way such an incorrect item could have come about is due to the previous (incorrect)
// version of this code (which set the password as a generic attribute instead of password data).
NSMutableDictionary *attributeQuery = [query mutableCopy];
[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes];
CFTypeRef attrResult = NULL;
OSStatus status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) attributeQuery, &attrResult);
//NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult;
if (status != noErr) {
// No existing item found--simply return nil for the password
if (error != nil && status != errSecItemNotFound) {
//Only return an error if a real exception happened--not simply for "not found."
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
// We have an existing item, now query for the password data associated with it.
NSMutableDictionary *passwordQuery = [query mutableCopy];
[passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData];
CFTypeRef resData = NULL;
status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData);
NSData *resultData = (__bridge_transfer NSData *)resData;
if (status != noErr) {
if (status == errSecItemNotFound) {
// We found attributes for the item previously, but no password now, so return a special error.
// Users of this API will probably want to detect this error and prompt the user to
// re-enter their credentials.  When you attempt to store the re-entered credentials
// using storeUsername:andPassword:forServiceName:updateExisting:error
// the old, incorrect entry will be deleted and a new one with a properly encrypted
// password will be added.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
else {
// Something else went wrong. Simply return the normal Keychain API error code.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
return nil;
}
NSString *password = nil;
if (resultData) {
password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
}
else {
// There is an existing item, but we weren't able to get password data for it for some reason,
// Possibly as a result of an item being incorrectly entered by the previous code.
// Set the -1999 error so the code above us can prompt the user again.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
return password;
}
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error
{
if (!username || !password || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
// See if we already have a password entered for these credentials.
NSError *getError = nil;
NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
if ([getError code] == -1999)
{
// There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
// Delete the existing item before moving on entering a correct one.
getError = nil;
[self deleteItemForUsername: username andServiceName: serviceName error: &getError];
if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
}
else if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
if (error != nil)
{
*error = nil;
}
OSStatus status = noErr;
if (existingPassword)
{
// We have an existing, properly entered item with a password.
// Update the existing item.
if (![existingPassword isEqualToString:password] && updateExisting)
{
//Only update if we're allowed to update existing.  If not, simply do nothing.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemUpdate((__bridge_retained CFDictionaryRef) query, (__bridge_retained CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]);
}
}
else
{
// No existing entry (or an existing, improperly entered, and therefore now
// deleted, entry).  Create a new entry.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
kSecValueData,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
[password dataUsingEncoding: NSUTF8StringEncoding],
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemAdd((__bridge_retained CFDictionaryRef) query, NULL);
}
if (error != nil && status != noErr)
{
// Something went wrong with adding the new item. Return the Keychain error code.
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error
{
if (!username || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
if (error != nil)
{
*error = nil;
}
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef) query);
if (error != nil && status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
#endif
@end 查看全部
 
   最近搞环信聊天,需求是游客身份也可以进行聊天,当用户注册了我们的APP后也需要把游客身份切换过来进行聊天,首先我们的环信注册,登录全都放前段处理了,下面就按照我们的需求逻辑来如何切换游客。
 
   1.APP用户的注册,也就注册环信,APP的登录返回的有用户ID,这个时候并没有让他登录环信,只是保存了返回的ID,下面就是用ID来判断该用户是否注册过环信的依据
 
大致说明一下,代码中用到一个类来保证uuid不会改变的状态,为防止app卸载后uuid的改变,我们把他存储到钥匙串里面来保存

下面用图来表示
我们先来看下整个身份切换实现的逻辑图
4861502-fa2f7d87d00c78d7.jpg


下面就上代码了,第一步从图中第一步来说判断userID是否存在

这个地方是在点击聊天按钮开始判断的
 
-(void)releaseInfo:(UIButton*)sender{
NSString*Hxusername=[userdic objectForKey:@"useid"];//获取保存的userID
NSString*phonestr=  [[NSUserDefaults standardUserDefaults]objectForKey:@"phonenum"];
NSString*chatid=[[phonestr md5String]substringFromIndex:16];//这个是获取客服的欢信ID
//单例里面处理用户是否登录,以及游客随机分配uuid来注册环信IM号
DataManager*datamage= [DataManager shareDataManager];
//判断用户ID是否存在,也就证明是否注册过环信
if (Hxusername.length>0) {
if ([datamage loginKefuSDK])//判断用户是否登录
{//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}else{
//游客身份的判断
if ([datamage customelogin]) {
//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}
}上面这是按钮方法里面的数据下面来说,DataManager*datamage= [DataManager shareDataManager];这个单利的方法
DataManager.h
@interface DataManager : NSObject
-(BOOL)customelogin;//判断游客之前是否有登录
-(void)requestchattphone;//获取美容院客服聊天的对象电话
@end
DataManager.m


@implementation DataManager
+(instancetype)shareDataManager{
static DataManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[DataManager alloc] init];
});
return manager;
}
//userID存在的时候 登录IM
- (BOOL)loginKefuSDK {
NSDictionary*userdic=[[NSUserDefaults standardUserDefaults]objectForKey:@"userMessage"];//接受用户是否登录
NSString*loguser=[NSString stringWithFormat:@"%@",[userdic objectForKey:@"useid"] ];
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
if ([loguser isEqualToString:client.currentUsername])//当前登录用户的ID和即将要登录人的ID是否一样
{
return YES;
}else
{
EMError *error = [[EMClient sharedClient] logout:YES];
if (!error) {
NSLog(@"退出成功");
}
}
}//这里APP用户登录环信的密码统统是123456
EMError *error = [[EMClient sharedClient] loginWithUsername:loguser password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}
//游客身份的登录方法
-(BOOL)customelogin
{
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
return YES;
}//该用户没有注册,来用改设备UUID来给用户注册环信,并登录环信
if (![self registerIMuser]) {
return NO;
}
EMError *error = [[EMClient sharedClient] loginWithUsername:self.Hxusername password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}
- (BOOL)registerIMuser { //举个栗子。注册建议在服务端创建环信id与自己app的账号一一对应,\
而不要放到APP中,可以在登录自己APP时从返回的结果中获取环信账号再登录环信服务器
EMError *error = nil;
NSString *newUser = [self getrandomUsername];
self.Hxusername = newUser;
error = [[EMClient sharedClient] registerWithUsername:newUser password:@"123456"];
if (error &&  error.code != EMErrorUserAlreadyExist) {
NSLog(@"注册失败;error code:%d,error description :%@",error.code,error.errorDescription);
return NO;
}return YES;
}
//创建一个随机的用户名,这里是设备UUID来代替的​
- (NSString *)getrandomUsername {
//第一种方法:
/*NSString *username = nil;
UIDevice *device = [UIDevice currentDevice];//创建设备对象
NSString *deviceUID = [[NSString alloc] initWithString:[[device identifierForVendor] UUIDString]];
if ([deviceUID length] == 0) {
CFUUIDRef uuid = CFUUIDCreate(NULL);
if (uuid)
{
deviceUID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
}
username = [deviceUID stringByReplacingOccurrencesOfString:@"-" withString:@""];
username = [username stringByAppendingString:[NSString stringWithFormat:@"%u",arc4random()0000]];
return username;*/
//第二种方法
//加上build ID是为了保证设备的唯一性,如果这里的buildID换了,设备的uuid也会变,这里的解决办法也就是放倒了钥匙串里面,不会因卸载程序,程序升级设备的标识会改变
NSString *SERVICE_NAME = NAVI_TEST_BUNDLE_ID;//最好用程序的bundle id
NSString * str =  [SFHFKeychainUtils getPasswordForUsername:@"UUID" andServiceName:SERVICE_NAME error:nil];  // 从keychain获取数据
if ([str length]<=0)
{
str  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];  // 保存UUID作为手机唯一标识符[SFHFKeychainUtils storeUsername:@"UUID"   andPassword:str    forServiceName:SERVICE_NAME updateExisting:1  error:nil];  // 往keychain添加数据
}
str = [str stringByReplacingOccurrencesOfString:@"-" withString:@""];
return str;
}在这里用到了一个类来处理的UUID不变(APP卸载后不会改变)
SFHFKeychainUtils.h
#import@interface SFHFKeychainUtils : NSObject
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@end
SFHFKeychainUtils.m​
#import "SFHFKeychainUtils.h"
static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
@interface SFHFKeychainUtils (PrivateMethods)
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@end
#endif
@implementation SFHFKeychainUtils
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { if (!username || !serviceName) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; return nil; }      SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];      if (*error || !item) { return nil; }
// from Advanced Mac OS X Programming, ch. 16
UInt32 length;
char *password;
SecKeychainAttribute attributes[8];
SecKeychainAttributeList list;
attributes[0].tag = kSecAccountItemAttr;
attributes[1].tag = kSecDescriptionItemAttr;
attributes[2].tag = kSecLabelItemAttr;
attributes[3].tag = kSecModDateItemAttr;
list.count = 4;
list.attr = attributes;
OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
if (status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return nil;
}
NSString *passwordString = nil;
if (password != NULL)
{ char passwordBuffer[1024];
if (length > 1023) {length = 1023;
}
strncpy(passwordBuffer, password, length);
passwordBuffer[length] = '\0';
passwordString = [NSString stringWithCString:passwordBuffer];
}SecKeychainItemFreeContent(&list, password);CFRelease(item);return passwordString;
}
+ (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {
if (!username || !password || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return;
}
OSStatus status = noErr;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
*error = nil;
if (item) {
status = SecKeychainItemModifyAttributesAndData(item,
NULL,
strlen([password UTF8String]),
[password UTF8String]);
CFRelease(item);
}
else {
status = SecKeychainAddGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
strlen([password UTF8String]),
[password UTF8String],
NULL);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];
return;
}
*error = nil;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
OSStatus status;
if (item) {
status = SecKeychainItemDelete(item);
CFRelease(item);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return nil;
}
*error = nil;
SecKeychainItemRef item;
OSStatus status = SecKeychainFindGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
NULL,
NULL,
&item);
if (status != noErr) {
if (status != errSecItemNotFound) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
return item;
}
#else
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return nil;
}
if (error != nil) {
*error = nil;
}
// Set up a query dictionary with the base query attributes: item type (generic), username, and service
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil];
NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];
// First do a query for attributes, in case we already have a Keychain item with no password data set.
// One likely way such an incorrect item could have come about is due to the previous (incorrect)
// version of this code (which set the password as a generic attribute instead of password data).
NSMutableDictionary *attributeQuery = [query mutableCopy];
[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes];
CFTypeRef attrResult = NULL;
OSStatus status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) attributeQuery, &attrResult);
//NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult;
if (status != noErr) {
// No existing item found--simply return nil for the password
if (error != nil && status != errSecItemNotFound) {
//Only return an error if a real exception happened--not simply for "not found."
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
// We have an existing item, now query for the password data associated with it.
NSMutableDictionary *passwordQuery = [query mutableCopy];
[passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData];
CFTypeRef resData = NULL;
status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData);
NSData *resultData = (__bridge_transfer NSData *)resData;
if (status != noErr) {
if (status == errSecItemNotFound) {
// We found attributes for the item previously, but no password now, so return a special error.
// Users of this API will probably want to detect this error and prompt the user to
// re-enter their credentials.  When you attempt to store the re-entered credentials
// using storeUsername:andPassword:forServiceName:updateExisting:error
// the old, incorrect entry will be deleted and a new one with a properly encrypted
// password will be added.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
else {
// Something else went wrong. Simply return the normal Keychain API error code.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
return nil;
}
NSString *password = nil;
if (resultData) {
password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
}
else {
// There is an existing item, but we weren't able to get password data for it for some reason,
// Possibly as a result of an item being incorrectly entered by the previous code.
// Set the -1999 error so the code above us can prompt the user again.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
return password;
}
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error
{
if (!username || !password || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
// See if we already have a password entered for these credentials.
NSError *getError = nil;
NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
if ([getError code] == -1999)
{
// There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
// Delete the existing item before moving on entering a correct one.
getError = nil;
[self deleteItemForUsername: username andServiceName: serviceName error: &getError];
if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
}
else if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
if (error != nil)
{
*error = nil;
}
OSStatus status = noErr;
if (existingPassword)
{
// We have an existing, properly entered item with a password.
// Update the existing item.
if (![existingPassword isEqualToString:password] && updateExisting)
{
//Only update if we're allowed to update existing.  If not, simply do nothing.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemUpdate((__bridge_retained CFDictionaryRef) query, (__bridge_retained CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]);
}
}
else
{
// No existing entry (or an existing, improperly entered, and therefore now
// deleted, entry).  Create a new entry.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
kSecValueData,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
[password dataUsingEncoding: NSUTF8StringEncoding],
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemAdd((__bridge_retained CFDictionaryRef) query, NULL);
}
if (error != nil && status != noErr)
{
// Something went wrong with adding the new item. Return the Keychain error code.
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error
{
if (!username || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
if (error != nil)
{
*error = nil;
}
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef) query);
if (error != nil && status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
#endif
@end
0
评论

炸窝了,苹果禁止使用热更新 iOS 热更新

新闻资讯 发表了文章 • 348 次浏览 • 2017-03-09 11:41 • 来自相关话题

今天一早,不少iOS开发群都炸窝了,原因是部分iOS开发者收到了苹果的警告邮件:




有开发者质疑可能是项目中使用了JSPatch、weex以及ReactNative等热更新技术。对于修复bug提交审核的开发者来说,热更新技术可以帮开发者避免长时间的审核等待以及多次被拒造成的成本开销。但也给黑客留了后门,也就违反了苹果的安全和隐私政策。

不过这次苹果只是对使用热更新的应用进行了警告,并没有开发者反应产品因此问题被下架。

对此,开发者表示:

舞小月:苹果注重的就是流畅性和用户体验,混编做的东西肯定没有native的流畅,这就违背了苹果本来的意愿,被禁也是正常的,而且苹果自己的蛋糕为何要分给竞争对手?以前没混编的时候你该怎么做不还是做了,现在没有,不代表以后没有,就像之前没有混编,后来有了混编。新的框架苹果自然也会去完善,苹果既然做了这个决定,他肯定会优化自己的东西。

Gilbertat:苹果爸爸会不会在自己的生态中搞死js啊

luohui8891:我们也是昨天收到的,目前没有什么对策。我们的APP只是用JSPatch做热修复,并不修改应用的功能行为等(但我觉得Apple并不care这个)。

lsllsllsl:没用RN没用JSPatch,同样收到警告。

luohui8891:@tcathy 根据邮件里说是你下次提交前请去掉这样远程下载代码运行的机制。所以应该就是下个版本如果不删除就reject

Loooren:早上收到邮件,itunesconnect站内信,电话通知....用到了weex

xiaofuyesnew:昨天晚上微软发布了Visual Studio 2017,自带基于React Native的iOS开发功能。鉴于微软这两年来开源的力度,发布这一功能似乎是在抢占开发者的市场,基于vs2017,在非苹果上开发ios应用更容易了。所以,苹果在这个节骨眼发出这个警告邮件,就有点威胁现有开发者的意思。暗地里想跟微软互怼。

对于那些已经在学习RN、weex、JSPatch的同学来说,这是个悲惨的故事









从苹果的角度看,禁止应用使用热更新技术更多是为了保护用户隐私、数据安全以及其全力打造的生态圈。对于用户来说,出于安全起见,应谨慎授予应用权限;对于开发者来说,为了审核以及长远的用户体验考虑,不要轻易触碰苹果拉的那条红线。




以上内容来源于CocoaChina,GitHub 查看全部
今天一早,不少iOS开发群都炸窝了,原因是部分iOS开发者收到了苹果的警告邮件:
001.png

有开发者质疑可能是项目中使用了JSPatch、weex以及ReactNative等热更新技术。对于修复bug提交审核的开发者来说,热更新技术可以帮开发者避免长时间的审核等待以及多次被拒造成的成本开销。但也给黑客留了后门,也就违反了苹果的安全和隐私政策。

不过这次苹果只是对使用热更新的应用进行了警告,并没有开发者反应产品因此问题被下架。

对此,开发者表示:

舞小月:苹果注重的就是流畅性和用户体验,混编做的东西肯定没有native的流畅,这就违背了苹果本来的意愿,被禁也是正常的,而且苹果自己的蛋糕为何要分给竞争对手?以前没混编的时候你该怎么做不还是做了,现在没有,不代表以后没有,就像之前没有混编,后来有了混编。新的框架苹果自然也会去完善,苹果既然做了这个决定,他肯定会优化自己的东西。

Gilbertat:苹果爸爸会不会在自己的生态中搞死js啊

luohui8891:我们也是昨天收到的,目前没有什么对策。我们的APP只是用JSPatch做热修复,并不修改应用的功能行为等(但我觉得Apple并不care这个)。

lsllsllsl:没用RN没用JSPatch,同样收到警告。

luohui8891:@tcathy 根据邮件里说是你下次提交前请去掉这样远程下载代码运行的机制。所以应该就是下个版本如果不删除就reject

Loooren:早上收到邮件,itunesconnect站内信,电话通知....用到了weex

xiaofuyesnew:昨天晚上微软发布了Visual Studio 2017,自带基于React Native的iOS开发功能。鉴于微软这两年来开源的力度,发布这一功能似乎是在抢占开发者的市场,基于vs2017,在非苹果上开发ios应用更容易了。所以,苹果在这个节骨眼发出这个警告邮件,就有点威胁现有开发者的意思。暗地里想跟微软互怼。

对于那些已经在学习RN、weex、JSPatch的同学来说,这是个悲惨的故事
002.png


003.png

从苹果的角度看,禁止应用使用热更新技术更多是为了保护用户隐私、数据安全以及其全力打造的生态圈。对于用户来说,出于安全起见,应谨慎授予应用权限;对于开发者来说,为了审核以及长远的用户体验考虑,不要轻易触碰苹果拉的那条红线。
004.png

以上内容来源于CocoaChinaGitHub
3
评论

Android ios V3.3.0 SDK 已发布,增加群组、聊天室管理员权限 iOS Android 产品更新

产品更新 发表了文章 • 545 次浏览 • 2017-03-08 16:37 • 来自相关话题

 Android​ V3.3.0 2017-03-07
 
新功能:
群组和聊天室改造:增加管理员权限,新增禁言,增减管理员的功能,支持使用分批的方式获取成员,禁言,管理员列表,支持完善的聊天室功能。新增加API请查看链接3.3.0 api修改优化dns劫持时的处理增加EMConversation.latestMessageFromOthers,表示收到对方的最后一条消息增加EMClient.compressLogs,压缩log,Demo中增加通过邮件发送log的示例libs.without.audio继续支持armeabi,解决armeabi-v5te的支持问题

bug 修订:
修复2.x升级3.x消息未读数为0的bugDemo在视频通话时,主叫方铃声没有播放的问题Demo在视频通话时,主叫方在建立连接成功后,文字提示不正确Demo在聊天窗口界面,清空消息后,收到新的消息,返回会话列表,未读消息数显示不正确修复在Oppo和Vivo手机上出现的JobService报错。EMGroupManager.createGroup成员列表数超过512产生的overflow错误修复部分手机在网络切换时发消息慢的bug
 
ios V3.3.0 2017-03-07
 
新功能:
新增:群组改造,增加一系列新接口,具体查看iOS iOS 3.3.0 api修改新增:获取SDK日志路径接口,将日志文件压缩成.gz文件,返回gz文件路径,[EMClient getLogFilesPath:]更新:使用视频通话录制功能时,必须在开始通话之前调用[EMVideoRecorderPlugin initGlobalConfig]

优化:
优化DNS劫持时的处理切换网络时,减小消息重发的等待时间

修复:
音视频通话丢包率(以前返回的是丢包数)IOS动态库用H264编码在iPhone6s上崩溃实时音视频新旧版互通崩溃
 
版本历史:Android SDK更新日志  ios SDK更新日志
下载地址:SDK下载 查看全部

7658.jpg_wh860_.jpg

 Android​ V3.3.0 2017-03-07
 
新功能:
  1. 群组和聊天室改造:增加管理员权限,新增禁言,增减管理员的功能,支持使用分批的方式获取成员,禁言,管理员列表,支持完善的聊天室功能。新增加API请查看链接3.3.0 api修改
  2. 优化dns劫持时的处理
  3. 增加EMConversation.latestMessageFromOthers,表示收到对方的最后一条消息
  4. 增加EMClient.compressLogs,压缩log,Demo中增加通过邮件发送log的示例
  5. libs.without.audio继续支持armeabi,解决armeabi-v5te的支持问题


bug 修订:
  1. 修复2.x升级3.x消息未读数为0的bug
  2. Demo在视频通话时,主叫方铃声没有播放的问题
  3. Demo在视频通话时,主叫方在建立连接成功后,文字提示不正确
  4. Demo在聊天窗口界面,清空消息后,收到新的消息,返回会话列表,未读消息数显示不正确
  5. 修复在Oppo和Vivo手机上出现的JobService报错。
  6. EMGroupManager.createGroup成员列表数超过512产生的overflow错误
  7. 修复部分手机在网络切换时发消息慢的bug

 
ios V3.3.0 2017-03-07
 
新功能:
  1. 新增:群组改造,增加一系列新接口,具体查看iOS iOS 3.3.0 api修改
  2. 新增:获取SDK日志路径接口,将日志文件压缩成.gz文件,返回gz文件路径,[EMClient getLogFilesPath:]
  3. 更新:使用视频通话录制功能时,必须在开始通话之前调用[EMVideoRecorderPlugin initGlobalConfig]


优化:
  1. 优化DNS劫持时的处理
  2. 切换网络时,减小消息重发的等待时间


修复:
  1. 音视频通话丢包率(以前返回的是丢包数)
  2. IOS动态库用H264编码在iPhone6s上崩溃
  3. 实时音视频新旧版互通崩溃

 
版本历史:Android SDK更新日志  ios SDK更新日志
下载地址:SDK下载
1
评论

淘宝购物车界面背后的逻辑及实现源码,欢迎Star! iOS

不死小强 发表了文章 • 973 次浏览 • 2017-02-22 17:16 • 来自相关话题

ViewController: 购物车界面
整个界面就是TableView + 底部结账栏View组成





以店铺为section:商店下的商品为row和店铺名称组成一个 section

定制段头的View 把section的全选按钮、点击商品、编辑的三个按钮的方法用代理的方法。

-(UIView*)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section;





建议使用Masonry进行cell适配

cell的创建就是和我们平常的一样,把要展示的样式代码编写或者xib都可以。再把数据源填充到我们所创建好的cell中和段头上。
创建好一个View添加在TableView的下方。View上写上全选及总金额等UI。每次我们选定的物品的增减都要调用该View赋值的方法,刷新金额等字段显示。





Cell:物品栏

创建两种cell,一个是正常的物品显示cell,另一个cell是编辑后的cell。

正常的cell:只说下label中划线的实现
//中划线


NSDictionary *attribtDic = @{NSStrikethroughStyleAttributeName: [NSNumber numberWithInteger:NSUnderlineStyleSingle]};

NSMutableAttributedString *attribtStr = [[NSMutableAttributedString alloc]initWithString:info[@"GoodsOldPrice"] attributes:attribtDic];

// 赋值

_Goods_OldPrice.attributedText = attribtStr;

编辑后的cell:

主要由三部分组成。商品数量 + 商品种类 + 删除




商品数量用的是第三方PPNumberButton,点击时改变model中该商品的实际数量;点击商品种类进行重新选择(方法未实现,原理一样);点击删除进行cell的删除及数据源的删除。

注释:这里的删除 及 修改 都是要对数据源进行修改在刷新的

Model:数据源的处理及购物车内各类按钮的判断

demo中的数据源我没有放到model中去处理,其实原理都一样,我把判断各类按钮的判断字段加到数据源中去了,如果用model模型的话,自己加上相对应的字段,并设置初始值。当进行model数据源的修改时,直接进行修改。





这是一种model处理方式,还有一种就是用JsonModel来处理,一层层的写下来,原理一样。 
购物车逻辑及实现总结

逻辑整理:当我们把有购买意向的物品加到购物车后,我们在购物车中调用接口获取购物车中的物品信息。数据源格式大概是(感觉不怎么对,但是能理解就行)

[

{@“店铺信息”:[@{物品信息},@{物品信息},@{物品信息}]},  -------》组一

{@"店铺信息":[@{物品信息}]},                                              -------》组二

{@”店铺信息“:[@{物品信息},@{物品信息}]}                        -------》组三

]

把数据源用model装起来,把数据填充到tableview中去。

1.单个商品的选择、单个店铺内所有商品的选择、结账栏下的全选                      

如果做得很简单的话,可以直接用系统的单选和全选方法。

最重要的两句 !!!!

TableDemo.editing=YES;      编辑状态

TableDemo.allowsMultipleSelectionDuringEditing=YES;   编辑的时候多选


cell.tintColor= [UIColorredColor];   选中后的颜色


选中和取消选中


-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath    选中


-(void)tableView:(UITableView*)tableView didDeselectRowAtIndexPath:(NSIndexPath*)indexPath    取消选中





如果不用系统的话,则利用model来进行单选和全选的操作,选中和取消选中对model中的判断字段进行修改,刷新当前cell或者section。

//一个section刷新


NSIndexSet *indexSet=[[NSIndexSet alloc]initWithIndex:section];


[tableview reloadSections:indexSet


withRowAnimation:UITableViewRowAnimationAutomatic];


//一个cell刷新


NSIndexPath *indexPath=[NSIndexPath indexPathForRow:row inSection:section];


[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationNone];


这里需要注意的是,每次单选和全选的时候,需要对底部结账栏进行数据的刷新,并且单选完整组后,需要对section上的按钮进行选中状态变化,当选中section时,同样需要对section下的row进行选中状态,如果全部商品选中,还得需要改变结账栏的状态。(这里其实不难,无非就是对model中各类字段进行改变,再刷新,同时判断选中数量的多少来进行按钮的状态变换)

2.删除单个商品

删除的话就相对容易了,直接对数据源删除对应的row,当只剩一个后,删除该数据后,记得删除该组section不然报错。删除后及时刷新底部结账栏金额显示。

3.编辑section

点击编辑按钮,修改model,展示编辑cell。编辑按钮上放了数量计数器、商品的信息、删除。

计数器:用到的是好友的一个库PPNumberButton 喜欢的大家可以去玩玩。点击后用代理方法把数量的变化跟新model。

商品信息:点击对商品的信息进行重新选择,同样修改数据源。

删除:代理出来进行model的修改。

因为这里用的是假数据,所以进行的都是对数据源的修改,正常情况下,原理都一样,可以在次基础上,如果接口成功,就对本地数据进行修改,最后提交的信息会和后台匹配一次的,如果有问题,可以自己修改一下。

有小伙伴提出demo中没有下拉刷新,其实下拉刷新不影响该demo。不过加上效果更好。

谢谢“爱在巴黎梦醒时”该小伙伴。
demo的bug注释:

因为demo中判断section的全选和编辑的按钮都是放在每个section的第一个row中的,所以删除section的第一个row后,会有全选和编辑的固定bug出来。特此声明,该bug不影响主体逻辑,如次bug影响小伙伴对逻辑思路的学习,那我后面再重新组织数据源。
demo纯代码编写的,只隔离了部分模块,因为我也是拿来练练手,所以如果有需要,后续我会把购物车模块化。如果内容有不妥和臃肿的地方,大家可以提出来,我及时学习并修改。如果大家有意见的可以@我1804094055qq.com。

项目源码Git地址https://github.com/zl645420646/-ZLShoppingCart
欢迎Star! 查看全部
淘宝.gif


ViewController: 购物车界面
整个界面就是TableView + 底部结账栏View组成

3899794-1ec96767b3e7afd5.png

以店铺为section:商店下的商品为row和店铺名称组成一个 section

定制段头的View 把section的全选按钮、点击商品、编辑的三个按钮的方法用代理的方法。

-(UIView*)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section;



3899794-6ef9165898df88f7.png

建议使用Masonry进行cell适配

cell的创建就是和我们平常的一样,把要展示的样式代码编写或者xib都可以。再把数据源填充到我们所创建好的cell中和段头上。
创建好一个View添加在TableView的下方。View上写上全选及总金额等UI。每次我们选定的物品的增减都要调用该View赋值的方法,刷新金额等字段显示。

3899794-700992bcfec1fcb0.png

Cell:物品栏

创建两种cell,一个是正常的物品显示cell,另一个cell是编辑后的cell。

正常的cell:只说下label中划线的实现

//中划线


NSDictionary *attribtDic = @{NSStrikethroughStyleAttributeName: [NSNumber numberWithInteger:NSUnderlineStyleSingle]};

NSMutableAttributedString *attribtStr = [[NSMutableAttributedString alloc]initWithString:info[@"GoodsOldPrice"] attributes:attribtDic];

// 赋值

_Goods_OldPrice.attributedText = attribtStr;



编辑后的cell:

主要由三部分组成。商品数量 + 商品种类 + 删除
3899794-6ce64a551798a1eb.png

商品数量用的是第三方PPNumberButton,点击时改变model中该商品的实际数量;点击商品种类进行重新选择(方法未实现,原理一样);点击删除进行cell的删除及数据源的删除。

注释:这里的删除 及 修改 都是要对数据源进行修改在刷新的

Model:数据源的处理及购物车内各类按钮的判断

demo中的数据源我没有放到model中去处理,其实原理都一样,我把判断各类按钮的判断字段加到数据源中去了,如果用model模型的话,自己加上相对应的字段,并设置初始值。当进行model数据源的修改时,直接进行修改。

3899794-272f9ea50f05377f.png

这是一种model处理方式,还有一种就是用JsonModel来处理,一层层的写下来,原理一样。 

购物车逻辑及实现总结

逻辑整理:当我们把有购买意向的物品加到购物车后,我们在购物车中调用接口获取购物车中的物品信息。数据源格式大概是(感觉不怎么对,但是能理解就行)

[

{@“店铺信息”:[@{物品信息},@{物品信息},@{物品信息}]},  -------》组一

{@"店铺信息":[@{物品信息}]},                                              -------》组二

{@”店铺信息“:[@{物品信息},@{物品信息}]}                        -------》组三

]

把数据源用model装起来,把数据填充到tableview中去。

1.单个商品的选择、单个店铺内所有商品的选择、结账栏下的全选                      

如果做得很简单的话,可以直接用系统的单选和全选方法。

最重要的两句 !!!!

TableDemo.editing=YES;      编辑状态

TableDemo.allowsMultipleSelectionDuringEditing=YES;   编辑的时候多选


cell.tintColor= [UIColorredColor];   选中后的颜色


选中和取消选中


-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath    选中


-(void)tableView:(UITableView*)tableView didDeselectRowAtIndexPath:(NSIndexPath*)indexPath    取消选中



3899794-b9d2effc299da195.png

如果不用系统的话,则利用model来进行单选和全选的操作,选中和取消选中对model中的判断字段进行修改,刷新当前cell或者section。

//一个section刷新


NSIndexSet *indexSet=[[NSIndexSet alloc]initWithIndex:section];


[tableview reloadSections:indexSet


withRowAnimation:UITableViewRowAnimationAutomatic];


//一个cell刷新


NSIndexPath *indexPath=[NSIndexPath indexPathForRow:row inSection:section];


[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationNone];


这里需要注意的是,每次单选和全选的时候,需要对底部结账栏进行数据的刷新,并且单选完整组后,需要对section上的按钮进行选中状态变化,当选中section时,同样需要对section下的row进行选中状态,如果全部商品选中,还得需要改变结账栏的状态。(这里其实不难,无非就是对model中各类字段进行改变,再刷新,同时判断选中数量的多少来进行按钮的状态变换)

2.删除单个商品

删除的话就相对容易了,直接对数据源删除对应的row,当只剩一个后,删除该数据后,记得删除该组section不然报错。删除后及时刷新底部结账栏金额显示。

3.编辑section

点击编辑按钮,修改model,展示编辑cell。编辑按钮上放了数量计数器、商品的信息、删除。

计数器:用到的是好友的一个库PPNumberButton 喜欢的大家可以去玩玩。点击后用代理方法把数量的变化跟新model。

商品信息:点击对商品的信息进行重新选择,同样修改数据源。

删除:代理出来进行model的修改。

因为这里用的是假数据,所以进行的都是对数据源的修改,正常情况下,原理都一样,可以在次基础上,如果接口成功,就对本地数据进行修改,最后提交的信息会和后台匹配一次的,如果有问题,可以自己修改一下。

有小伙伴提出demo中没有下拉刷新,其实下拉刷新不影响该demo。不过加上效果更好。

谢谢“爱在巴黎梦醒时”该小伙伴。


demo的bug注释:

因为demo中判断section的全选和编辑的按钮都是放在每个section的第一个row中的,所以删除section的第一个row后,会有全选和编辑的固定bug出来。特此声明,该bug不影响主体逻辑,如次bug影响小伙伴对逻辑思路的学习,那我后面再重新组织数据源。


demo纯代码编写的,只隔离了部分模块,因为我也是拿来练练手,所以如果有需要,后续我会把购物车模块化。如果内容有不妥和臃肿的地方,大家可以提出来,我及时学习并修改。如果大家有意见的可以@我1804094055qq.com。

项目源码Git地址https://github.com/zl645420646/-ZLShoppingCart
欢迎Star!
0
评论

ios V2.3.1 已发布,增加获取日志压缩文件路径接口 产品快递 iOS

产品更新 发表了文章 • 606 次浏览 • 2017-02-20 11:48 • 来自相关话题

ios版本:V2.3.1 2016-02-17





新功能/改进:
修改HttpsOnly参数默认值,默认设置为NO(由于苹果强制ATS政策延缓, 所以SDK默认关闭httpsOnly)
[[EaseMob sharedInstance].chatManager setIsUseHttpsOnly:YES];//设置httpsonly,YES开启,NO关闭
增加获取日志压缩文件路径接口(具体上传日志方式可由开发者决定, Demo是通过邮件的形式上报日志)优化群组过多时重连卡顿问题修复离线已读回执有时丢失问题修复SDK收到特殊消息闪退问题
 
 版本历史:ios 2.x更新日志
下载地址:SDK下载 查看全部
ios版本:V2.3.1 2016-02-17
2351.jpg_wh860_.jpg


新功能/改进:
  1. 修改HttpsOnly参数默认值,默认设置为NO(由于苹果强制ATS政策延缓, 所以SDK默认关闭httpsOnly)

[[EaseMob sharedInstance].chatManager setIsUseHttpsOnly:YES];//设置httpsonly,YES开启,NO关闭

  1. 增加获取日志压缩文件路径接口(具体上传日志方式可由开发者决定, Demo是通过邮件的形式上报日志)
  2. 优化群组过多时重连卡顿问题
  3. 修复离线已读回执有时丢失问题
  4. 修复SDK收到特殊消息闪退问题

 
 版本历史:ios 2.x更新日志
下载地址:SDK下载
6
评论

基于环信写的通讯项目,实现QQ小表情教程 iOS 自定义表情

zl 发表了文章 • 584 次浏览 • 2017-02-09 15:27 • 来自相关话题

先上效果图:










 
大家直接看下面demo就可以,搜索#pragma mark smallpngface就是所有修改的地方。
图片资源可以自己替换自己的,大家可以照猫画虎,我只是抛砖引玉。     

https://pan.baidu.com/s/1bA7eMy
  查看全部
先上效果图:

6069EDD5-304A-41BD-AE22-443F1604185A.png


568D243B-599A-43F3-A08D-E2B7C0CAC6E5.png

 
大家直接看下面demo就可以,搜索#pragma mark smallpngface就是所有修改的地方。
图片资源可以自己替换自己的,大家可以照猫画虎,我只是抛砖引玉。     

https://pan.baidu.com/s/1bA7eMy
 
2
评论

环信头像和昵称显示的详细、详细、详细教程! 头像 昵称 扩展 头像和昵称的显示 iOS 昵称 头像

xuke007008 发表了文章 • 2026 次浏览 • 2017-02-07 18:17 • 来自相关话题

写在前边:本文由江南大神的环信集成demo衍生而来!

附上大神的集成链接: http://www.imgeek.org/article/825307886 
 
通过官方的文档我们知道有两种显示头像和昵称的方式(http://docs.easemob.com/im/490integrationcases/10nickname  官方文档)

这里主要讲方式二!(通过扩展消息传递显示)
 
这里主要有三个类需要改,分别是:
EaseMessageViewController  
EaseBaseMessageCell 
chatUIhelper 

首先我们需要在发送消息的时候添加扩展字段,在EaseMessageViewController.m里。可以看到有以下方法:
#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 sendMessage: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};
}
}
}
[self sendTextMessage:text withExt:ext];
}

- (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext
{
EMMessage *message = [EaseSDKHelper sendTextMessage:text
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:ext];
[self _sendMessage:message];
}

- (void)sendLocationMessageLatitude:(double)latitude
longitude:(double)longitude
andAddress:(NSString *)address
{
EMMessage *message = [EaseSDKHelper sendLocationMessageWithLatitude:latitude
longitude:longitude
address:address
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

- (void)sendImageMessageWithData:(NSData *)imageData
{
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:nil];
[self _sendMessage:message];
}

- (void)sendImageMessage:(UIImage *)image
{
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:nil];
[self _sendMessage:message];
}

- (void)sendVoiceMessageWithLocalPath:(NSString *)localPath
duration:(NSInteger)duration
{
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:nil];
[self _sendMessage:message];
}

- (void)sendVideoMessageWithURL:(NSURL *)url
{
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:nil];
[self _sendMessage:message];
}
有发送各种消息的,我们要每个里边都加扩展字段么?那恐怕要累死咯!  仔细看会发现发送消息的方法最后都会走一个方法:
- (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 sendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {
if (!aError) {
[weakself _refreshAfterSentMessage:aMessage];
}
else {
[weakself.tableView reloadData];
}
}];
}
好的,就是这里了,添加扩展字段,包含用户的头像地址,昵称和环信ID。 找到保存用户信息的类UserCacheInfo,找到相应的字段,在这个方法里添加如下代码:
NSMutableDictionary *Muext = [NSMutableDictionary dictionaryWithDictionary:message.ext];
UserCacheInfo *info = [UserCacheManager currUser];
[Muext setObject:kCurrEaseUserId forKey:kChatUserId];
[Muext setObject:info.NickName forKey:kChatUserNick];
[Muext setObject:info.AvatarUrl forKey:kChatUserPic];
message.ext = Muext;

 
这样第一步就完成了!


接下来我们要在接收消息的方法里保存传过来的扩展消息里的头像、昵称和环信ID,这就用到chatUIhelper.m这个类,这个方法里:
- (void)didReceiveMessages:(NSArray *)aMessages
{
BOOL isRefreshCons = YES;
for(EMMessage *message in aMessages){
[UserCacheManager saveInfo:message.ext];// 通过消息的扩展属性传递昵称和头像时,需要调用这句代码缓存
BOOL needShowNotification = (message.chatType != EMChatTypeChat) ? [self _needShowNotification:message.conversationId] : YES;

#ifdef REDPACKET_AVALABLE
/**
* 屏蔽红包被抢消息的提示
*/
NSDictionary *dict = message.ext;
needShowNotification = (dict && [dict valueForKey:RedpacketKeyRedpacketTakenMessageSign]) ? NO : needShowNotification;
#endif

UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (needShowNotification) {
#if !TARGET_IPHONE_SIMULATOR
switch (state) {
case UIApplicationStateActive:
[self playSoundAndVibration];
break;
case UIApplicationStateInactive:
[self playSoundAndVibration];
break;
case UIApplicationStateBackground:
[self showNotificationWithMessage:message];
break;
default:
break;
}
#endif
}

if (_chatVC == nil) {
_chatVC = [self _getCurrentChatView];
}
BOOL isChatting = NO;
if (_chatVC) {
isChatting = [message.conversationId isEqualToString:_chatVC.conversation.conversationId];
}
if (_chatVC == nil || !isChatting || state == UIApplicationStateBackground) {
[self _handleReceivedAtMessage:message];

if (self.conversationListVC) {
[_conversationListVC refresh];
}

if (self.mainVC) {
NOTIFY_POST(kSetupUnreadMessageCount);
}
return;
}

if (isChatting) {
isRefreshCons = NO;
}
}

if (isRefreshCons) {
if (self.conversationListVC) {
[_conversationListVC refresh];
}

if (self.mainVC) {
NOTIFY_POST(kSetupUnreadMessageCount);
}
}
}
关键就是这句话:
[UserCacheManager saveInfo:message.ext];// 通过消息的扩展属性传递昵称和头像时,需要调用这句代码缓存!!!
 
到这里头像和昵称的问题就基本解决了!
 
 
重要的总是留在最后!!!  不看后悔哦!!!
 
上两步完成后你会惊奇的发现头像和昵称正常显示了,然而当你换个头像测试的时候,你会发现很不美妙,头像没有更换,这是什么问题呢?   这就要用到开始讲到的第一个类EaseBaseMessageCell.m,我们仔细看代码会发现它是怎么赋值的,如下:
#pragma mark - setter

- (void)setModel:(id<IMessageModel>)model
{
[super setModel:model];

if (model.avatarURLPath) {
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
} else {
self.avatarView.image = model.avatarImage;
}
_nameLabel.text = model.nickname;

if (self.model.isSender) {
_hasRead.hidden = YES;
switch (self.model.messageStatus) {
case EMMessageStatusDelivering:
{
_statusButton.hidden = YES;
[_activity setHidden:NO];
[_activity startAnimating];
}
break;
case EMMessageStatusSuccessed:
{
_statusButton.hidden = YES;
[_activity stopAnimating];
if (self.model.isMessageRead) {
_hasRead.hidden = NO;
}
}
break;
case EMMessageStatusPending:
case EMMessageStatusFailed:
{
[_activity stopAnimating];
[_activity setHidden:YES];
_statusButton.hidden = NO;
}
break;
default:
break;
}
}
}
看到这里就明白了是头像缓存了,直接用的是缓存里的头像,我们需要更新的话直接设置一下缓存策略就可以了,代码修改如下:
把 [self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
改成 [self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage options:EMSDWebImageRefreshCached];
然后运行一下你会发现世界如此美好,大功告成!  
对各位小伙伴you有没有帮助呢? 

如有任何问题,请咨询【环信IM互帮互助群】,群号:340452063 (进群记得改名片哦!江南大神也在群里!)
本人群里的名片:上海-iOS-小码农  。 查看全部
写在前边:本文由江南大神的环信集成demo衍生而来!

附上大神的集成链接: http://www.imgeek.org/article/825307886 
 
通过官方的文档我们知道有两种显示头像和昵称的方式(http://docs.easemob.com/im/490integrationcases/10nickname  官方文档)

这里主要讲方式二!(通过扩展消息传递显示)
 
这里主要有三个类需要改,分别是:
EaseMessageViewController  
EaseBaseMessageCell 
chatUIhelper 

首先我们需要在发送消息的时候添加扩展字段,在EaseMessageViewController.m里。可以看到有以下方法:
#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 sendMessage: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};
}
}
}
[self sendTextMessage:text withExt:ext];
}

- (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext
{
EMMessage *message = [EaseSDKHelper sendTextMessage:text
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:ext];
[self _sendMessage:message];
}

- (void)sendLocationMessageLatitude:(double)latitude
longitude:(double)longitude
andAddress:(NSString *)address
{
EMMessage *message = [EaseSDKHelper sendLocationMessageWithLatitude:latitude
longitude:longitude
address:address
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

- (void)sendImageMessageWithData:(NSData *)imageData
{
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:nil];
[self _sendMessage:message];
}

- (void)sendImageMessage:(UIImage *)image
{
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:nil];
[self _sendMessage:message];
}

- (void)sendVoiceMessageWithLocalPath:(NSString *)localPath
duration:(NSInteger)duration
{
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:nil];
[self _sendMessage:message];
}

- (void)sendVideoMessageWithURL:(NSURL *)url
{
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:nil];
[self _sendMessage:message];
}

有发送各种消息的,我们要每个里边都加扩展字段么?那恐怕要累死咯!  仔细看会发现发送消息的方法最后都会走一个方法:
- (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 sendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {
if (!aError) {
[weakself _refreshAfterSentMessage:aMessage];
}
else {
[weakself.tableView reloadData];
}
}];
}

好的,就是这里了,添加扩展字段,包含用户的头像地址,昵称和环信ID。 找到保存用户信息的类UserCacheInfo,找到相应的字段,在这个方法里添加如下代码:
NSMutableDictionary *Muext = [NSMutableDictionary dictionaryWithDictionary:message.ext];
UserCacheInfo *info = [UserCacheManager currUser];
[Muext setObject:kCurrEaseUserId forKey:kChatUserId];
[Muext setObject:info.NickName forKey:kChatUserNick];
[Muext setObject:info.AvatarUrl forKey:kChatUserPic];
message.ext = Muext;

 
这样第一步就完成了!


接下来我们要在接收消息的方法里保存传过来的扩展消息里的头像、昵称和环信ID,这就用到chatUIhelper.m这个类,这个方法里:
- (void)didReceiveMessages:(NSArray *)aMessages
{
BOOL isRefreshCons = YES;
for(EMMessage *message in aMessages){
[UserCacheManager saveInfo:message.ext];// 通过消息的扩展属性传递昵称和头像时,需要调用这句代码缓存
BOOL needShowNotification = (message.chatType != EMChatTypeChat) ? [self _needShowNotification:message.conversationId] : YES;

#ifdef REDPACKET_AVALABLE
/**
* 屏蔽红包被抢消息的提示
*/
NSDictionary *dict = message.ext;
needShowNotification = (dict && [dict valueForKey:RedpacketKeyRedpacketTakenMessageSign]) ? NO : needShowNotification;
#endif

UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (needShowNotification) {
#if !TARGET_IPHONE_SIMULATOR
switch (state) {
case UIApplicationStateActive:
[self playSoundAndVibration];
break;
case UIApplicationStateInactive:
[self playSoundAndVibration];
break;
case UIApplicationStateBackground:
[self showNotificationWithMessage:message];
break;
default:
break;
}
#endif
}

if (_chatVC == nil) {
_chatVC = [self _getCurrentChatView];
}
BOOL isChatting = NO;
if (_chatVC) {
isChatting = [message.conversationId isEqualToString:_chatVC.conversation.conversationId];
}
if (_chatVC == nil || !isChatting || state == UIApplicationStateBackground) {
[self _handleReceivedAtMessage:message];

if (self.conversationListVC) {
[_conversationListVC refresh];
}

if (self.mainVC) {
NOTIFY_POST(kSetupUnreadMessageCount);
}
return;
}

if (isChatting) {
isRefreshCons = NO;
}
}

if (isRefreshCons) {
if (self.conversationListVC) {
[_conversationListVC refresh];
}

if (self.mainVC) {
NOTIFY_POST(kSetupUnreadMessageCount);
}
}
}

关键就是这句话:
[UserCacheManager saveInfo:message.ext];// 通过消息的扩展属性传递昵称和头像时,需要调用这句代码缓存!!!
 
到这里头像和昵称的问题就基本解决了!
 
 
  • 重要的总是留在最后!!!  不看后悔哦!!!

 
上两步完成后你会惊奇的发现头像和昵称正常显示了,然而当你换个头像测试的时候,你会发现很不美妙,头像没有更换,这是什么问题呢?   这就要用到开始讲到的第一个类EaseBaseMessageCell.m,我们仔细看代码会发现它是怎么赋值的,如下:
#pragma mark - setter

- (void)setModel:(id<IMessageModel>)model
{
[super setModel:model];

if (model.avatarURLPath) {
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
} else {
self.avatarView.image = model.avatarImage;
}
_nameLabel.text = model.nickname;

if (self.model.isSender) {
_hasRead.hidden = YES;
switch (self.model.messageStatus) {
case EMMessageStatusDelivering:
{
_statusButton.hidden = YES;
[_activity setHidden:NO];
[_activity startAnimating];
}
break;
case EMMessageStatusSuccessed:
{
_statusButton.hidden = YES;
[_activity stopAnimating];
if (self.model.isMessageRead) {
_hasRead.hidden = NO;
}
}
break;
case EMMessageStatusPending:
case EMMessageStatusFailed:
{
[_activity stopAnimating];
[_activity setHidden:YES];
_statusButton.hidden = NO;
}
break;
default:
break;
}
}
}

看到这里就明白了是头像缓存了,直接用的是缓存里的头像,我们需要更新的话直接设置一下缓存策略就可以了,代码修改如下:
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
改成 [self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage options:EMSDWebImageRefreshCached];

然后运行一下你会发现世界如此美好,大功告成!  
对各位小伙伴you有没有帮助呢? 

如有任何问题,请咨询【环信IM互帮互助群】,群号:340452063 (进群记得改名片哦!江南大神也在群里!)
本人群里的名片:上海-iOS-小码农  。
1
评论

环信及时通信iOS SDK的一些看法和建议 环信 ios iOS

xiaohao01 发表了文章 • 527 次浏览 • 2017-01-16 11:46 • 来自相关话题

先说下环境:我使用的是HyphenateLite,3.2.3版本
 1,关于bitcode
终于看到3.2.3版本有说:sdk支持bitcode拉,赶紧下载组装。编译时提示:
libopencore-amrnb.a(wrapper.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
看来官方给出的sdk支持bitcode还真是仅限于“sdk”了,EaseUI里的libopencore-amrnb不是环信出品的,就不管了??可这对开发者来说有什么用呢?我们需要sdk提供的全部组件都支持bicode才行呢!!!
另外:跟iOS SDK客服沟通了下,似乎没有搞明白开启bitcode时什么意思。
强调一遍:必须在TARGET中设置bitcode为YES,然后真机运行,真机运行!!!!
麻烦官方再仔细检查一遍给我们开发者一个真正能用的bitcode版本!!!
 
2,关于文档
这次3.2.3说使用了动态framework,嗯,对官方的这种与时俱进赞一个。但你们能仔细看看给的文档吗:
注: 由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交
然后呢?如何操作?能再简洁一点吗?
熟悉开发的肯定都知道用lipo操作,但是刚入门的是还需要查询的呀。能直接说下怎么操作岂不是更好!
 
3,一些建议
1,做SDK不像平常开发个app,自己的一亩三分地想怎么搞就怎么搞,sdk是拿出来给大家用的,对象都是开发者,需要开发sdk的同学本身就非常精通。一个建议,对于暴漏的.h文件最好都引用:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>//如何有UI
 
2,并且文档什么的要尽量细化。看到官方推出了不少视频的开发文档,这个虽然很好,降低了门槛,可是sdk总是在升级的在变动的,视频的内容往往发出来不久就落后于实际的代码了。建议多些文字文档,重要步骤尽量详细,文字能快速传达信息,没人有希望集成个sdk都要花费很多时间。
 
以上是我个人的浅见,不是纯粹是发牢骚,大家都是开发者,都希望作品精益求精,希望共同进步!
 
匆促中难免有错字,望理解!谢谢!
  查看全部
先说下环境:我使用的是HyphenateLite,3.2.3版本
 1,关于bitcode
终于看到3.2.3版本有说:sdk支持bitcode拉,赶紧下载组装。编译时提示:
libopencore-amrnb.a(wrapper.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
看来官方给出的sdk支持bitcode还真是仅限于“sdk”了,EaseUI里的libopencore-amrnb不是环信出品的,就不管了??可这对开发者来说有什么用呢?我们需要sdk提供的全部组件都支持bicode才行呢!!!
另外:跟iOS SDK客服沟通了下,似乎没有搞明白开启bitcode时什么意思。
强调一遍:必须在TARGET中设置bitcode为YES,然后真机运行,真机运行!!!!
麻烦官方再仔细检查一遍给我们开发者一个真正能用的bitcode版本!!!
 
2,关于文档
这次3.2.3说使用了动态framework,嗯,对官方的这种与时俱进赞一个。但你们能仔细看看给的文档吗:

注: 由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交


然后呢?如何操作?能再简洁一点吗?
熟悉开发的肯定都知道用lipo操作,但是刚入门的是还需要查询的呀。能直接说下怎么操作岂不是更好!
 
3,一些建议
1,做SDK不像平常开发个app,自己的一亩三分地想怎么搞就怎么搞,sdk是拿出来给大家用的,对象都是开发者,需要开发sdk的同学本身就非常精通。一个建议,对于暴漏的.h文件最好都引用:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>//如何有UI
 
2,并且文档什么的要尽量细化。看到官方推出了不少视频的开发文档,这个虽然很好,降低了门槛,可是sdk总是在升级的在变动的,视频的内容往往发出来不久就落后于实际的代码了。建议多些文字文档,重要步骤尽量详细,文字能快速传达信息,没人有希望集成个sdk都要花费很多时间。
 
以上是我个人的浅见,不是纯粹是发牢骚,大家都是开发者,都希望作品精益求精,希望共同进步!
 
匆促中难免有错字,望理解!谢谢!
 
0
评论

环信动态库sdk上架问题解决方案 iOS 动态库

beyond 发表了文章 • 1236 次浏览 • 2017-01-10 17:28 • 来自相关话题

环信发布了动态库sdk:




但是也会有一些问题,这里讲下关于这个上架的问题。

1.先把Hyphenate.framework放到桌面上;

2.终端位置cd到桌面;

3.运行:lipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7  

4.运行后没有输出提示,直接运行下一个命令:lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64                                                                                                                                                                

5.运行后一样没有输出提示,直接运行下一个命令:lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate                                                                                                                                                                            

6.运行后一样没有输出提示,直接运行最后一个命令:mv Hyphenate Hyphenate.framework/




得到的Hyphenate.framework就是最后的结果,拖进工程,编译打包上架。
 
作者:环信ios工程师张磊 查看全部
环信发布了动态库sdk:
2639170-be3d6bfe875d544c.png

但是也会有一些问题,这里讲下关于这个上架的问题。

1.先把Hyphenate.framework放到桌面上;

2.终端位置cd到桌面;

3.运行:lipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7  

4.运行后没有输出提示,直接运行下一个命令:lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64                                                                                                                                                                

5.运行后一样没有输出提示,直接运行下一个命令:lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate                                                                                                                                                                            

6.运行后一样没有输出提示,直接运行最后一个命令:mv Hyphenate Hyphenate.framework/
2639170-6aa66fdee8102250.png

得到的Hyphenate.framework就是最后的结果,拖进工程,编译打包上架。
 
作者:环信ios工程师张磊
0
评论

Android ios V3.2.3 SDK 已发布,SDK十余项更新,更加简洁易用,新增广告红包 产品快递 Android iOS

产品更新 发表了文章 • 662 次浏览 • 2016-12-30 11:51 • 来自相关话题

Android​ V3.2.3 2016-12-29
新功能/优化:
sdk提供aar及gradle方式集成,具体方法查看gradle方式导入aar增加离线推送设置的相关接口,具体方法可查看EMPushManager API文档为了使sdk更简洁易用,修改以及过时了一些api,具体修改查看3.2.3api修改,另外过时的api后续3-5个版本会进行删除优化loadAllConversationsFromDB()方法,从联表查询改为从两个表分别查询,解决在个别乐视手机上执行很慢的问题优化登录模块,减少登录失败的概率鉴于市面上的手机基本都是armeabi-v7a及以上的架构,从这版本开始不再提供普通的armeabi架构的so,减少打包时app的体积

红包相关:
新增:
小额随机红包增加广告红包(需要使用请单独联系商务)商户后台增加广告红包配置、统计功能商户后台增加修改密码功能

优化:
绑卡后的用户验证四要素改为验证二要素发红包等页面增加点击空白区域收回键盘的功能群成员列表索引增加常用姓氏以及汉字的支持

修复bug:
红包详情页领取人列表展示不全华为P8手机密码框无法获取焦点部分银行卡号输入正确,提示银行卡号不正确红包祝福语有换行符显示不正确修复Emoji表情显示乱码修复商户自主配置红包最低限额错误修复零钱明细显示顺序错误问题
 
iOS​ V3.2.3 2016-12-29
新功能/优化:
新增:实时1v1音视频,设置了对方不在线发送离线推送的前提下,当对方不在线时返回回调,以便于用户自定义离线消息推送更新:SDK支持bitcode更新:SDK使用动态库为了使SDK更简洁易用,过时的API会在后续3~5个版本进行删除

红包相关:
新增:
小额随机红包商户后台增加修改密码功能

优化:
绑卡后的用户验证四要素改为验证二要素iOS和Android两端UI展示一致性支付流程的优化SDK注册流程去掉XIB集成过程的参数检查风险策略

修复:
SDKToken注册失败的问题发红包缺少参数的问题修复Emoji表情显示乱码修复支付密码可能误报出错修复商户自主配置红包最低限额错误修复零钱明细显示顺序错误问题修改抢红包流程为依赖后端数据修复支行信息返回为空时的文案
 
 版本历史:Android SDK更新日志  ios SDK更新日志
下载地址:SDK下载 查看全部
7750.jpg_wh860_.jpg

Android​ V3.2.3 2016-12-29
新功能/优化:
  • sdk提供aar及gradle方式集成,具体方法查看gradle方式导入aar
  • 增加离线推送设置的相关接口,具体方法可查看EMPushManager API文档
  • 为了使sdk更简洁易用,修改以及过时了一些api,具体修改查看3.2.3api修改,另外过时的api后续3-5个版本会进行删除
  • 优化loadAllConversationsFromDB()方法,从联表查询改为从两个表分别查询,解决在个别乐视手机上执行很慢的问题
  • 优化登录模块,减少登录失败的概率
  • 鉴于市面上的手机基本都是armeabi-v7a及以上的架构,从这版本开始不再提供普通的armeabi架构的so,减少打包时app的体积


红包相关:
新增:

  • 小额随机红包
  • 增加广告红包(需要使用请单独联系商务)
  • 商户后台增加广告红包配置、统计功能
  • 商户后台增加修改密码功能


优化:
  • 绑卡后的用户验证四要素改为验证二要素
  • 发红包等页面增加点击空白区域收回键盘的功能
  • 群成员列表索引增加常用姓氏以及汉字的支持


修复bug:
  • 红包详情页领取人列表展示不全
  • 华为P8手机密码框无法获取焦点
  • 部分银行卡号输入正确,提示银行卡号不正确
  • 红包祝福语有换行符显示不正确
  • 修复Emoji表情显示乱码
  • 修复商户自主配置红包最低限额错误
  • 修复零钱明细显示顺序错误问题

 
iOS​ V3.2.3 2016-12-29
新功能/优化:
  • 新增:实时1v1音视频,设置了对方不在线发送离线推送的前提下,当对方不在线时返回回调,以便于用户自定义离线消息推送
  • 更新:SDK支持bitcode
  • 更新:SDK使用动态库
  • 为了使SDK更简洁易用,过时的API会在后续3~5个版本进行删除


红包相关:
新增:

  • 小额随机红包
  • 商户后台增加修改密码功能


优化:
  • 绑卡后的用户验证四要素改为验证二要素
  • iOS和Android两端UI展示一致性
  • 支付流程的优化
  • SDK注册流程
  • 去掉XIB
  • 集成过程的参数检查
  • 风险策略


修复:
  • SDKToken注册失败的问题
  • 发红包缺少参数的问题
  • 修复Emoji表情显示乱码
  • 修复支付密码可能误报出错
  • 修复商户自主配置红包最低限额错误
  • 修复零钱明细显示顺序错误问题
  • 修改抢红包流程为依赖后端数据
  • 修复支行信息返回为空时的文案

 
 版本历史:Android SDK更新日志  ios SDK更新日志
下载地址:SDK下载
0
评论

ios V2.3.0 SDK 已发布,增加HttpsOnly参数,允许用户配置 iOS 产品快递

产品更新 发表了文章 • 300 次浏览 • 2016-12-28 14:46 • 来自相关话题

ios V2.3.0 2016-12-28




新功能/改进:
修复2.2.9升级覆盖2.1.5至2.2.3版本,可能无法登录的bug

增加HttpsOnly参数,允许用户配置,默认设置为YES

SDK支持bitcode
版本历史:更新日志
下载地址:SDK下载 查看全部
ios V2.3.0 2016-12-28
1494.jpg_wh860_.jpg

新功能/改进:

修复2.2.9升级覆盖2.1.5至2.2.3版本,可能无法登录的bug

增加HttpsOnly参数,允许用户配置,默认设置为YES

SDK支持bitcode


版本历史:更新日志
下载地址:SDK下载
0
评论

IOS 2.2.9设置https only遇到无法登录的解决方案 ios_2.X iOS 环信集成指南 https

beyond 发表了文章 • 675 次浏览 • 2016-12-28 14:34 • 来自相关话题

分享一个自己遇到的在环信IOS2.X中设置https登录不上的解决方案。
 
SDK的老版本(版本范围为SDK2.1.5-2.2.3)中存在默认不使用https的设置。部分使用了范围内SDK版本的用户在升级到最新的SDK2.2.9时,设置https only的选项后会出现用户无法正常登录的问题。

这个问题可以升级到SDK2.2.9及以上版本,在SDK初始化时添加otherConfig:@{kSDKConfigUseHttps:@YES}的设置,具体的代码实现如下:[[EaseMob sharedInstance] registerSDKWithAppKey:@"easemob-demo#chatdemoui" apnsCertName:@"chatdemoui" otherConfig:@{kSDKConfigUseHttps:@YES}];
上记代码仅未示例,具体在使用时需要将appkey等信息替换为自己的对应信息就可以了。
 
集成过程中遇到问题欢迎在IMGeek社区发帖咨询。 查看全部
分享一个自己遇到的在环信IOS2.X中设置https登录不上的解决方案。
 
SDK的老版本(版本范围为SDK2.1.5-2.2.3)中存在默认不使用https的设置。部分使用了范围内SDK版本的用户在升级到最新的SDK2.2.9时,设置https only的选项后会出现用户无法正常登录的问题。

这个问题可以升级到SDK2.2.9及以上版本,在SDK初始化时添加otherConfig:@{kSDKConfigUseHttps:@YES}的设置,具体的代码实现如下:
[[EaseMob sharedInstance] registerSDKWithAppKey:@"easemob-demo#chatdemoui" apnsCertName:@"chatdemoui" otherConfig:@{kSDKConfigUseHttps:@YES}];

上记代码仅未示例,具体在使用时需要将appkey等信息替换为自己的对应信息就可以了。
 
集成过程中遇到问题欢迎在IMGeek社区发帖咨询。
1
评论

ios V3.2.2 SDK 已发布,增加是否删除会话选项 ios_3.x iOS 产品快递

产品更新 发表了文章 • 578 次浏览 • 2016-12-08 19:10 • 来自相关话题

ios ​V3.2.2 2016-12-08





 
新功能/优化:
 

SDK满足apple ATS的要求

删除好友逻辑的修改(增加是否删除会话选项)

修复呼叫时对方不在线,不能正确显示通话结束原因的问题

版本历史:更新日志 
下载地址:SDK下载 查看全部
ios ​V3.2.2 2016-12-08

2597.jpg_wh860_.jpg

 
新功能/优化:
 


SDK满足apple ATS的要求

删除好友逻辑的修改(增加是否删除会话选项)

修复呼叫时对方不在线,不能正确显示通话结束原因的问题


版本历史:更新日志 
下载地址:SDK下载
0
评论

ios V2.2.9 SDK 已发布,SDK满足apple ATS的要求 ios_2.X iOS 产品快递

产品更新 发表了文章 • 634 次浏览 • 2016-12-08 18:54 • 来自相关话题

ios 版本 V3.2.2 2016-12-08





 
新功能/优化:

SDK满足apple ATS的要求

删除好友逻辑的修改(增加是否删除会话选项)

修复呼叫时对方不在线,不能正确显示通话结束原因的问题

版本历史:更新日志 
下载地址:SDK下载 查看全部
ios 版本 V3.2.2 2016-12-08
7282.jpg_wh860_.jpg


 
新功能/优化:


SDK满足apple ATS的要求

删除好友逻辑的修改(增加是否删除会话选项)

修复呼叫时对方不在线,不能正确显示通话结束原因的问题


版本历史:更新日志 
下载地址:SDK下载
0
评论

ios V3.2.1 SDK 已发布,聊天室列表支持分页获取 iOS 产品快递

产品更新 发表了文章 • 472 次浏览 • 2016-12-07 11:33 • 来自相关话题

ios V3.2.1 2016-11-12
 





新功能/优化:
 
聊天室列表支持分页获取

EMOption中usingHttps默认为YES

bug fix:
修复Lite版本SDK编译warning的问题
 
版本历史:更新日志  
下载地址:SDK下载 查看全部
ios V3.2.1 2016-11-12
 
3099.jpg_wh860_.jpg


新功能/优化:
 

聊天室列表支持分页获取

EMOption中usingHttps默认为YES



bug fix:

修复Lite版本SDK编译warning的问题


 
版本历史:更新日志  
下载地址:SDK下载
0
评论

快速找到当前页面的viewcontroller,有助于看别人代码 iOS

奋斗的蜗牛 发表了文章 • 328 次浏览 • 2016-10-28 17:52 • 来自相关话题

下面我将上传一个微信demo试试看了下效果,只需要往工程里面添加创建的视图控制器,就可以快速找到当前页面的viewcontroler




 
下面我将上传一个微信demo试试看了下效果,只需要往工程里面添加创建的视图控制器,就可以快速找到当前页面的viewcontroler
屏幕快照_2016-10-28_下午5.47_.49_.png

 
1
评论

【环信集成笔记】入门篇-分享一些ios集成小技巧 环信集成笔记 iOS 环信_iOS

beyond 发表了文章 • 2571 次浏览 • 2016-09-20 11:47 • 来自相关话题

这段时间由于家里的事和工作上的项目太忙好久没写东西了,,,今天先说说集成环信的经验吧,也许会很乱,我会慢慢修改的。新手一枚第一次集成环信,咱们慢慢看算是我自己的一个整理。
 
一、准备工作:

1.环信官网http://www.easemob.com 也可以百度环信还是很好找的。

2.苹果账号,因为集成即时聊天要推送证书所以必须有账号,证书制作我就不在这里说了,可以上网查。

3.在环信创建APP上传推送证书,这样可以了过程很简单的(appkey,推送证书名是有用的可以先记录)。

二、开始集成

1.这里总的说一下

第一点环信提供两套SDK,一套带有实时语音(打电话)版本HyphenateFullSDK,一套没有的HyphenateSDK,正常不是专门聊天的APP都是不用实时语音功能的。

第二点环信给了一套做好的UI叫EaseUI,可以用能减少很多时间,特别是聊天页面自己写比较麻烦,特别是EASYUI中的Model写的都不错。

第三点集成方式可以手动集成SDK,可以用pod集成,手动集成就不说了,下面给出pod命令
pod 'HyphenateSDK', :git => 'https://github.com/easemob/hyphenate-cocoapods.git'
pod 'HyphenateFullSDK', :git => 'https://github.com/easemob/hyphenate-full-cocoapods.git'
pod 'EaseUI' //这个环信官网上没有是我在环信的git上找到的。
这里有一点注意的地方,如果你用pod集成,注意pod更新是会吧你改动的代码刷新掉,可以不直接用EaseUI里的东西,可以写子类,或者刷新掉了,可以用git或者svn,还原修改,这样就能回来了。
这样我们就把SDK集成到我们的项目了。

第四点什么改APP端做什么该服务端做,服务端做的其实很少,只有两个,第一注册环信,这部分一般式绑定在我们注册当前APP用户的要查询服务端数据库,所以服务端做,第二,好友关系,环信用户之间聊天是不需要好友关系的,所以决定了好友关系这部分可以直接用我们的服务端维护。这两点以外,其他的都是我们APP端做,,,

2.开始代码部分(这部分代码前提集成了HyphenateSDK和EaseUI)

首先在AppDelegate中注册环信,直接可用EaseUI中的注册方法

一,注册环信/*!

* APP启动时注册环信,并登陆当前用户(如果有用户的话)

*

* @param application application description

* @param launchOptions launchOptions description

*/

-(void)startHuanXinEasyUIUseapplication:(UIApplication *)application Options:(NSDictionary *)launchOptions{

//AppKey:注册的AppKey,详细见下面注释。

//apnsCertName:推送证书名(不需要加后缀),详细见下面注释。

NSString *apnsCertName = nil;

#if DEBUG

apnsCertName = @"开发环境测试证书";

#else

apnsCertName = @"发布环境证书";

#endif

//环信appkey存放在UserDefaults

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];

NSString *appkey = [ud stringForKey:@"identifier_appkey"];

if (!appkey) {

appkey = @"appkey";

[ud setObject:appkey forKey:@"identifier_appkey"];

}

//利用EasyUI启动环信(这里有一个坑,下面这个注册方法,点进去看会吧用户是否同意被加进群组改成NO,这里会导致群组加不上,记得看看,改回来)

[[EaseSDKHelper shareHelper] easemobApplication:application

didFinishLaunchingWithOptions:launchOptions

appkey:appkey

apnsCertName:apnsCertName

otherConfig:@{kSDKConfigEnableConsoleLogger:[NSNumber numberWithBool:YES]}];

//判断当前是否有用户,有用户登陆环信

if (DEF_PERSISTENT_GET_OBJECT(@"userId") != nil && DEF_PERSISTENT_GET_OBJECT(@"userPwd") != nil) {

NSString *accound = [NSString stringWithFormat:@"%@",DEF_PERSISTENT_GET_OBJECT(@"userId")];

NSString *pwd = [NSString stringWithFormat:@"%@",DEF_PERSISTENT_GET_OBJECT(@"userPwd")];

EMError *error = [[EMClient sharedClient] loginWithUsername:accound password:pwd];

[[EMClient sharedClient] addDelegate:self];

if (!error) {

NSLog(@"登录成功");
}
}
}二、环信好友

聊天就得有好友,刚开始总述中说过好友是咱们服务器来维护的所以不用管,走一个接口就好了,因为我们集成EasyUI所以我们从接口中请求的数据最好都变成EasyUI中用户的Model(EaseUserModel),这样在用环信聊天的是不会那么麻烦,也可以自己写一个Model这样麻烦但是不乱,#import <Foundation/Foundation.h>

#import "IUserModel.h"

@interface EaseUserModel : NSObject<IUserModel>

@property (strong, nonatomic, readonly) NSString *buddy;//用户名
@property (strong, nonatomic) NSString *nickname;//名字
@property (strong, nonatomic) NSString *avatarURLPath;//头像地址
@property (strong, nonatomic) UIImage *avatarImage;//头像图片

- (instancetype)initWithBuddy:(NSString *)buddy;

@end中用户名是最有用的是直接用于创建聊天的,名字头像用处不大,因为这些存储都是在本地的,地方一改名字头像你这面可能没反应所以不用这个头像名字,都是使用消息中的扩展字段下面说。

三、环信聊天

聊天界面我推荐直接用EasyUI的,自己写各种消息cell实在麻烦,使用这个直接就是可以聊天的创建方法如下EaseMessageViewController *viewController = [[EaseMessageViewController alloc] initWithConversationChatter:@"聊天对象的id,或者群组的id" conversationType:聊天的类型(单聊,群聊,聊天室)];四、环信聊天消息重点*

在发消息过程中是不能传递用户名和头像的,所以我们要给消息添加扩展字段ext,(扩展字段可以其实就是发送消息是附带传递一个json数据),我们可以吧自己的头像和名字在每次发消息的时候发过去,这样聊天时,当前本人可以知道自己的名字和头像,聊天对方会在消息中发过来名字头像,这样两个人都有名字头像了,下面贴下我的扩展字段- (void)_sendMessage:(EMMessage *)message //发消息的总方法
{
message.ext = @{@"nikeName":_myNikeName,@"headImagePath":_myHeadImagePath}; //添加消息扩展,我的名字,我的头像

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) {
[weakself.tableView reloadData];
}];
}五、消息列表

消息列表的获取用环信的获取方法- (void)tableViewDidTriggerHeaderRefresh
{
NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations];//本地获取消息
NSArray* sorted = [conversations sortedArrayUsingComparator:
^(EMConversation *obj1, EMConversation* obj2){
EMMessage *message1 = [obj1 latestMessage];
EMMessage *message2 = [obj2 latestMessage];
if(message1.timestamp > message2.timestamp) {
return(NSComparisonResult)NSOrderedAscending;
}else {
return(NSComparisonResult)NSOrderedDescending;
}
}];
[self.tableView endHeaderFresh];
[self.tableView reloadData];
}这里有一个地方,再获得会话列表的时候,有几个数据要自己获取一下,头像,名字,最后一条消息内容,最后一条消息时间,消息未读数。其中消息未读数事环信帮我们做好的直接设置显示未读数就好
其中消息最后一条消息内容,最后一条消息时间。要我们获取最后一条消息在给其赋值//在tableView代理中写
cell.detailLabel.attributedText = [[EaseEmotionEscape sharedInstance] attStringFromTextForChatting:[self _latestMessageTitleForConversationModel:model]textFont:cell.detailLabel.font];
cell.timeLabel.text = [self _latestMessageTimeForConversationModel:model];

//获取最后一条消息内容
- (NSString *)_latestMessageTitleForConversationModel:(id<IConversationModel>)conversationModel
{
NSString *latestMessageTitle = @"";
EMMessage *lastMessage = [conversationModel.conversation latestMessage];
if (lastMessage) {
EMMessageBody *messageBody = lastMessage.body;
switch (messageBody.type) {
case EMMessageBodyTypeImage:{
latestMessageTitle = NSEaseLocalizedString(@"message.image1", @"[image]");
} break;
case EMMessageBodyTypeText:{
NSString *didReceiveText = [EaseConvertToCommonEmoticonsHelper
convertToSystemEmoticons:((EMTextMessageBody *)messageBody).text];
latestMessageTitle = didReceiveText;
} break;
case EMMessageBodyTypeVoice:{
latestMessageTitle = NSEaseLocalizedString(@"message.voice1", @"[voice]");
} break;
case EMMessageBodyTypeLocation: {
latestMessageTitle = NSEaseLocalizedString(@"message.location1", @"[location]");
} break;
case EMMessageBodyTypeVideo: {
latestMessageTitle = NSEaseLocalizedString(@"message.video1", @"
[video]");
} break;
case EMMessageBodyTypeFile: {
latestMessageTitle = NSEaseLocalizedString(@"message.file1", @"[file]");
} break;
default: {
} break;
}
}
return latestMessageTitle;
}

//获取最后一条消息时间
- (NSString *)_latestMessageTimeForConversationModel:(id<IConversationModel>)conversationModel
{
NSString *latestMessageTime = @"";
EMMessage *lastMessage = [conversationModel.conversation latestMessage];;
if (lastMessage) {
double timeInterval = lastMessage.timestamp ;
if(timeInterval > 140000000000) {
timeInterval = timeInterval / 1000;
}
NSDateFormatter* formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:@"YYYY-MM-dd"];
latestMessageTime = [formatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:timeInterval]];
}
return latestMessageTime;
}[/video]




至于头像名字则要我们使用我没发消息的扩展字段,
这里我们可以获取会话中最后一条来自对方的消息,然后取出扩展字段赋值就好,(ps:有一种情况我刚刚和一个人说话会话中没有来自对方的最后一条消息,这个时候我们要用我们本地自己赋值这个会话的头像和名字)

三、总结

恩,这是这个杂记的第一次写的东西,写了写基本的用法,简单的聊天功能,写的也比较乱,,我会慢慢整理修改的。
 
作者简介:我是windKing,一个iOS开发的小白希望能有一样的人和我一起成长。看的过程中有不明白的地方欢迎联系我qq1662628982。 查看全部
这段时间由于家里的事和工作上的项目太忙好久没写东西了,,,今天先说说集成环信的经验吧,也许会很乱,我会慢慢修改的。新手一枚第一次集成环信,咱们慢慢看算是我自己的一个整理。
 
一、准备工作:

1.环信官网http://www.easemob.com 也可以百度环信还是很好找的。

2.苹果账号,因为集成即时聊天要推送证书所以必须有账号,证书制作我就不在这里说了,可以上网查。

3.在环信创建APP上传推送证书,这样可以了过程很简单的(appkey,推送证书名是有用的可以先记录)。

二、开始集成

1.这里总的说一下

第一点环信提供两套SDK,一套带有实时语音(打电话)版本HyphenateFullSDK,一套没有的HyphenateSDK,正常不是专门聊天的APP都是不用实时语音功能的。

第二点环信给了一套做好的UI叫EaseUI,可以用能减少很多时间,特别是聊天页面自己写比较麻烦,特别是EASYUI中的Model写的都不错。

第三点集成方式可以手动集成SDK,可以用pod集成,手动集成就不说了,下面给出pod命令
pod 'HyphenateSDK', :git => 'https://github.com/easemob/hyphenate-cocoapods.git'
pod 'HyphenateFullSDK', :git => 'https://github.com/easemob/hyphenate-full-cocoapods.git'
pod 'EaseUI' //这个环信官网上没有是我在环信的git上找到的。
这里有一点注意的地方,如果你用pod集成,注意pod更新是会吧你改动的代码刷新掉,可以不直接用EaseUI里的东西,可以写子类,或者刷新掉了,可以用git或者svn,还原修改,这样就能回来了。
这样我们就把SDK集成到我们的项目了。

第四点什么改APP端做什么该服务端做,服务端做的其实很少,只有两个,第一注册环信,这部分一般式绑定在我们注册当前APP用户的要查询服务端数据库,所以服务端做,第二,好友关系,环信用户之间聊天是不需要好友关系的,所以决定了好友关系这部分可以直接用我们的服务端维护。这两点以外,其他的都是我们APP端做,,,

2.开始代码部分(这部分代码前提集成了HyphenateSDK和EaseUI)

首先在AppDelegate中注册环信,直接可用EaseUI中的注册方法

一,注册环信
/*!

* APP启动时注册环信,并登陆当前用户(如果有用户的话)

*

* @param application application description

* @param launchOptions launchOptions description

*/

-(void)startHuanXinEasyUIUseapplication:(UIApplication *)application Options:(NSDictionary *)launchOptions{

//AppKey:注册的AppKey,详细见下面注释。

//apnsCertName:推送证书名(不需要加后缀),详细见下面注释。

NSString *apnsCertName = nil;

#if DEBUG

apnsCertName = @"开发环境测试证书";

#else

apnsCertName = @"发布环境证书";

#endif

//环信appkey存放在UserDefaults

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];

NSString *appkey = [ud stringForKey:@"identifier_appkey"];

if (!appkey) {

appkey = @"appkey";

[ud setObject:appkey forKey:@"identifier_appkey"];

}

//利用EasyUI启动环信(这里有一个坑,下面这个注册方法,点进去看会吧用户是否同意被加进群组改成NO,这里会导致群组加不上,记得看看,改回来)

[[EaseSDKHelper shareHelper] easemobApplication:application

didFinishLaunchingWithOptions:launchOptions

appkey:appkey

apnsCertName:apnsCertName

otherConfig:@{kSDKConfigEnableConsoleLogger:[NSNumber numberWithBool:YES]}];

//判断当前是否有用户,有用户登陆环信

if (DEF_PERSISTENT_GET_OBJECT(@"userId") != nil && DEF_PERSISTENT_GET_OBJECT(@"userPwd") != nil) {

NSString *accound = [NSString stringWithFormat:@"%@",DEF_PERSISTENT_GET_OBJECT(@"userId")];

NSString *pwd = [NSString stringWithFormat:@"%@",DEF_PERSISTENT_GET_OBJECT(@"userPwd")];

EMError *error = [[EMClient sharedClient] loginWithUsername:accound password:pwd];

[[EMClient sharedClient] addDelegate:self];

if (!error) {

NSLog(@"登录成功");
}
}
}
二、环信好友

聊天就得有好友,刚开始总述中说过好友是咱们服务器来维护的所以不用管,走一个接口就好了,因为我们集成EasyUI所以我们从接口中请求的数据最好都变成EasyUI中用户的Model(EaseUserModel),这样在用环信聊天的是不会那么麻烦,也可以自己写一个Model这样麻烦但是不乱,
#import <Foundation/Foundation.h>

#import "IUserModel.h"

@interface EaseUserModel : NSObject<IUserModel>

@property (strong, nonatomic, readonly) NSString *buddy;//用户名
@property (strong, nonatomic) NSString *nickname;//名字
@property (strong, nonatomic) NSString *avatarURLPath;//头像地址
@property (strong, nonatomic) UIImage *avatarImage;//头像图片

- (instancetype)initWithBuddy:(NSString *)buddy;

@end
中用户名是最有用的是直接用于创建聊天的,名字头像用处不大,因为这些存储都是在本地的,地方一改名字头像你这面可能没反应所以不用这个头像名字,都是使用消息中的扩展字段下面说。

三、环信聊天

聊天界面我推荐直接用EasyUI的,自己写各种消息cell实在麻烦,使用这个直接就是可以聊天的创建方法如下
EaseMessageViewController *viewController = [[EaseMessageViewController alloc] initWithConversationChatter:@"聊天对象的id,或者群组的id" conversationType:聊天的类型(单聊,群聊,聊天室)];
四、环信聊天消息重点*

在发消息过程中是不能传递用户名和头像的,所以我们要给消息添加扩展字段ext,(扩展字段可以其实就是发送消息是附带传递一个json数据),我们可以吧自己的头像和名字在每次发消息的时候发过去,这样聊天时,当前本人可以知道自己的名字和头像,聊天对方会在消息中发过来名字头像,这样两个人都有名字头像了,下面贴下我的扩展字段
- (void)_sendMessage:(EMMessage *)message //发消息的总方法
{
message.ext = @{@"nikeName":_myNikeName,@"headImagePath":_myHeadImagePath}; //添加消息扩展,我的名字,我的头像

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) {
[weakself.tableView reloadData];
}];
}
五、消息列表

消息列表的获取用环信的获取方法
- (void)tableViewDidTriggerHeaderRefresh
{
NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations];//本地获取消息
NSArray* sorted = [conversations sortedArrayUsingComparator:
^(EMConversation *obj1, EMConversation* obj2){
EMMessage *message1 = [obj1 latestMessage];
EMMessage *message2 = [obj2 latestMessage];
if(message1.timestamp > message2.timestamp) {
return(NSComparisonResult)NSOrderedAscending;
}else {
return(NSComparisonResult)NSOrderedDescending;
}
}];
[self.tableView endHeaderFresh];
[self.tableView reloadData];
}
这里有一个地方,再获得会话列表的时候,有几个数据要自己获取一下,头像,名字,最后一条消息内容,最后一条消息时间,消息未读数。其中消息未读数事环信帮我们做好的直接设置显示未读数就好
其中消息最后一条消息内容,最后一条消息时间。要我们获取最后一条消息在给其赋值
//在tableView代理中写
cell.detailLabel.attributedText = [[EaseEmotionEscape sharedInstance] attStringFromTextForChatting:[self _latestMessageTitleForConversationModel:model]textFont:cell.detailLabel.font];
cell.timeLabel.text = [self _latestMessageTimeForConversationModel:model];

//获取最后一条消息内容
- (NSString *)_latestMessageTitleForConversationModel:(id<IConversationModel>)conversationModel
{
NSString *latestMessageTitle = @"";
EMMessage *lastMessage = [conversationModel.conversation latestMessage];
if (lastMessage) {
EMMessageBody *messageBody = lastMessage.body;
switch (messageBody.type) {
case EMMessageBodyTypeImage:{
latestMessageTitle = NSEaseLocalizedString(@"message.image1", @"[image]");
} break;
case EMMessageBodyTypeText:{
NSString *didReceiveText = [EaseConvertToCommonEmoticonsHelper
convertToSystemEmoticons:((EMTextMessageBody *)messageBody).text];
latestMessageTitle = didReceiveText;
} break;
case EMMessageBodyTypeVoice:{
latestMessageTitle = NSEaseLocalizedString(@"message.voice1", @"[voice]");
} break;
case EMMessageBodyTypeLocation: {
latestMessageTitle = NSEaseLocalizedString(@"message.location1", @"[location]");
} break;
case EMMessageBodyTypeVideo: {
latestMessageTitle = NSEaseLocalizedString(@"message.video1", @"
[video]");
} break;
case EMMessageBodyTypeFile: {
latestMessageTitle = NSEaseLocalizedString(@"message.file1", @"[file]");
} break;
default: {
} break;
}
}
return latestMessageTitle;
}

//获取最后一条消息时间
- (NSString *)_latestMessageTimeForConversationModel:(id<IConversationModel>)conversationModel
{
NSString *latestMessageTime = @"";
EMMessage *lastMessage = [conversationModel.conversation latestMessage];;
if (lastMessage) {
double timeInterval = lastMessage.timestamp ;
if(timeInterval > 140000000000) {
timeInterval = timeInterval / 1000;
}
NSDateFormatter* formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:@"YYYY-MM-dd"];
latestMessageTime = [formatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:timeInterval]];
}
return latestMessageTime;
}[/video]




至于头像名字则要我们使用我没发消息的扩展字段,
这里我们可以获取会话中最后一条来自对方的消息,然后取出扩展字段赋值就好,(ps:有一种情况我刚刚和一个人说话会话中没有来自对方的最后一条消息,这个时候我们要用我们本地自己赋值这个会话的头像和名字)

三、总结

恩,这是这个杂记的第一次写的东西,写了写基本的用法,简单的聊天功能,写的也比较乱,,我会慢慢整理修改的。
 
作者简介:我是windKing,一个iOS开发的小白希望能有一样的人和我一起成长。看的过程中有不明白的地方欢迎联系我qq1662628982。
0
评论

环信SDK集成日记-自定义消息工具条和表情键盘 iOS 表情 环信 iOS集成 环信

beyond 发表了文章 • 1752 次浏览 • 2016-09-12 19:20 • 来自相关话题

今天看了下环信SDK聊天模块下封装的自定义消息工具条以及表情键盘,了解了下iOS自带表情的转码。顺[b]带记录下集成环信SDK遇到的坑。[/b]一、DXMessageToolBar工具条的封装

DXMessageToolBar是加在聊天页面底部的消息工具条,其负责控制四个控件:

1、录音视图DXRecordView
2、输入文本框XHMessageTextView
3、表情键盘DXFaceView
4、更多视图DXChatBarMoreView

DXMessageToolBar不仅要控制这四个UIView之间的切换,负责正确改变视图的位置与大小,还要将一系列的动作事件通过委托传到聊天页面控制器ChatViewController中去处理。

DXRecordView是录制音频的时候展示的视图,通过不断检测音量大小来展示不同的UIImage从而达到动态效果。

XHMessageTextView是继承自UITextView的,其中实现了自定义placeHolder的颜色,由于修改placeHolder是私有方法,因此这里换了种方式实现,那就是通过重写drawRect:方法,在其中将placeHolder绘制到UITextView上面。

DXFaceView表情键盘,它上面加了FacialView。FacialView上面放置了一些列的UIButton,UIButton的标题设置为iOS自带的表情。

DXChatBarMoreView更多视图,上面添加从相册选择照片,调用相机拍摄照片等按钮。

二、表情键盘的封装

表情键盘上放置了一些iOS自带的表情,而且在最后加了一个发送按钮和一个删除按钮,如图:





点击对应的表情,将表情字符串添加到文本框中。点击删除按钮通过委托调用了DXMessageToolBar的方法,其实现方式:- (void)selectedFacialView:(NSString *)str isDelete:(BOOL)isDelete
{
NSString *chatText = self.inputTextView.text;

if (!isDelete && str.length > 0) {
self.inputTextView.text = [NSString stringWithFormat:@"%@%@",chatText,str];
}
else {
if (chatText.length >= 2)
{
NSString *subStr = [chatText substringFromIndex:chatText.length-2];
if ([(DXFaceView *)self.faceView stringIsFace:subStr]) {
self.inputTextView.text = [chatText substringToIndex:chatText.length-2];
[self textViewDidChange:self.inputTextView];
return;
}
}

if (chatText.length > 0) {
self.inputTextView.text = [chatText substringToIndex:chatText.length-1];
}
}

[self textViewDidChange:self.inputTextView];
}由此可见一个表情字符串占两个字符的长度,所以要在点击删除按钮之后判断文本框当前字符串的末尾处是否是表情。

三、iOS自带表情的转码

每个表情都有它自己的编码,通过编码可以拿到它对应的表情字符串:#import <Foundation/Foundation.h>

#define MAKE_Q(x) @#x
#define MAKE_EM(x,y) MAKE_Q(x##y)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunicode"
#define MAKE_EMOJI(x) MAKE_EM(\U000,x)
#pragma clang diagnostic pop

#define EMOJI_METHOD(x,y) + (NSString *)x { return MAKE_EMOJI(y); }
#define EMOJI_HMETHOD(x) + (NSString *)x;
#define EMOJI_CODE_TO_SYMBOL(x) ((((0x808080F0 | (x & 0x3F000) >> 4) | (x & 0xFC0) << 10) | (x & 0x1C0000) << 18) | (x & 0x3F) << 24);

/*!
@class
@brief iOS内置表情编码处理类
*/
@interface Emoji : NSObject

/*!
@method
@brief unicode编码转换为iOS内置表情字符串
@discussion
@param code iOS内置表情对应的unicode编码值
@result iOS内置表情字符串
*/
+ (NSString *)emojiWithCode:(int)code;

/*!
@method
@brief 获取所有iOS内置表情
@discussion
@result iOS表情字符串数组
*/
+ (NSArray *)allEmoji;
@end

#import "Emoji.h"
#import "EmojiEmoticons.h"

@implementation Emoji
+ (NSString *)emojiWithCode:(int)code {
int sym = EMOJI_CODE_TO_SYMBOL(code);
return [[NSString alloc] initWithBytes:&sym length:sizeof(sym) encoding:NSUTF8StringEncoding];
}
+ (NSArray *)allEmoji {
NSMutableArray *array = [NSMutableArray new];
[array addObjectsFromArray:[EmojiEmoticons allEmoticons]];
return array;
}
@end这里拿表情编码做一系列运算,然后传入[[NSString alloc] initWithBytes:&sym length:sizeof(sym) encoding:NSUTF8StringEncoding];生成对应的字符串的运算方法暂时没找到解释,没搞清楚运算原理。若哪位大神看到出处,敬请告知!

四、集成环信遇到的坑

1、显示用户头像和昵称

环信默认不显示头像和昵称,如果要显示头像,需要自己实现。这里要实现显示头像和昵称,需要发消息的时候在ext这个扩展字段中加入和Android约定好的字段,在接收到消息的时候将这些字段保存到本地数据库中。

这里特别需要注意的一点是:在读取本地数据库的时候,是通过chatter来匹配的话,记得环信的chatter在保存的时候都做了小写处理。所以,在数据库中匹配的时候,最好是大小写不敏感的匹配方式:NSString *sql = [NSString stringWithFormat:@"select * from %@
where upper(chatter)=upper('%@')",_tbName,chatter];2、自定义推送内容

环信默认推送内容是“你有一条新的消息”,如果要自定义内容,需要在在ext扩展字段中添加以下字段:@"em_apns_ext":@{
@"em_push_title":文本内容
},3、发送用户信息给客服

如果要在用户跟客服聊天时能在客服后台看到当前用户的一些基本信息,需要在ext扩展字段中添加以下字段(比如需要用户手机号码和名字):@"weichat":@{
@"visitor":@{
@"phone":userPhone,
@"userNickname":userNickName,
}
},4、发送用户轨迹给客服

如果要在用户点击客服咨询时,比如是点击职位下面的咨询,想将当期职位的信息发给客服,那就是环信的发送用户轨迹信息。这个时候在进入聊天页面时需要单独处理,自动发送一个自己封装的信息:-(void)sendJobInfo:(YLJob *)aJob
{
NSMutableDictionary *ext = [NSMutableDictionary dictionary];
NSDictionary *msgtype = @{@"track":@{@"title":aJob.title?:@"",
@"item_url":aJob.link?:@""}};
[ext setObject:msgtype forKey:@"msgtype"];

EMChatText *text = [[EMChatText alloc] initWithText:[NSString stringWithFormat:@"我想咨询 %@",aJob.title?:@""]];
EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithChatObject:text];
EMMessage *message = [[EMMessage alloc] initWithReceiver:_chatter bodies:[NSArray arrayWithObject:body]];
message.ext = ext;
[[EaseMob sharedInstance].chatManager asyncSendMessage:message progress:nil];

}这里只是简单的使用了环信已经封装好的文本消息的接口,并且在聊天页面的展示方式也将是纯文本的方式Cell的展现方式。如果要以其他样式的Cell展示,需要自定义Cell,并且在展示的时候根据ext中的字段来判断是否是这种自定义消息。

其他具体可用字段名称,请登录环信官网查看官方文档。

参考:
以下是Emoji的表情编码对照表:
Emoji Unicode Tables
 
作者:Code_Ninja 查看全部
今天看了下环信SDK聊天模块下封装的自定义消息工具条以及表情键盘,了解了下iOS自带表情的转码。顺[b]带记录下集成环信SDK遇到的坑。[/b]
一、DXMessageToolBar工具条的封装

DXMessageToolBar是加在聊天页面底部的消息工具条,其负责控制四个控件:

1、录音视图DXRecordView
2、输入文本框XHMessageTextView
3、表情键盘DXFaceView
4、更多视图DXChatBarMoreView

DXMessageToolBar不仅要控制这四个UIView之间的切换,负责正确改变视图的位置与大小,还要将一系列的动作事件通过委托传到聊天页面控制器ChatViewController中去处理。

DXRecordView是录制音频的时候展示的视图,通过不断检测音量大小来展示不同的UIImage从而达到动态效果。

XHMessageTextView是继承自UITextView的,其中实现了自定义placeHolder的颜色,由于修改placeHolder是私有方法,因此这里换了种方式实现,那就是通过重写drawRect:方法,在其中将placeHolder绘制到UITextView上面。

DXFaceView表情键盘,它上面加了FacialView。FacialView上面放置了一些列的UIButton,UIButton的标题设置为iOS自带的表情。

DXChatBarMoreView更多视图,上面添加从相册选择照片,调用相机拍摄照片等按钮。

二、表情键盘的封装

表情键盘上放置了一些iOS自带的表情,而且在最后加了一个发送按钮和一个删除按钮,如图:

141604-f58b918d4fb3aad0.jpg

点击对应的表情,将表情字符串添加到文本框中。点击删除按钮通过委托调用了DXMessageToolBar的方法,其实现方式:
- (void)selectedFacialView:(NSString *)str isDelete:(BOOL)isDelete
{
NSString *chatText = self.inputTextView.text;

if (!isDelete && str.length > 0) {
self.inputTextView.text = [NSString stringWithFormat:@"%@%@",chatText,str];
}
else {
if (chatText.length >= 2)
{
NSString *subStr = [chatText substringFromIndex:chatText.length-2];
if ([(DXFaceView *)self.faceView stringIsFace:subStr]) {
self.inputTextView.text = [chatText substringToIndex:chatText.length-2];
[self textViewDidChange:self.inputTextView];
return;
}
}

if (chatText.length > 0) {
self.inputTextView.text = [chatText substringToIndex:chatText.length-1];
}
}

[self textViewDidChange:self.inputTextView];
}
由此可见一个表情字符串占两个字符的长度,所以要在点击删除按钮之后判断文本框当前字符串的末尾处是否是表情。

三、iOS自带表情的转码

每个表情都有它自己的编码,通过编码可以拿到它对应的表情字符串:
#import <Foundation/Foundation.h>

#define MAKE_Q(x) @#x
#define MAKE_EM(x,y) MAKE_Q(x##y)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunicode"
#define MAKE_EMOJI(x) MAKE_EM(\U000,x)
#pragma clang diagnostic pop

#define EMOJI_METHOD(x,y) + (NSString *)x { return MAKE_EMOJI(y); }
#define EMOJI_HMETHOD(x) + (NSString *)x;
#define EMOJI_CODE_TO_SYMBOL(x) ((((0x808080F0 | (x & 0x3F000) >> 4) | (x & 0xFC0) << 10) | (x & 0x1C0000) << 18) | (x & 0x3F) << 24);

/*!
@class
@brief iOS内置表情编码处理类
*/
@interface Emoji : NSObject

/*!
@method
@brief unicode编码转换为iOS内置表情字符串
@discussion
@param code iOS内置表情对应的unicode编码值
@result iOS内置表情字符串
*/
+ (NSString *)emojiWithCode:(int)code;

/*!
@method
@brief 获取所有iOS内置表情
@discussion
@result iOS表情字符串数组
*/
+ (NSArray *)allEmoji;
@end

#import "Emoji.h"
#import "EmojiEmoticons.h"

@implementation Emoji
+ (NSString *)emojiWithCode:(int)code {
int sym = EMOJI_CODE_TO_SYMBOL(code);
return [[NSString alloc] initWithBytes:&sym length:sizeof(sym) encoding:NSUTF8StringEncoding];
}
+ (NSArray *)allEmoji {
NSMutableArray *array = [NSMutableArray new];
[array addObjectsFromArray:[EmojiEmoticons allEmoticons]];
return array;
}
@end
这里拿表情编码做一系列运算,然后传入[[NSString alloc] initWithBytes:&sym length:sizeof(sym) encoding:NSUTF8StringEncoding];生成对应的字符串的运算方法暂时没找到解释,没搞清楚运算原理。若哪位大神看到出处,敬请告知!

四、集成环信遇到的坑

1、显示用户头像和昵称

环信默认不显示头像和昵称,如果要显示头像,需要自己实现。这里要实现显示头像和昵称,需要发消息的时候在ext这个扩展字段中加入和Android约定好的字段,在接收到消息的时候将这些字段保存到本地数据库中。

这里特别需要注意的一点是:在读取本地数据库的时候,是通过chatter来匹配的话,记得环信的chatter在保存的时候都做了小写处理。所以,在数据库中匹配的时候,最好是大小写不敏感的匹配方式:
NSString *sql = [NSString stringWithFormat:@"select * from %@ 
where upper(chatter)=upper('%@')",_tbName,chatter];
2、自定义推送内容

环信默认推送内容是“你有一条新的消息”,如果要自定义内容,需要在在ext扩展字段中添加以下字段:
@"em_apns_ext":@{                           
@"em_push_title":文本内容
},
3、发送用户信息给客服

如果要在用户跟客服聊天时能在客服后台看到当前用户的一些基本信息,需要在ext扩展字段中添加以下字段(比如需要用户手机号码和名字):
@"weichat":@{
@"visitor":@{
@"phone":userPhone,
@"userNickname":userNickName,
}
},
4、发送用户轨迹给客服

如果要在用户点击客服咨询时,比如是点击职位下面的咨询,想将当期职位的信息发给客服,那就是环信的发送用户轨迹信息。这个时候在进入聊天页面时需要单独处理,自动发送一个自己封装的信息:
-(void)sendJobInfo:(YLJob *)aJob
{
NSMutableDictionary *ext = [NSMutableDictionary dictionary];
NSDictionary *msgtype = @{@"track":@{@"title":aJob.title?:@"",
@"item_url":aJob.link?:@""}};
[ext setObject:msgtype forKey:@"msgtype"];

EMChatText *text = [[EMChatText alloc] initWithText:[NSString stringWithFormat:@"我想咨询 %@",aJob.title?:@""]];
EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithChatObject:text];
EMMessage *message = [[EMMessage alloc] initWithReceiver:_chatter bodies:[NSArray arrayWithObject:body]];
message.ext = ext;
[[EaseMob sharedInstance].chatManager asyncSendMessage:message progress:nil];

}
这里只是简单的使用了环信已经封装好的文本消息的接口,并且在聊天页面的展示方式也将是纯文本的方式Cell的展现方式。如果要以其他样式的Cell展示,需要自定义Cell,并且在展示的时候根据ext中的字段来判断是否是这种自定义消息。

其他具体可用字段名称,请登录环信官网查看官方文档。

参考:
以下是Emoji的表情编码对照表:
Emoji Unicode Tables
 
作者:Code_Ninja
0
评论

【环信集成笔记】进阶篇-集成环信 ios 2.0,看这篇就够了 环信 环信 iOS集成 iOS 环信集成笔记

beyond 发表了文章 • 1975 次浏览 • 2016-09-12 19:12 • 来自相关话题

一、准备工作
 
1、注册环信帐号注册一个环信账号之后,我们用注册的帐号登陆。然后创建一个应用,会得到一个对应的AppKey,这个AppKey在初始化环信SDK的时候需要用到。(这个去环信官网自己弄环信)
 
2、制作推送证书如果需要做离线推送的功能,需要制作一个推送证书。如果只是需要实现单聊、群聊等功能,可以跳过此步骤。个人建议刚开始接触环信的开发者可以忽略此步骤。制作证书
 
3、下载环信sdk.下的是2.0





二、集成环信的SDK

1、把环信SDK添加到工程中

从环信官网下载下来的是一个压缩包,解压之后,把我们需要的环信SDK,即EaseMobSDK这个文件夹,整个的拖入到我们的工程中。如下图:




在lib文件夹下面有两个静态库,只需要用到一个,根据你的需求选择。libEaseMobClientSDKLite.a不包含实时语音功能,libEaseMobClientSDK.a包含所有功能。2、添加对应的依赖库

向Build Phases → Link Binary With Libraries 中添加依赖库
MobileCoreServices.frameworkCFNetwork.framelibsqlite3.tbdlibstdc++.6.0.9.tbdlibz.tbdlibiconv.tbdlibresolv.tbdlibxml2.tbd
温馨提示:注意不要添加错了,也不能添加少了,添加完毕之后,不要着急,先编译一下。编译成功,则说明没有问题;如果编译报错,则仔细对照上面例举的静态库进行添加,直到编译成功,再进行下一步。
 
3、配置工程
 
3.1 不包含语音静态库的配置方法
(1) 删掉libEaseMobClientSDK.a,保留libEaseMobClientSDKLite.a;
(2) 在Build Settings -> Other Linker Flags 添加”fore_load”和”libEaseMobClientSDKLite.a”的相对路径。
如下图所示:




3.2 包含语音静态库的配置方法

(1) 删掉libEaseMobClientSDKLite.a,保留libEaseMobClientSDK.a;

(2) 在Build Settings -> Other Linker Flags 添加”-ObjC”。

如下图所示:




4、验证SDK是否添加成功
在AppDelegate.m文件中添加环信SDK初始化的方法,记得添加头文件”EaseMob.h”。下面提供了我用的测试AppKey,你可以替换成你自己申请的AppKey。编译成功,则说明你已经正确集成了环信的SDK了。
 
如果编译有问题,可能存在的原因:
(1) 静态库没有添加正确;
(2) 静态库工程配置不正确#define APPKEY @"1101#testrongyun" //环信APPKEY
#define APNSCert @"TestHuanXin" //环信推送证书名称
#import "AppDelegate.h"
#import "EaseMob.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//初始化环信SDK
[[EaseMob sharedInstance] registerSDKWithAppKey:APPKEY apnsCertName:APNSCert];
return YES;
}






三、添加UI文件到你的工程

集成环信2.0UI文件,需要添加的文件,如下图所示:




添加完成之后,如下图所示:




四、设置pch文件的路径
 
文件添加成功之后,编译会报错,因为你没有添加pch文件。自己手动添加pch文件(EaseUI-Prefix.pch),设置一下pch文件的加载路径即可。如下图所示:




在EaseUI-Prefix.pch中添加头文件”EaseUI.h”,如下图:




最后,编译一下,编译成功则说明添加集成UI文件成功。
 
五,搭建基本框架
 
1、新建三个UIViewController
 
新建三个ViewController,继承UIViewController,分别命名为:FirstViewController,SecondViewController,ThirdViewController。如下图所示





2、添加登陆方法

在AppDelegate.m中添加如下代码:#define APPKEY @"1101#testrongyun" //环信APPKEY
#define APNSCert @"TestHuanXin" //环信推送证书名称
#import "AppDelegate.h"
#import "EaseMob.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "ThirdViewController.h"
@interface AppDelegate ()
@end@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//初始化环信SDK
[[EaseMob sharedInstance] registerSDKWithAppKey:APPKEY apnsCertName:APNSCert];
//异步登陆的方法(这里的账号密码要去环信后台自己注册)
[[EaseMob sharedInstance].chatManager asyncLoginWithUsername:@"账号" password:@"密码" completion:^(NSDictionary *loginInfo, EMError *error) {
if (!error && loginInfo) {
NSLog(@"登陆成功");
[self setUpNav];
}
} onQueue:nil];
return YES;
}
- (void)setUpNav
{
FirstViewController *firstVC = [[FirstViewController alloc] init];
SecondViewController *secondVC = [[SecondViewController alloc] init];
ThirdViewController *thirdVC = [[ThirdViewController alloc] init];
firstVC.title = @"会话列表";
secondVC.title = @"通讯录";
thirdVC.title = @"设置";
UITabBarController *tabBar = [[UITabBarController alloc] init];
tabBar.viewControllers = @[[[UINavigationController alloc] initWithRootViewController:firstVC],
[[UINavigationController alloc] initWithRootViewController:secondVC],
[[UINavigationController alloc] initWithRootViewController:thirdVC]];
self.window.rootViewController = tabBar;
self.window.backgroundColor = [UIColor whiteColor];
}
@end编译一下,看下效果。




六、添加与聊天有关的文件

1、添加GifImage文件2、添加chat文件








添加完成之后,编译一下,把报错的地方全部注释掉,有很多地方需要注释掉,这些地方是因为有些我们不需要的文件没有添加进来。(自己注释比较麻烦)
 
注释好的GifImage和chat文件,下载后无需注释无关代码,可直接使用注释好的文件,
 
七、实现单聊在SecondViewController.m中添加如下代码:#import "SecondViewController.h"#import "ChatViewController.h"@interface SecondViewController (){
NSArray *arrSystem;
NSArray *arrFriends;
}
@property (retain, nonatomic) UITableView *tableView;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
arrSystem = @[@"申请与通知",@"群聊",@"聊天室"];
_tableView = [[UITableView alloc] initWithFrame:self.view.frame];
_tableView.delegate = self;
_tableView.dataSource = self;
[self.view addSubview:_tableView];
//获取好友列表
[[EaseMob sharedInstance].chatManager asyncFetchBuddyListWithCompletion:^(NSArray *buddyList, EMError *error) {
if (!error) {
NSLog(@"获取成功 -- %@",buddyList);
arrFriends = [NSArray arrayWithArray:buddyList];
[_tableView reloadData];
}
} onQueue:nil];
}
#pragma mark - UITableViewDelegate & UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return arrSystem.count;
} else {
return arrFriends.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"CELL";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
switch (indexPath.section) {
case 0:
{
cell.textLabel.text = [arrSystem objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:@"groupPublicHeader"];
break;
}
case 1:
{
EMBuddy *eMBuddy = [arrFriends objectAtIndex:indexPath.row];
cell.textLabel.text = eMBuddy.username;
cell.imageView.image = [UIImage imageNamed:@"chatListCellHead"];
break;
}
default:
break;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
EMBuddy *buddy = [arrFriends objectAtIndex:indexPath.row];
ChatViewController *chatVC = [[ChatViewController alloc] initWithConversationChatter:buddy.username conversationType:eConversationTypeChat];
chatVC.title = buddy.username; //好友的名字
chatVC.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:chatVC animated:YES];
}编译,效果








真机运行一下,可能会报错,





解决方案:




把这个值设置成no

文章作者:环信热心用户樊呵呵 查看全部
一、准备工作
 
1、注册环信帐号注册一个环信账号之后,我们用注册的帐号登陆。然后创建一个应用,会得到一个对应的AppKey,这个AppKey在初始化环信SDK的时候需要用到。(这个去环信官网自己弄环信)
 
2、制作推送证书如果需要做离线推送的功能,需要制作一个推送证书。如果只是需要实现单聊、群聊等功能,可以跳过此步骤。个人建议刚开始接触环信的开发者可以忽略此步骤。制作证书
 
3、下载环信sdk.下的是2.0
2267403-9029e76fb6048493.jpg


二、集成环信的SDK

1、把环信SDK添加到工程中

从环信官网下载下来的是一个压缩包,解压之后,把我们需要的环信SDK,即EaseMobSDK这个文件夹,整个的拖入到我们的工程中。如下图:
2267403-2b2574629722ab00.jpg

在lib文件夹下面有两个静态库,只需要用到一个,根据你的需求选择。
libEaseMobClientSDKLite.a不包含实时语音功能,libEaseMobClientSDK.a包含所有功能。
2、添加对应的依赖库

向Build Phases → Link Binary With Libraries 中添加依赖库
  1. MobileCoreServices.framework
  2. CFNetwork.frame
  3. libsqlite3.tbd
  4. libstdc++.6.0.9.tbd
  5. libz.tbd
  6. libiconv.tbd
  7. libresolv.tbd
  8. libxml2.tbd

温馨提示:注意不要添加错了,也不能添加少了,添加完毕之后,不要着急,先编译一下。编译成功,则说明没有问题;如果编译报错,则仔细对照上面例举的静态库进行添加,直到编译成功,再进行下一步。
 
3、配置工程
 
3.1 不包含语音静态库的配置方法
(1) 删掉libEaseMobClientSDK.a,保留libEaseMobClientSDKLite.a;
(2) 在Build Settings -> Other Linker Flags 添加”fore_load”和”libEaseMobClientSDKLite.a”的相对路径。
如下图所示:
2267403-18e5a986d5358977.jpg

3.2 包含语音静态库的配置方法

(1) 删掉libEaseMobClientSDKLite.a,保留libEaseMobClientSDK.a;

(2) 在Build Settings -> Other Linker Flags 添加”-ObjC”。

如下图所示:
2267403-9aa919e2913c9de1.jpg

4、验证SDK是否添加成功
在AppDelegate.m文件中添加环信SDK初始化的方法,记得添加头文件”EaseMob.h”。下面提供了我用的测试AppKey,你可以替换成你自己申请的AppKey。编译成功,则说明你已经正确集成了环信的SDK了。
 
如果编译有问题,可能存在的原因:
(1) 静态库没有添加正确;
(2) 静态库工程配置不正确
#define APPKEY      @"1101#testrongyun"     //环信APPKEY
#define APNSCert @"TestHuanXin" //环信推送证书名称
#import "AppDelegate.h"
#import "EaseMob.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//初始化环信SDK
[[EaseMob sharedInstance] registerSDKWithAppKey:APPKEY apnsCertName:APNSCert];
return YES;
}






三、添加UI文件到你的工程

集成环信2.0UI文件,需要添加的文件,如下图所示:
2267403-8f645439e0dface8.jpg

添加完成之后,如下图所示:
2267403-b2667ca172b29da9.jpg

四、设置pch文件的路径
 
文件添加成功之后,编译会报错,因为你没有添加pch文件。自己手动添加pch文件(EaseUI-Prefix.pch),设置一下pch文件的加载路径即可。如下图所示:
2267403-da9cd01f79acf31a.jpg

在EaseUI-Prefix.pch中添加头文件”EaseUI.h”,如下图:
2267403-0280af41aced006c.jpg

最后,编译一下,编译成功则说明添加集成UI文件成功。
 
五,搭建基本框架
 
1、新建三个UIViewController
 
新建三个ViewController,继承UIViewController,分别命名为:FirstViewController,SecondViewController,ThirdViewController。如下图所示

2267403-ad785fff16328185.jpg

2、添加登陆方法

在AppDelegate.m中添加如下代码:
#define APPKEY      @"1101#testrongyun"     //环信APPKEY
#define APNSCert @"TestHuanXin" //环信推送证书名称
#import "AppDelegate.h"
#import "EaseMob.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "ThirdViewController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//初始化环信SDK
[[EaseMob sharedInstance] registerSDKWithAppKey:APPKEY apnsCertName:APNSCert];
//异步登陆的方法(这里的账号密码要去环信后台自己注册)
[[EaseMob sharedInstance].chatManager asyncLoginWithUsername:@"账号" password:@"密码" completion:^(NSDictionary *loginInfo, EMError *error) {
if (!error && loginInfo) {
NSLog(@"登陆成功");
[self setUpNav];
}
} onQueue:nil];
return YES;
}
- (void)setUpNav
{
FirstViewController *firstVC = [[FirstViewController alloc] init];
SecondViewController *secondVC = [[SecondViewController alloc] init];
ThirdViewController *thirdVC = [[ThirdViewController alloc] init];
firstVC.title = @"会话列表";
secondVC.title = @"通讯录";
thirdVC.title = @"设置";
UITabBarController *tabBar = [[UITabBarController alloc] init];
tabBar.viewControllers = @[[[UINavigationController alloc] initWithRootViewController:firstVC],
[[UINavigationController alloc] initWithRootViewController:secondVC],
[[UINavigationController alloc] initWithRootViewController:thirdVC]];
self.window.rootViewController = tabBar;
self.window.backgroundColor = [UIColor whiteColor];
}
@end
编译一下,看下效果。
2267403-687ab41414eac944.png

六、添加与聊天有关的文件

1、添加GifImage文件2、添加chat文件
2267403-7543120b78599164.jpg

2267403-fc7cc6d2aac8ddb8.jpg

添加完成之后,编译一下,把报错的地方全部注释掉,有很多地方需要注释掉,这些地方是因为有些我们不需要的文件没有添加进来。(自己注释比较麻烦)
 
注释好的GifImage和chat文件,下载后无需注释无关代码,可直接使用注释好的文件,
 
七、实现单聊在SecondViewController.m中添加如下代码:
#import "SecondViewController.h"#import "ChatViewController.h"@interface SecondViewController (){
NSArray *arrSystem;
NSArray *arrFriends;
}
@property (retain, nonatomic) UITableView *tableView;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
arrSystem = @[@"申请与通知",@"群聊",@"聊天室"];
_tableView = [[UITableView alloc] initWithFrame:self.view.frame];
_tableView.delegate = self;
_tableView.dataSource = self;
[self.view addSubview:_tableView];
//获取好友列表
[[EaseMob sharedInstance].chatManager asyncFetchBuddyListWithCompletion:^(NSArray *buddyList, EMError *error) {
if (!error) {
NSLog(@"获取成功 -- %@",buddyList);
arrFriends = [NSArray arrayWithArray:buddyList];
[_tableView reloadData];
}
} onQueue:nil];
}
#pragma mark - UITableViewDelegate & UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return arrSystem.count;
} else {
return arrFriends.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"CELL";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
switch (indexPath.section) {
case 0:
{
cell.textLabel.text = [arrSystem objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:@"groupPublicHeader"];
break;
}
case 1:
{
EMBuddy *eMBuddy = [arrFriends objectAtIndex:indexPath.row];
cell.textLabel.text = eMBuddy.username;
cell.imageView.image = [UIImage imageNamed:@"chatListCellHead"];
break;
}
default:
break;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
EMBuddy *buddy = [arrFriends objectAtIndex:indexPath.row];
ChatViewController *chatVC = [[ChatViewController alloc] initWithConversationChatter:buddy.username conversationType:eConversationTypeChat];
chatVC.title = buddy.username; //好友的名字
chatVC.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:chatVC animated:YES];
}
编译,效果
2267403-b19aab1a08432dfb.png

2267403-f96603327daee041.png

真机运行一下,可能会报错,

2267403-683387308562a7d6.jpg

解决方案:
2267403-4a01a8288cd4c024.jpg

把这个值设置成no

文章作者:环信热心用户樊呵呵