iOS

iOS

1
评论

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

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

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天
 
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
2
评论

【视频教程+源码】基于环信IM做一个仿微信APP-更新ing 郭永峰 高仿微信 仿微信 环信 XMPP

郭永峰 发表了文章 • 1110 次浏览 • 2017-05-16 15:29 • 来自相关话题

我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。
    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
4月12号成立工作室,现在18号,过了一个星期一个星期录了5天的环信教程视频,我将放在网盘免费分享环信的教程视频主要是针对有开发经验者教程主要是使用环信来模仿微信来做一个即时通讯的案例课程主要是先讲socket基础 -> 环信 ->自定义协议希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解同时能希望大家在面试时,在即时通讯这块不在陌生
  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏






支付宝打赏






谢谢大家的支持,个人微信号清扫描下面张图






 
郭永峰IT交流QQ群请加:596441895 查看全部
我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。

    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
  1. 4月12号成立工作室,现在18号,过了一个星期
  2. 一个星期录了5天的环信教程视频,我将放在网盘免费分享
  3. 环信的教程视频主要是针对有开发经验者
  4. 教程主要是使用环信来模仿微信来做一个即时通讯的案例
  5. 课程主要是先讲socket基础 -> 环信 ->自定义协议
  6. 希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解
  7. 同时能希望大家在面试时,在即时通讯这块不在陌生

  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏

微信.png


支付宝打赏

支付宝.png


谢谢大家的支持,个人微信号清扫描下面张图

个人.png


 
郭永峰IT交流QQ群请加:596441895
1
评论

【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了 环信公开课 昵称头像 环信大表哥

beyond 发表了文章 • 742 次浏览 • 2017-05-08 11:55 • 来自相关话题

 ​ 青年的思想愈被榜样的力量所激动,就愈会发出强烈的光辉-法捷耶夫
  在刚刚过去的五四青年节,环信公开课第12期如期举行,这期公开课嘉宾“环信大表哥”一人手写三端,Android/IOS/服务端信手捏来,关于用户体系更是引经据典一番,整场公开课妙语连珠、妙趣横生。加上环信MM的现场答疑,这样一来便消除了拘谨,活跃了气氛,环信小伙伴们在欢快愉悦的气氛中度过了这个有意义的五四青年节! 

环信公开课12期讲了什么?
如何利用消息扩展属性显示昵称头像?如何通过APP服务器处理昵称头像的显示?昵称头像的本地缓存策略?音视频通话如何显示昵称头像?

关于环信大表哥:
   马骏斌丨美国海遇网络CTO,传说中的祖传CTO“环信大表哥”,10年研发经验,现任美国海遇网络CTO。曾就职于北京超图从事GIS研发,负责基于SuperMap平台研究GIS算法以及图形处理工具,优化底层三维渲染引擎,协助产品研发进行数据处理或空间分析工作。

环信简版demo作者,开源了基于环信的直播项目-小马直播间,在imgeek、简书等社区发表了数十篇环信教程,他的教程和开源项目累计帮助了超过1W多名开发者集成环信,自己创建了环信互帮互助群,每天"夜黑风高"活跃在群里回答问题+远程写代码,人送外号“环信大表哥”。
 
自2011年2月创办www.C3DN.net(中国3D技术开发者社区),并兼任C3DN站长。创办C3DN,不仅为了延续对3D技术的热爱,最主要是想帮助更多需要帮助的人学习3D开发技术;

先放一张大表哥PPT截图,这个style!你们感(la)受(yan)下(jing)。





环信公开课视频回放:视频观看地址
  
环信公开课第12期讲师PPT在文末下载,最后引用一句某知名互联网公司创始人名言致大表哥“百年之后,你我的肉身终将陨灭,而我们的精神和梦想依然可以在代码中相见”
ios简版demo地址
Android简版demo地址 查看全部
 
​ 青年的思想愈被榜样的力量所激动,就愈会发出强烈的光辉-法捷耶夫

  在刚刚过去的五四青年节,环信公开课第12期如期举行,这期公开课嘉宾“环信大表哥”一人手写三端,Android/IOS/服务端信手捏来,关于用户体系更是引经据典一番,整场公开课妙语连珠、妙趣横生。加上环信MM的现场答疑,这样一来便消除了拘谨,活跃了气氛,环信小伙伴们在欢快愉悦的气氛中度过了这个有意义的五四青年节! 

环信公开课12期讲了什么?
  1. 如何利用消息扩展属性显示昵称头像?
  2. 如何通过APP服务器处理昵称头像的显示?
  3. 昵称头像的本地缓存策略?
  4. 音视频通话如何显示昵称头像?


关于环信大表哥:

   马骏斌丨美国海遇网络CTO,传说中的祖传CTO“环信大表哥”,10年研发经验,现任美国海遇网络CTO。曾就职于北京超图从事GIS研发,负责基于SuperMap平台研究GIS算法以及图形处理工具,优化底层三维渲染引擎,协助产品研发进行数据处理或空间分析工作。

环信简版demo作者,开源了基于环信的直播项目-小马直播间,在imgeek、简书等社区发表了数十篇环信教程,他的教程和开源项目累计帮助了超过1W多名开发者集成环信,自己创建了环信互帮互助群,每天"夜黑风高"活跃在群里回答问题+远程写代码,人送外号“环信大表哥”。
 
自2011年2月创办www.C3DN.net(中国3D技术开发者社区),并兼任C3DN站长。创办C3DN,不仅为了延续对3D技术的热爱,最主要是想帮助更多需要帮助的人学习3D开发技术;



先放一张大表哥PPT截图,这个style!你们感(la)受(yan)下(jing)。
QQ截图20170508115114.jpg


环信公开课视频回放:视频观看地址
  
环信公开课第12期讲师PPT在文末下载,最后引用一句某知名互联网公司创始人名言致大表哥“百年之后,你我的肉身终将陨灭,而我们的精神和梦想依然可以在代码中相见
ios简版demo地址
Android简版demo地址
1
回复

在pct中导入EMSDKFull.h时报错 iOS 环信

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

2
回复

iOS 环信2.0 发送消息重复。 iOS

刘小刘 回复了问题 • 3 人关注 • 876 次浏览 • 2017-05-22 20:01 • 来自相关话题

1
回复

为什么环信的demo里边群主可以屏蔽群消息? iOS

木云落 回复了问题 • 2 人关注 • 39 次浏览 • 2017-05-22 17:14 • 来自相关话题

2
评论

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

木云落 发表了文章 • 140 次浏览 • 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 版本或者不想导入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 版本或者不想导入Lite版的 , 只想引入EaseUI 
这时需要把 #import <Hyphenate/Hyphenate.h>注释掉,然后把报错地方的Hyphenate换成HyphenateLite就可以了
 
 
11. 
1.png

A : 可以删除或者重命名Podfile.lock文件,重新执行pod install命令
8
回复

各位大神,有单聊集成音视频的demo吗?iOS3.0的? 音视频 iOS 环信_iOS

SN 回复了问题 • 8 人关注 • 979 次浏览 • 2017-05-12 14:29 • 来自相关话题

0
评论

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

奋斗的蜗牛 发表了文章 • 78 次浏览 • 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
4
回复

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

yúañ來諟ィ厼ˇ¶﹏,g 回复了问题 • 2 人关注 • 115 次浏览 • 2017-05-04 09:39 • 来自相关话题

2
最佳

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

asd777 回复了问题 • 2 人关注 • 124 次浏览 • 2017-04-20 15:33 • 来自相关话题

1
回复

跟着视频一起写的demo 环信 iOS

xxl 回复了问题 • 2 人关注 • 191 次浏览 • 2017-04-12 15:59 • 来自相关话题

3
回复

手动集成EaseUI,Tabbar变黑色 iOS

lizilong 回复了问题 • 3 人关注 • 125 次浏览 • 2017-04-12 15:37 • 来自相关话题

1
回复

环信视频通话,A用户和B用户怎么才能一连接就用扬声器视频对话呢? iOS 环信 音视频

lizilong 回复了问题 • 2 人关注 • 726 次浏览 • 2017-04-11 15:01 • 来自相关话题

1
回复

有没有人做过环信图片连续滑动?能稍微解释一下基本步骤吗? 环信_iOS iOS

donghai 回复了问题 • 2 人关注 • 119 次浏览 • 2017-04-10 17:27 • 来自相关话题

1
最佳

ios 环信最新版本 打包上线时候报错 审核bug 集成上线 iOS

donghai 回复了问题 • 2 人关注 • 250 次浏览 • 2017-03-31 18:10 • 来自相关话题

4
回复

怎么修改EaseChatBarMoreView,自定义高度,自定义图标 iOS

zjjzmw1 回复了问题 • 3 人关注 • 1097 次浏览 • 2017-03-22 17:09 • 来自相关话题

5
回复

ios 环信创建会话失败 iOS 环信

zl 回复了问题 • 3 人关注 • 245 次浏览 • 2017-03-20 09:54 • 来自相关话题

1
回复

看视频如果出现问题可以看这里 dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate iOS 环信

donghai 回复了问题 • 2 人关注 • 737 次浏览 • 2017-03-15 12:33 • 来自相关话题

2
最佳

环信服务端集成,如何集成? iOS Android webim

执念 回复了问题 • 3 人关注 • 1269 次浏览 • 2017-03-13 11:36 • 来自相关话题

0
评论

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

新闻资讯 发表了文章 • 282 次浏览 • 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 产品更新

产品更新 发表了文章 • 426 次浏览 • 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下载
2
最佳

我在环信登录成功的回调中插入了一条消息,但是数据库中并没有这一条消息请问是为什么? iOS

zl 回复了问题 • 2 人关注 • 192 次浏览 • 2017-03-03 19:31 • 来自相关话题

8
回复

fudh环信ios工程师 能不能把你集成音视频的demo发给我1217212724 或者给一个连接 iOS 环信 音视频

笑笑骗你一世~ 回复了问题 • 9 人关注 • 1308 次浏览 • 2017-02-24 13:51 • 来自相关话题

1
评论

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

不死小强 发表了文章 • 531 次浏览 • 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

产品更新 发表了文章 • 454 次浏览 • 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下载
2
回复

从环信数据库获取消息时,能将ext扩展里的信息作为查询条件吗? 环信_iOS iOS

zhangyb 回复了问题 • 2 人关注 • 186 次浏览 • 2017-02-17 19:28 • 来自相关话题

1
最佳

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

环信沈冲 回复了问题 • 2 人关注 • 234 次浏览 • 2017-02-15 12:14 • 来自相关话题

条新动态, 点击查看
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 然后获取出来之后去显示吧
 
参考demo中chatviewcontroller这个方法:
//通过会话管理者获取已收发消息
    [self tableViewDidTriggerHeaderRefresh];
参考demo中chatviewcontroller这个方法:
//通过会话管理者获取已收发消息
    [self tableViewDidTriggerHeaderRefresh];
根控制器中这个回调方法没实现吧:/*!
 @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 • 24 个回复 不感兴趣

求一份 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保存后读取不出来

赞同来自:

具体操作代码贴一下。
具体操作代码贴一下。
1
评论

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

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

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天
 
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
2
评论

【视频教程+源码】基于环信IM做一个仿微信APP-更新ing 郭永峰 高仿微信 仿微信 环信 XMPP

郭永峰 发表了文章 • 1110 次浏览 • 2017-05-16 15:29 • 来自相关话题

我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。
    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
4月12号成立工作室,现在18号,过了一个星期一个星期录了5天的环信教程视频,我将放在网盘免费分享环信的教程视频主要是针对有开发经验者教程主要是使用环信来模仿微信来做一个即时通讯的案例课程主要是先讲socket基础 -> 环信 ->自定义协议希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解同时能希望大家在面试时,在即时通讯这块不在陌生
  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏






支付宝打赏






谢谢大家的支持,个人微信号清扫描下面张图






 
郭永峰IT交流QQ群请加:596441895 查看全部
我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。

    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
  1. 4月12号成立工作室,现在18号,过了一个星期
  2. 一个星期录了5天的环信教程视频,我将放在网盘免费分享
  3. 环信的教程视频主要是针对有开发经验者
  4. 教程主要是使用环信来模仿微信来做一个即时通讯的案例
  5. 课程主要是先讲socket基础 -> 环信 ->自定义协议
  6. 希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解
  7. 同时能希望大家在面试时,在即时通讯这块不在陌生

  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏

微信.png


支付宝打赏

支付宝.png


谢谢大家的支持,个人微信号清扫描下面张图

个人.png


 
郭永峰IT交流QQ群请加:596441895
1
评论

【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了 环信公开课 昵称头像 环信大表哥

beyond 发表了文章 • 742 次浏览 • 2017-05-08 11:55 • 来自相关话题

 ​ 青年的思想愈被榜样的力量所激动,就愈会发出强烈的光辉-法捷耶夫
  在刚刚过去的五四青年节,环信公开课第12期如期举行,这期公开课嘉宾“环信大表哥”一人手写三端,Android/IOS/服务端信手捏来,关于用户体系更是引经据典一番,整场公开课妙语连珠、妙趣横生。加上环信MM的现场答疑,这样一来便消除了拘谨,活跃了气氛,环信小伙伴们在欢快愉悦的气氛中度过了这个有意义的五四青年节! 

环信公开课12期讲了什么?
如何利用消息扩展属性显示昵称头像?如何通过APP服务器处理昵称头像的显示?昵称头像的本地缓存策略?音视频通话如何显示昵称头像?

关于环信大表哥:
   马骏斌丨美国海遇网络CTO,传说中的祖传CTO“环信大表哥”,10年研发经验,现任美国海遇网络CTO。曾就职于北京超图从事GIS研发,负责基于SuperMap平台研究GIS算法以及图形处理工具,优化底层三维渲染引擎,协助产品研发进行数据处理或空间分析工作。

环信简版demo作者,开源了基于环信的直播项目-小马直播间,在imgeek、简书等社区发表了数十篇环信教程,他的教程和开源项目累计帮助了超过1W多名开发者集成环信,自己创建了环信互帮互助群,每天"夜黑风高"活跃在群里回答问题+远程写代码,人送外号“环信大表哥”。
 
自2011年2月创办www.C3DN.net(中国3D技术开发者社区),并兼任C3DN站长。创办C3DN,不仅为了延续对3D技术的热爱,最主要是想帮助更多需要帮助的人学习3D开发技术;

先放一张大表哥PPT截图,这个style!你们感(la)受(yan)下(jing)。





环信公开课视频回放:视频观看地址
  
环信公开课第12期讲师PPT在文末下载,最后引用一句某知名互联网公司创始人名言致大表哥“百年之后,你我的肉身终将陨灭,而我们的精神和梦想依然可以在代码中相见”
ios简版demo地址
Android简版demo地址 查看全部
 
​ 青年的思想愈被榜样的力量所激动,就愈会发出强烈的光辉-法捷耶夫

  在刚刚过去的五四青年节,环信公开课第12期如期举行,这期公开课嘉宾“环信大表哥”一人手写三端,Android/IOS/服务端信手捏来,关于用户体系更是引经据典一番,整场公开课妙语连珠、妙趣横生。加上环信MM的现场答疑,这样一来便消除了拘谨,活跃了气氛,环信小伙伴们在欢快愉悦的气氛中度过了这个有意义的五四青年节! 

环信公开课12期讲了什么?
  1. 如何利用消息扩展属性显示昵称头像?
  2. 如何通过APP服务器处理昵称头像的显示?
  3. 昵称头像的本地缓存策略?
  4. 音视频通话如何显示昵称头像?


关于环信大表哥:

   马骏斌丨美国海遇网络CTO,传说中的祖传CTO“环信大表哥”,10年研发经验,现任美国海遇网络CTO。曾就职于北京超图从事GIS研发,负责基于SuperMap平台研究GIS算法以及图形处理工具,优化底层三维渲染引擎,协助产品研发进行数据处理或空间分析工作。

环信简版demo作者,开源了基于环信的直播项目-小马直播间,在imgeek、简书等社区发表了数十篇环信教程,他的教程和开源项目累计帮助了超过1W多名开发者集成环信,自己创建了环信互帮互助群,每天"夜黑风高"活跃在群里回答问题+远程写代码,人送外号“环信大表哥”。
 
自2011年2月创办www.C3DN.net(中国3D技术开发者社区),并兼任C3DN站长。创办C3DN,不仅为了延续对3D技术的热爱,最主要是想帮助更多需要帮助的人学习3D开发技术;



先放一张大表哥PPT截图,这个style!你们感(la)受(yan)下(jing)。
QQ截图20170508115114.jpg


环信公开课视频回放:视频观看地址
  
环信公开课第12期讲师PPT在文末下载,最后引用一句某知名互联网公司创始人名言致大表哥“百年之后,你我的肉身终将陨灭,而我们的精神和梦想依然可以在代码中相见
ios简版demo地址
Android简版demo地址
2
评论

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

o孟力o 发表了文章 • 7331 次浏览 • 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了
1
评论

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

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

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天
 
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
2
评论

【视频教程+源码】基于环信IM做一个仿微信APP-更新ing 郭永峰 高仿微信 仿微信 环信 XMPP

郭永峰 发表了文章 • 1110 次浏览 • 2017-05-16 15:29 • 来自相关话题

我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。
    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
4月12号成立工作室,现在18号,过了一个星期一个星期录了5天的环信教程视频,我将放在网盘免费分享环信的教程视频主要是针对有开发经验者教程主要是使用环信来模仿微信来做一个即时通讯的案例课程主要是先讲socket基础 -> 环信 ->自定义协议希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解同时能希望大家在面试时,在即时通讯这块不在陌生
  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏






支付宝打赏






谢谢大家的支持,个人微信号清扫描下面张图






 
郭永峰IT交流QQ群请加:596441895 查看全部
我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。

    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
  1. 4月12号成立工作室,现在18号,过了一个星期
  2. 一个星期录了5天的环信教程视频,我将放在网盘免费分享
  3. 环信的教程视频主要是针对有开发经验者
  4. 教程主要是使用环信来模仿微信来做一个即时通讯的案例
  5. 课程主要是先讲socket基础 -> 环信 ->自定义协议
  6. 希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解
  7. 同时能希望大家在面试时,在即时通讯这块不在陌生

  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏

微信.png


支付宝打赏

支付宝.png


谢谢大家的支持,个人微信号清扫描下面张图

个人.png


 
郭永峰IT交流QQ群请加:596441895
1
评论

【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了 环信公开课 昵称头像 环信大表哥

beyond 发表了文章 • 742 次浏览 • 2017-05-08 11:55 • 来自相关话题

 ​ 青年的思想愈被榜样的力量所激动,就愈会发出强烈的光辉-法捷耶夫
  在刚刚过去的五四青年节,环信公开课第12期如期举行,这期公开课嘉宾“环信大表哥”一人手写三端,Android/IOS/服务端信手捏来,关于用户体系更是引经据典一番,整场公开课妙语连珠、妙趣横生。加上环信MM的现场答疑,这样一来便消除了拘谨,活跃了气氛,环信小伙伴们在欢快愉悦的气氛中度过了这个有意义的五四青年节! 

环信公开课12期讲了什么?
如何利用消息扩展属性显示昵称头像?如何通过APP服务器处理昵称头像的显示?昵称头像的本地缓存策略?音视频通话如何显示昵称头像?

关于环信大表哥:
   马骏斌丨美国海遇网络CTO,传说中的祖传CTO“环信大表哥”,10年研发经验,现任美国海遇网络CTO。曾就职于北京超图从事GIS研发,负责基于SuperMap平台研究GIS算法以及图形处理工具,优化底层三维渲染引擎,协助产品研发进行数据处理或空间分析工作。

环信简版demo作者,开源了基于环信的直播项目-小马直播间,在imgeek、简书等社区发表了数十篇环信教程,他的教程和开源项目累计帮助了超过1W多名开发者集成环信,自己创建了环信互帮互助群,每天"夜黑风高"活跃在群里回答问题+远程写代码,人送外号“环信大表哥”。
 
自2011年2月创办www.C3DN.net(中国3D技术开发者社区),并兼任C3DN站长。创办C3DN,不仅为了延续对3D技术的热爱,最主要是想帮助更多需要帮助的人学习3D开发技术;

先放一张大表哥PPT截图,这个style!你们感(la)受(yan)下(jing)。





环信公开课视频回放:视频观看地址
  
环信公开课第12期讲师PPT在文末下载,最后引用一句某知名互联网公司创始人名言致大表哥“百年之后,你我的肉身终将陨灭,而我们的精神和梦想依然可以在代码中相见”
ios简版demo地址
Android简版demo地址 查看全部
 
​ 青年的思想愈被榜样的力量所激动,就愈会发出强烈的光辉-法捷耶夫

  在刚刚过去的五四青年节,环信公开课第12期如期举行,这期公开课嘉宾“环信大表哥”一人手写三端,Android/IOS/服务端信手捏来,关于用户体系更是引经据典一番,整场公开课妙语连珠、妙趣横生。加上环信MM的现场答疑,这样一来便消除了拘谨,活跃了气氛,环信小伙伴们在欢快愉悦的气氛中度过了这个有意义的五四青年节! 

环信公开课12期讲了什么?
  1. 如何利用消息扩展属性显示昵称头像?
  2. 如何通过APP服务器处理昵称头像的显示?
  3. 昵称头像的本地缓存策略?
  4. 音视频通话如何显示昵称头像?


关于环信大表哥:

   马骏斌丨美国海遇网络CTO,传说中的祖传CTO“环信大表哥”,10年研发经验,现任美国海遇网络CTO。曾就职于北京超图从事GIS研发,负责基于SuperMap平台研究GIS算法以及图形处理工具,优化底层三维渲染引擎,协助产品研发进行数据处理或空间分析工作。

环信简版demo作者,开源了基于环信的直播项目-小马直播间,在imgeek、简书等社区发表了数十篇环信教程,他的教程和开源项目累计帮助了超过1W多名开发者集成环信,自己创建了环信互帮互助群,每天"夜黑风高"活跃在群里回答问题+远程写代码,人送外号“环信大表哥”。
 
自2011年2月创办www.C3DN.net(中国3D技术开发者社区),并兼任C3DN站长。创办C3DN,不仅为了延续对3D技术的热爱,最主要是想帮助更多需要帮助的人学习3D开发技术;



先放一张大表哥PPT截图,这个style!你们感(la)受(yan)下(jing)。
QQ截图20170508115114.jpg


环信公开课视频回放:视频观看地址
  
环信公开课第12期讲师PPT在文末下载,最后引用一句某知名互联网公司创始人名言致大表哥“百年之后,你我的肉身终将陨灭,而我们的精神和梦想依然可以在代码中相见
ios简版demo地址
Android简版demo地址
1
回复

在pct中导入EMSDKFull.h时报错 iOS 环信

回复

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

2
回复

iOS 环信2.0 发送消息重复。 iOS

回复

刘小刘 回复了问题 • 3 人关注 • 876 次浏览 • 2017-05-22 20:01 • 来自相关话题

1
回复

为什么环信的demo里边群主可以屏蔽群消息? iOS

回复

木云落 回复了问题 • 2 人关注 • 39 次浏览 • 2017-05-22 17:14 • 来自相关话题

8
回复

各位大神,有单聊集成音视频的demo吗?iOS3.0的? 音视频 iOS 环信_iOS

回复

SN 回复了问题 • 8 人关注 • 979 次浏览 • 2017-05-12 14:29 • 来自相关话题

4
回复

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

回复

yúañ來諟ィ厼ˇ¶﹏,g 回复了问题 • 2 人关注 • 115 次浏览 • 2017-05-04 09:39 • 来自相关话题

2
最佳

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

回复

asd777 回复了问题 • 2 人关注 • 124 次浏览 • 2017-04-20 15:33 • 来自相关话题

1
回复

跟着视频一起写的demo 环信 iOS

回复

xxl 回复了问题 • 2 人关注 • 191 次浏览 • 2017-04-12 15:59 • 来自相关话题

3
回复

手动集成EaseUI,Tabbar变黑色 iOS

回复

lizilong 回复了问题 • 3 人关注 • 125 次浏览 • 2017-04-12 15:37 • 来自相关话题

1
回复

环信视频通话,A用户和B用户怎么才能一连接就用扬声器视频对话呢? iOS 环信 音视频

回复

lizilong 回复了问题 • 2 人关注 • 726 次浏览 • 2017-04-11 15:01 • 来自相关话题

1
回复

有没有人做过环信图片连续滑动?能稍微解释一下基本步骤吗? 环信_iOS iOS

回复

donghai 回复了问题 • 2 人关注 • 119 次浏览 • 2017-04-10 17:27 • 来自相关话题

1
最佳

ios 环信最新版本 打包上线时候报错 审核bug 集成上线 iOS

回复

donghai 回复了问题 • 2 人关注 • 250 次浏览 • 2017-03-31 18:10 • 来自相关话题

4
回复

怎么修改EaseChatBarMoreView,自定义高度,自定义图标 iOS

回复

zjjzmw1 回复了问题 • 3 人关注 • 1097 次浏览 • 2017-03-22 17:09 • 来自相关话题

5
回复

ios 环信创建会话失败 iOS 环信

回复

zl 回复了问题 • 3 人关注 • 245 次浏览 • 2017-03-20 09:54 • 来自相关话题

1
回复

看视频如果出现问题可以看这里 dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate iOS 环信

回复

donghai 回复了问题 • 2 人关注 • 737 次浏览 • 2017-03-15 12:33 • 来自相关话题

2
最佳

环信服务端集成,如何集成? iOS Android webim

回复

执念 回复了问题 • 3 人关注 • 1269 次浏览 • 2017-03-13 11:36 • 来自相关话题

2
最佳

我在环信登录成功的回调中插入了一条消息,但是数据库中并没有这一条消息请问是为什么? iOS

回复

zl 回复了问题 • 2 人关注 • 192 次浏览 • 2017-03-03 19:31 • 来自相关话题

8
回复

fudh环信ios工程师 能不能把你集成音视频的demo发给我1217212724 或者给一个连接 iOS 环信 音视频

回复

笑笑骗你一世~ 回复了问题 • 9 人关注 • 1308 次浏览 • 2017-02-24 13:51 • 来自相关话题

2
回复

从环信数据库获取消息时,能将ext扩展里的信息作为查询条件吗? 环信_iOS iOS

回复

zhangyb 回复了问题 • 2 人关注 • 186 次浏览 • 2017-02-17 19:28 • 来自相关话题

1
最佳

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

回复

环信沈冲 回复了问题 • 2 人关注 • 234 次浏览 • 2017-02-15 12:14 • 来自相关话题

2
回复

IOS V3.0的键盘有时高度异常 键盘 iOS

回复

xxl 回复了问题 • 2 人关注 • 184 次浏览 • 2017-02-14 18:01 • 来自相关话题

1
回复

V3.2.3的SDK版本,没有找到即将重连、重连成功的方法 iOS 环信_iOS 重连

回复

donghai 回复了问题 • 2 人关注 • 333 次浏览 • 2017-01-22 14:22 • 来自相关话题

6
最佳

关于ios登陆的问题 登陆 iOS 环信客服 环信_iOS

回复

Junetou 回复了问题 • 3 人关注 • 403 次浏览 • 2017-01-21 21:51 • 来自相关话题

1
回复

关于ios登陆不成功的问题 登陆失败 iOS

回复

zhuhy 回复了问题 • 2 人关注 • 348 次浏览 • 2017-01-20 17:55 • 来自相关话题

2
回复

iOS 3.x 下载缩略图失败是为什么?errorCode = 403 缩略图 iOS 环信_iOS

回复

xxl 回复了问题 • 2 人关注 • 367 次浏览 • 2017-01-19 11:15 • 来自相关话题

1
最佳

iOS动态库sdk上架问题? 动态库 iOS

回复

beyond 回复了问题 • 2 人关注 • 505 次浏览 • 2017-01-10 17:32 • 来自相关话题

1
评论

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

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

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天
 
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
2
评论

【视频教程+源码】基于环信IM做一个仿微信APP-更新ing 郭永峰 高仿微信 仿微信 环信 XMPP

郭永峰 发表了文章 • 1110 次浏览 • 2017-05-16 15:29 • 来自相关话题

我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。
    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
4月12号成立工作室,现在18号,过了一个星期一个星期录了5天的环信教程视频,我将放在网盘免费分享环信的教程视频主要是针对有开发经验者教程主要是使用环信来模仿微信来做一个即时通讯的案例课程主要是先讲socket基础 -> 环信 ->自定义协议希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解同时能希望大家在面试时,在即时通讯这块不在陌生
  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏






支付宝打赏






谢谢大家的支持,个人微信号清扫描下面张图






 
郭永峰IT交流QQ群请加:596441895 查看全部
我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。

    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
  1. 4月12号成立工作室,现在18号,过了一个星期
  2. 一个星期录了5天的环信教程视频,我将放在网盘免费分享
  3. 环信的教程视频主要是针对有开发经验者
  4. 教程主要是使用环信来模仿微信来做一个即时通讯的案例
  5. 课程主要是先讲socket基础 -> 环信 ->自定义协议
  6. 希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解
  7. 同时能希望大家在面试时,在即时通讯这块不在陌生

  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏

微信.png


支付宝打赏

支付宝.png


谢谢大家的支持,个人微信号清扫描下面张图

个人.png


 
郭永峰IT交流QQ群请加:596441895
1
评论

【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了 环信公开课 昵称头像 环信大表哥

beyond 发表了文章 • 742 次浏览 • 2017-05-08 11:55 • 来自相关话题

 ​ 青年的思想愈被榜样的力量所激动,就愈会发出强烈的光辉-法捷耶夫
  在刚刚过去的五四青年节,环信公开课第12期如期举行,这期公开课嘉宾“环信大表哥”一人手写三端,Android/IOS/服务端信手捏来,关于用户体系更是引经据典一番,整场公开课妙语连珠、妙趣横生。加上环信MM的现场答疑,这样一来便消除了拘谨,活跃了气氛,环信小伙伴们在欢快愉悦的气氛中度过了这个有意义的五四青年节! 

环信公开课12期讲了什么?
如何利用消息扩展属性显示昵称头像?如何通过APP服务器处理昵称头像的显示?昵称头像的本地缓存策略?音视频通话如何显示昵称头像?

关于环信大表哥:
   马骏斌丨美国海遇网络CTO,传说中的祖传CTO“环信大表哥”,10年研发经验,现任美国海遇网络CTO。曾就职于北京超图从事GIS研发,负责基于SuperMap平台研究GIS算法以及图形处理工具,优化底层三维渲染引擎,协助产品研发进行数据处理或空间分析工作。

环信简版demo作者,开源了基于环信的直播项目-小马直播间,在imgeek、简书等社区发表了数十篇环信教程,他的教程和开源项目累计帮助了超过1W多名开发者集成环信,自己创建了环信互帮互助群,每天"夜黑风高"活跃在群里回答问题+远程写代码,人送外号“环信大表哥”。
 
自2011年2月创办www.C3DN.net(中国3D技术开发者社区),并兼任C3DN站长。创办C3DN,不仅为了延续对3D技术的热爱,最主要是想帮助更多需要帮助的人学习3D开发技术;

先放一张大表哥PPT截图,这个style!你们感(la)受(yan)下(jing)。





环信公开课视频回放:视频观看地址
  
环信公开课第12期讲师PPT在文末下载,最后引用一句某知名互联网公司创始人名言致大表哥“百年之后,你我的肉身终将陨灭,而我们的精神和梦想依然可以在代码中相见”
ios简版demo地址
Android简版demo地址 查看全部
 
​ 青年的思想愈被榜样的力量所激动,就愈会发出强烈的光辉-法捷耶夫

  在刚刚过去的五四青年节,环信公开课第12期如期举行,这期公开课嘉宾“环信大表哥”一人手写三端,Android/IOS/服务端信手捏来,关于用户体系更是引经据典一番,整场公开课妙语连珠、妙趣横生。加上环信MM的现场答疑,这样一来便消除了拘谨,活跃了气氛,环信小伙伴们在欢快愉悦的气氛中度过了这个有意义的五四青年节! 

环信公开课12期讲了什么?
  1. 如何利用消息扩展属性显示昵称头像?
  2. 如何通过APP服务器处理昵称头像的显示?
  3. 昵称头像的本地缓存策略?
  4. 音视频通话如何显示昵称头像?


关于环信大表哥:

   马骏斌丨美国海遇网络CTO,传说中的祖传CTO“环信大表哥”,10年研发经验,现任美国海遇网络CTO。曾就职于北京超图从事GIS研发,负责基于SuperMap平台研究GIS算法以及图形处理工具,优化底层三维渲染引擎,协助产品研发进行数据处理或空间分析工作。

环信简版demo作者,开源了基于环信的直播项目-小马直播间,在imgeek、简书等社区发表了数十篇环信教程,他的教程和开源项目累计帮助了超过1W多名开发者集成环信,自己创建了环信互帮互助群,每天"夜黑风高"活跃在群里回答问题+远程写代码,人送外号“环信大表哥”。
 
自2011年2月创办www.C3DN.net(中国3D技术开发者社区),并兼任C3DN站长。创办C3DN,不仅为了延续对3D技术的热爱,最主要是想帮助更多需要帮助的人学习3D开发技术;



先放一张大表哥PPT截图,这个style!你们感(la)受(yan)下(jing)。
QQ截图20170508115114.jpg


环信公开课视频回放:视频观看地址
  
环信公开课第12期讲师PPT在文末下载,最后引用一句某知名互联网公司创始人名言致大表哥“百年之后,你我的肉身终将陨灭,而我们的精神和梦想依然可以在代码中相见
ios简版demo地址
Android简版demo地址
2
评论

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

木云落 发表了文章 • 140 次浏览 • 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 版本或者不想导入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 版本或者不想导入Lite版的 , 只想引入EaseUI 
这时需要把 #import <Hyphenate/Hyphenate.h>注释掉,然后把报错地方的Hyphenate换成HyphenateLite就可以了
 
 
11. 
1.png

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

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

奋斗的蜗牛 发表了文章 • 78 次浏览 • 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 热更新

新闻资讯 发表了文章 • 282 次浏览 • 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 产品更新

产品更新 发表了文章 • 426 次浏览 • 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

不死小强 发表了文章 • 531 次浏览 • 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

产品更新 发表了文章 • 454 次浏览 • 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 发表了文章 • 452 次浏览 • 2017-02-09 15:27 • 来自相关话题

先上效果图:










 
大家直接看下面demo就可以,搜索#pragma mark smallpngface就是所有修改的地方。
图片资源可以自己替换自己的,大家可以照猫画虎,我只是抛砖引玉。     

https://pan.baidu.com/s/1geEZgB5 

  查看全部
先上效果图:

6069EDD5-304A-41BD-AE22-443F1604185A.png


568D243B-599A-43F3-A08D-E2B7C0CAC6E5.png

 
大家直接看下面demo就可以,搜索#pragma mark smallpngface就是所有修改的地方。
图片资源可以自己替换自己的,大家可以照猫画虎,我只是抛砖引玉。     

https://pan.baidu.com/s/1geEZgB5 

 
2
评论

环信头像和昵称显示的详细、详细、详细教程! 头像 昵称 扩展 头像和昵称的显示 iOS 昵称 头像

xuke007008 发表了文章 • 1235 次浏览 • 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 发表了文章 • 363 次浏览 • 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 发表了文章 • 642 次浏览 • 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

产品更新 发表了文章 • 525 次浏览 • 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 产品快递

产品更新 发表了文章 • 225 次浏览 • 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 发表了文章 • 570 次浏览 • 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 产品快递

产品更新 发表了文章 • 463 次浏览 • 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 产品快递

产品更新 发表了文章 • 516 次浏览 • 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 产品快递

产品更新 发表了文章 • 374 次浏览 • 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

奋斗的蜗牛 发表了文章 • 266 次浏览 • 2016-10-28 17:52 • 来自相关话题

下面我将上传一个微信demo试试看了下效果,只需要往工程里面添加创建的视图控制器,就可以快速找到当前页面的viewcontroler




 
下面我将上传一个微信demo试试看了下效果,只需要往工程里面添加创建的视图控制器,就可以快速找到当前页面的viewcontroler
屏幕快照_2016-10-28_下午5.47_.49_.png

 
1
评论

【环信集成笔记】入门篇-分享一些ios集成小技巧 环信集成笔记 iOS 环信_iOS

beyond 发表了文章 • 2117 次浏览 • 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 发表了文章 • 1303 次浏览 • 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 发表了文章 • 1753 次浏览 • 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

文章作者:环信热心用户樊呵呵
0
评论

【推荐】两大APP与云账户红包SDK集成详情及Demon分享 红包SDK iOS 云账户 Android

云账户 发表了文章 • 568 次浏览 • 2016-09-07 10:54 • 来自相关话题

云账户红包SDK3.0已经发布一段时间了!
就在本月,两家老牌重量级APP完成了云账户红包SDK的集成,经过双方严格的联合测试,现在正是发版了!他们就是“工作圈”&“超信”(IOS、安卓市场都可以下载哦,有兴趣的朋友们可以去市场里下载体验一下,绝对nice!)
可能有朋友不太了解这两个百万级用户的产品,这里我给大家正是介绍一下:
工作圈:
g工作圈是用友旗下畅捷通公司开发的一款企业移动办公平台,融合了企业经营管理所需的各项专业服务,是基于工作场景应用集成:公告、审批、任务、工作报告、签到、文件柜、电话会议、企业通讯录、圈子信息交流等组成的企业互联网应用,帮助企业提高沟通协作效率、简化工作流程、降低管理成本。让管理更简单,工作更轻松!
 
超信:
超信是一款基于手机通讯录的短信增强工具,安装超信既可以完美的取代系统短信功能,开机即用,更加简单便捷的与手机通讯录中的联系人互相手法短信。如果双方都安装超信,就能通过手机网络(WiFi、3G、GPRS)与您铜须路中的联系人发送(需小豪少量网络流量)信息、图片、语音和位置等多媒体文件。
以下是云账户在两个产品中的截图:
工作圈&云账户:
超信&云账户:
除了工作圈和超信之外,已经成功集成云账户红包SDK并发版上线的比较有代表性的APP有:拉拉公园、INVITE、全景图片、蜗牛睡眠、捷库工作、领公报等,正在集成中的APP有上百家,其中还有几位神秘大咖在享受我们的云服务和私有化部署服务,这里暂时先保持下神秘!
我们的原则是,先上线、再PR,少吹牛,多干实事!
另外,云账户红包SDK已经满足完美集成在市场上主流的IM,客服SaaS提供商环信、容联云通讯、融云、leancloud、亲加的产品中,开发者如果用了以上IM或客服产品,可以更加快速安全的接入!
云账户红包SDK,更详细的信息各位开发者可以访问云账户官网:www.yunzhanghu.com去了解,官网上提供Demo下载,帮助开发者们更好地体验我们的产品,毕竟“先尝后买,价格公道,童叟无欺” 查看全部
云账户红包SDK3.0已经发布一段时间了!
就在本月,两家老牌重量级APP完成了云账户红包SDK的集成,经过双方严格的联合测试,现在正是发版了!他们就是“工作圈”&“超信”(IOS、安卓市场都可以下载哦,有兴趣的朋友们可以去市场里下载体验一下,绝对nice!)
可能有朋友不太了解这两个百万级用户的产品,这里我给大家正是介绍一下:
工作圈:
g工作圈是用友旗下畅捷通公司开发的一款企业移动办公平台,融合了企业经营管理所需的各项专业服务,是基于工作场景应用集成:公告、审批、任务、工作报告、签到、文件柜、电话会议、企业通讯录、圈子信息交流等组成的企业互联网应用,帮助企业提高沟通协作效率、简化工作流程、降低管理成本。让管理更简单,工作更轻松!
 
超信:
超信是一款基于手机通讯录的短信增强工具,安装超信既可以完美的取代系统短信功能,开机即用,更加简单便捷的与手机通讯录中的联系人互相手法短信。如果双方都安装超信,就能通过手机网络(WiFi、3G、GPRS)与您铜须路中的联系人发送(需小豪少量网络流量)信息、图片、语音和位置等多媒体文件。
以下是云账户在两个产品中的截图:
工作圈&云账户:
超信&云账户:
除了工作圈和超信之外,已经成功集成云账户红包SDK并发版上线的比较有代表性的APP有:拉拉公园、INVITE、全景图片、蜗牛睡眠、捷库工作、领公报等,正在集成中的APP有上百家,其中还有几位神秘大咖在享受我们的云服务和私有化部署服务,这里暂时先保持下神秘!
我们的原则是,先上线、再PR,少吹牛,多干实事!
另外,云账户红包SDK已经满足完美集成在市场上主流的IM,客服SaaS提供商环信、容联云通讯、融云、leancloud、亲加的产品中,开发者如果用了以上IM或客服产品,可以更加快速安全的接入!
云账户红包SDK,更详细的信息各位开发者可以访问云账户官网:www.yunzhanghu.com去了解,官网上提供Demo下载,帮助开发者们更好地体验我们的产品,毕竟“先尝后买,价格公道,童叟无欺”
1
评论

Duplicate interface definition for class 'EaseUI'解决办法 iOS EaseUI swift

※樂※ 发表了文章 • 679 次浏览 • 2016-09-05 12:44 • 来自相关话题

今天项目迁移时发现报 Duplicate interface definition for class 'EaseUI' 错误,然后重新修改了EaseUI-Prefix的指定路径就好了!
今天项目迁移时发现报 Duplicate interface definition for class 'EaseUI' 错误,然后重新修改了EaseUI-Prefix的指定路径就好了!
1
评论

报错: _OBJC_CLASS_$_CMMotionManager 和 报错:ChatDemoHelper中TTAlertNoTitle is invalid in C99解决方法 ChatDemoHelper 环信 iOS集成 iOS

Shawnre6 发表了文章 • 944 次浏览 • 2016-09-03 04:48 • 来自相关话题

(1)报错  _OBJC_CLASS_$_CMMotionManager  :




解决方法:直接去Linked Frameworks and Libraries 导入 CoreMotion.framework文件即可:





  
 
(2)报错:ChatDemoHelper.m文件中 TTAlertNoTitle is invalid in C99
解决方法:直接在ChatDemoHelper.m文件中导入两个文件即可:
 
#include <sys/types.h>
#include <sys/sysctl.h>
 
 
真的是很蛋疼的两个报错。。。 查看全部
(1)报错  _OBJC_CLASS_$_CMMotionManager  :
1.pic_.jpg

解决方法:直接去Linked Frameworks and Libraries 导入 CoreMotion.framework文件即可:

2.pic_.jpg

  
 
(2)报错:ChatDemoHelper.m文件中 TTAlertNoTitle is invalid in C99
解决方法:直接在ChatDemoHelper.m文件中导入两个文件即可:
 
#include <sys/types.h>
#include <sys/sysctl.h>
 
 
真的是很蛋疼的两个报错。。。
5
评论

基于环信SDK开发,开源的高仿ios微信项目--酷信 iOS 微信 iOS环信SDK高仿微信MVVM架构求STAR

nacker 发表了文章 • 27432 次浏览 • 2016-07-20 16:27 • 来自相关话题

写在前面的话(大伙在使用过程中遇到BUG请@我一下,我会去认真迭代这款高仿APP)
 项目简介:
 1.此版本是有史以来Github上最牛逼的高仿微信项目没有之一,采用MVVM和MVC两种开发架构思想,纯代码开发,这是你们在培训机构学不到的.仅供大家学习使用,不得用于商业用途.最终解释权归作者二哥所有. 2.如果各位下客能帮我点STAR,半个月STAR500+,我会陆陆续续发布待实现功能,其实已经做完,一个月STAR1000+我会把微信主要功能全部实现发布出来,两个月STAR2000+我会发布纯Swift版,纯Swift版采用纯代码开发已经做的差不多了.就看大伙的手能不能点STAR了.希望大家不要下完就跑了.作为作者的二哥会很心痛的.
3.我之前接触过很多项目,就有一个项目中的朋友圈整个控制器4千行,尼玛4千行了这项目怎么迭代,二哥现在300行解决了朋友圈的问题,还在优化中...

高仿微信计划:测试账号:

nacker 123456
h18 123456
 1.采用技术点

* pod用于第三方库的管理

* 环信SDK(V3.1.4 2016-07-08)作为此APP DEMO的IM功能

* Masonry用于界面布局

* FMDB用于数据存储

* MJRefresh用于上拉下拉刷新

* MJExtension用于字典转模型

* SDWebImage用于图片展示下载

* ReactiveCocoa用作响应式编程提高代码可读性

* MLLabel用于Label的图文混排

* IM部分采用MVC架构,朋友圈采用MVVM架构

2.已经实现功能

* 微信首页(列表数据展示、cell侧滑编辑、点击进入聊天详情界面、发送文字图片和语音)

* 通讯录(联系人字母排序、添加联系人)

* 发现(朋友圈、下拉刷新)

* 我(界面、退出功能)

3.待实现功能

* 搜索好友

* 朋友圈细节完善

* 扫一扫

* 相册、钱包

* 其他细节实现

* 摇一摇

* 发送朋友圈信息

* 其他

部分截图





整体架构图






聊天界面

 





通讯录

 





发现






登录

 
感恩感谢那些开源作者们,我在这里就不一一点名感谢了.有了你们在整个项目的开发进度上提升了不少.也让我学会了很多




关于我
QQ群  : 527885963  






期待

* 如果在使用过程中遇到BUG,希望你能Issues我,谢谢(或者尝试下载最新的框架代码看看BUG修复没有)
* 如果在使用过程中发现功能不够用,希望你能Issues我,我非常想为这个框架增加更多好用的功能,谢谢
* 如果你想为LZEasemob3输出代码,请拼命Pull Requests我
 
项目git源码地址 喜欢请star:https://github.com/nacker/LZEasemob3
  查看全部
写在前面的话
(大伙在使用过程中遇到BUG请@我一下,我会去认真迭代这款高仿APP)

 项目简介:
  •  1.此版本是有史以来Github上最牛逼的高仿微信项目没有之一,采用MVVM和MVC两种开发架构思想,纯代码开发,这是你们在培训机构学不到的.仅供大家学习使用,不得用于商业用途.最终解释权归作者二哥所有.
  •  2.如果各位下客能帮我点STAR,半个月STAR500+,我会陆陆续续发布待实现功能,其实已经做完,一个月STAR1000+我会把微信主要功能全部实现发布出来,两个月STAR2000+我会发布纯Swift版,纯Swift版采用纯代码开发已经做的差不多了.就看大伙的手能不能点STAR了.希望大家不要下完就跑了.作为作者的二哥会很心痛的.

  • 3.我之前接触过很多项目,就有一个项目中的朋友圈整个控制器4千行,尼玛4千行了这项目怎么迭代,二哥现在300行解决了朋友圈的问题,还在优化中...


高仿微信计划:
测试账号:

nacker 123456
h18 123456

 1.采用技术点

* pod用于第三方库的管理

* 环信SDK(V3.1.4 2016-07-08)作为此APP DEMO的IM功能

* Masonry用于界面布局

* FMDB用于数据存储

* MJRefresh用于上拉下拉刷新

* MJExtension用于字典转模型

* SDWebImage用于图片展示下载

* ReactiveCocoa用作响应式编程提高代码可读性

* MLLabel用于Label的图文混排

* IM部分采用MVC架构,朋友圈采用MVVM架构

2.已经实现功能

* 微信首页(列表数据展示、cell侧滑编辑、点击进入聊天详情界面、发送文字图片和语音)

* 通讯录(联系人字母排序、添加联系人)

* 发现(朋友圈、下拉刷新)

* 我(界面、退出功能)

3.待实现功能

* 搜索好友

* 朋友圈细节完善

* 扫一扫

* 相册、钱包

* 其他细节实现

* 摇一摇

* 发送朋友圈信息

* 其他

部分截图


LZEasemob3.png

整体架构图



LZ1.gif

聊天界面


 


LZ2.gif

通讯录


 


LZ3.gif

发现



LZ4.gif

登录


 
感恩
感谢那些开源作者们,我在这里就不一一点名感谢了.有了你们在整个项目的开发进度上提升了不少.也让我学会了很多




关于我
  • QQ群  : 527885963  


me.png


期待

* 如果在使用过程中遇到BUG,希望你能Issues我,谢谢(或者尝试下载最新的框架代码看看BUG修复没有)
* 如果在使用过程中发现功能不够用,希望你能Issues我,我非常想为这个框架增加更多好用的功能,谢谢
* 如果你想为LZEasemob3输出代码,请拼命Pull Requests我
 
项目git源码地址 喜欢请star:https://github.com/nacker/LZEasemob3
 
4
评论

分享 iOS 使用消息扩展集成头像和昵称的方法 iOS 头像 昵称 扩展

ghysrc 发表了文章 • 2905 次浏览 • 2016-07-13 17:51 • 来自相关话题

虽然文档里有提到,但没说具体怎么实现,demo 看着很绕也不知道有没有...社区里关于这个提问也很多,回答也都不明确,刚上手完全是各种不明白。所以分享一个简单粗暴的办法...
1、找到 EaseUI 里的 EaseMessageViewController.m
2、给所有的 sendXXXMessage 里添加消息扩展,如下图:

图中是图片消息和语音消息的示例,其他消息都是一样的。
3、在 modelForMessage 里将消息的扩展取出,给 model 的 avatarURLPath 和 nickname 赋值即可。

  查看全部
虽然文档里有提到,但没说具体怎么实现,demo 看着很绕也不知道有没有...社区里关于这个提问也很多,回答也都不明确,刚上手完全是各种不明白。所以分享一个简单粗暴的办法...
1、找到 EaseUI 里的 EaseMessageViewController.m
2、给所有的 sendXXXMessage 里添加消息扩展,如下图:

图中是图片消息和语音消息的示例,其他消息都是一样的。
3、在 modelForMessage 里将消息的扩展取出,给 model 的 avatarURLPath 和 nickname 赋值即可。