iOS 融云集成记录(不含离线推送版)

最近学习iOS用到了IM 集成了融云,跳了很多坑 记录下
PS: 代码不一定要集成一次写一次,应该一个功能尽可能的写一次,其他的地方都是尽可能的copy

  1. 集成
    pod 'RongCloudIM/IMKit', '~> 2.9.17'

2.管理及监听类(本版本暂未集成离线推送,后期迭代添加)

//
//  RongHelp.swift
//
//  Created by mac  on 2019/9/20.
//  Copyright © 2019 mac . All rights reserved.
//

import Foundation

class RongHelp: NSObject {
  
  static let shared = RongHelp()
  private var userHandler = UserHandler()

  func initRong() {
      RCIM.shared().initWithAppKey(KRongKey)
      RCIM.shared().enablePersistentUserInfoCache = false
      /// 是否在发送的所有消息中携带当前登录的用户信息 设置为false 否则Android和ios交互可能会问题  经测试消息显示用户名会偶尔显示未用户id
      RCIM.shared().enableMessageAttachUserInfo = false
      RCIM.shared().receiveMessageDelegate = self
      RCIM.shared().connectionStatusDelegate =  self
      RCIM.shared().userInfoDataSource = self
  }

  /// 注册用户
  func registerRongYun() {
      RCIM.shared().connect(
              withToken: UserToken.shared.imToken,
              success: { cuccessString in
              },
              error: { (errorCode) in
              },
              tokenIncorrect: { [weak self] in
                  self?.requestRongToken()
              })
  }
  
  /// 刷新用户信息
  func refreshUserInfoCache(userInfo: RCUserInfo? = nil) {

      if userInfo != nil {
          RCIM.shared().refreshUserInfoCache(userInfo, withUserId: userInfo!.userId)
          return
      }

      if let bean = UserToken.shared.userBean {
          RCIM.shared().refreshUserInfoCache(
                  RCUserInfo(
                          userId: "\(bean.userBean.id)",
                          name: bean.profileBean.nickName,
                          portrait: TSRouter.fileCheckUrl + bean.profileBean.avatar
                  ),
                  withUserId: "\(bean.userBean.id)"
          )
      }
  }
     
}


// MARK: 登录状态监听
extension RongHelp: RCIMConnectionStatusDelegate{
  func onRCIMConnectionStatusChanged(_ status: RCConnectionStatus) {
      /// 挤下线
      if status == .ConnectionStatus_KICKED_OFFLINE_BY_OTHER_CLIENT {
          MBProgressHUD.show(info: "你的账号在其他地方登录,请重新登录")
         // TODO 挤下线代码逻辑            
      }
  }
}

// 融云代理
extension RongHelp: RCIMUserInfoDataSource, RCIMReceiveMessageDelegate{

  /// 获取消息未读数
  func unReadCount() -> Int {
      return Int(RCIMClient.shared().getTotalUnreadCount())
  }
  
  /// 消息个数
  func onRCIMReceive(_ message: RCMessage!, left: Int32) {
  
     /// 如下的代码 是一个融云头像问题的解决思路,稍后会说到,如果有更好的办法 这个方法可以省略
      NotificationCenter.default.post(name: UserNotification.rongImMessageCount.notification, object: self)
      if message.conversationType == RCConversationType.ConversationType_PRIVATE {
          do{
              if let textMessage = message.content as? RCTextMessage, let extra = textMessage.extra {
                  let json = JSON(parseJSON: extra)
                  let userInfo = RCUserInfo(userId: json["id"].stringValue, name: json["name"].stringValue, portrait: json["avatar"].stringValue)
                  refreshUserInfoCache(userInfo: userInfo)
              }
          }catch{
              print("-------rong 解析异常")
          }
      }
  }

  // 内容提供者
  func getUserInfo(withUserId userId: String!, completion: ((RCUserInfo?) -> Void)!) {
      guard  let rongUserId = userId else {
          return
      }
      // 调用自己接口查询userInfo
      requestRongUserInfo(userId: rongUserId)
  }
}

// 网络请求部分
extension RongHelp {
  // 查询用户信息
  func requestRongUserInfo(userId: String) {
      userHandler.requestRongUserInfo(userID: userId, success: { userInfo in
          RCIM.shared().refreshUserInfoCache(userInfo
                  , withUserId: userId)
      }) { (_) in
      }
  }

  //获取登录ImToken 总共重试5次 每次间隔1S
  func requestRongToken() {
      var retryCount = 0
      func requestToken() {
          userHandler.requestRongToken(
                  success: { (token) in
                      retryCount = 0
                      UserToken.shared.imToken = token
                  }) { (error) in
              retryCount += 1
              if retryCount < 5 {
                  requestToken()
              }

          }
      }
      requestToken()
  }

}

  1. 使用
    本app为强登录类型app, 具体注册地方看项目需求
    AppDelegate 中 RongHelp.shared.initRong()
    账号登录后 重新注册融云 防止被挤下线 账号登录后融云未登录
  /// 获取用户信息
  func requestUserInfo() {
      UserHandler().requestUserInfo(
              isThread: false,
              success: {
                  [weak self] _ in
                 RongHelp.shared.registerRongYun()
                 /// more 其他的业务逻辑
              }) {  (error) in
                /// more
     }
  }

  /// 注册用户
  func registerRongYun() {
      RCIM.shared().connect(
              withToken: UserToken.shared.imToken,
              success: { cuccessString in
              },
              error: { (errorCode) in
              },
              tokenIncorrect: { [weak self] in
                  self?.requestRongToken()

              })
  }

4,自定义聊天列表

class HomeRongMessageController: RCConversationListViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "消息"
        
        // 设置头像大小
        setConversationPortraitSize(CGSize(width: 60, height: 60))
        // 头像形状
        setConversationAvatarStyle(RCUserAvatarStyle.USER_AVATAR_CYCLE)
       //显示的会话类型 本页面为系统消息和单聊
        setDisplayConversationTypes([
                                     RCConversationType.ConversationType_PRIVATE.rawValue,
                                     RCConversationType.ConversationType_SYSTEM.rawValue]
        )
        conversationListTableView.separatorStyle = UITableViewCell.SeparatorStyle.none
        conversationListTableView.register(R.nib.homeRongMessageCell)

        // 自定义空数据占位图
        // emptyConversationView = emptyView
    }
}

extension HomeRongMessageController {

    // 加数据标签 加了之后 才会调用下面的rcConversationListTableView代理方法
    override func willReloadTableData(_ dataSource: NSMutableArray!) -> NSMutableArray! {

        dataSource.forEach {
            let model: RCConversationModel = $0 as! RCConversationModel
            model.conversationModelType = RCConversationModelType.CONVERSATION_MODEL_TYPE_CUSTOMIZATION
        }
        return dataSource
    }

    override func rcConversationListTableView(_ tableView: UITableView!, heightForRowAt indexPath: IndexPath!) -> CGFloat {
        return 90
    }

    // 自定义显示样式
    override func rcConversationListTableView(_ tableView: UITableView!, cellForRowAt indexPath: IndexPath!) -> RCConversationBaseCell! {
        let cell = conversationListTableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.homeRongMessageCell, for: indexPath)!
        cell.delegate = self
        cell.config(model: conversationListDataSource[indexPath.row] as! RCConversationModel)
        return cell
    }

    // 点击事件
    override func onSelectedTableRow(_ conversationModelType: RCConversationModelType, conversationModel model: RCConversationModel!, at indexPath: IndexPath!) {
    }
}

// 解决融云头像的一种思路 有好办法 可以去掉
extension HomeRongMessageController: HomeRongMessageCellDelegate {

    func refreshUserInfo(userId: String) {
        UserHandler().requestRongUserInfo(userID: userId, success: { userInfo in
            RongHelp.shared.refreshUserInfoCache(userInfo: userInfo)
            self?.conversationListTableView.reloadData()
        }) { (_) in
        }
    }
}

自定义显示的ui及代码逻辑


自定义会话列表样式.jpg

以下是消息接受的自定义cell中的代码逻辑(只添加了一部分常用的,更多的自己仿写,ps:融云开发文档没法看,代码注释写的是真好,给前辈点赞)

      let userModel = RCIM.shared().getUserInfoCache(model.targetId)

        // 没有缓存用户信息 去接口查询
        if userModel == nil {
            delegate?.refreshUserInfo(userId: model.targetId)
        }

        //  消息的发送状态
        switch model.sentStatus {
        case .SentStatus_FAILED:
            contentLeftConstraint.constant = 24
            stateTipImage.isHidden = false
            stateTipImage.image = R.image.im.im_tip()!
        case .SentStatus_SENDING:
            contentLeftConstraint.constant = 24
            stateTipImage.isHidden = false
            stateTipImage.image = R.image.im.im_send_ing()!
        default:
            contentLeftConstraint.constant = 0
            stateTipImage.isHidden = true
        }

        // 接受状态
        switch model.receivedStatus {
        case .ReceivedStatus_READ:
            contentLabel.textColor = UIColor.colour.gamut999999
        default:
            contentLabel.textColor = UIColor.blues.gamut24BD90

        }

        // 草稿
        if !model.draft.isEmpty {
            contentLabel.attributedText = PublicHelper.attributedString(texts: ["[草稿] ", model.draft], colors: [UIColor.red, UIColor.colour.gamut4D4D4D])
            return
        }

        // 正常内容
        if let textMessage = (model.lastestMessage as? RCTextMessage) {
            contentLabel.text = textMessage.content
            contentLabel.textColor = UIColor.colour.gamut999999
        }

        // 图片信息
        if model.lastestMessage is RCImageMessage {
            contentLabel.text = "[图片]"
        }

        // 图片信息
        if model.lastestMessage is RCVoiceMessage {
            contentLabel.text = "[语音]"
            contentLabel.textColor = (model.receivedStatus == .ReceivedStatus_LISTENED)
                    ? UIColor.colour.gamut999999
                    : UIColor.blues.gamut24BD90
        }

        // 文件
        if let bean = model.lastestMessage as? RCFileMessage {
            contentLabel.text = "[文件] " + bean.name
            contentLabel.textColor = UIColor.colour.gamut999999
        }

效果如下 (只截图部分,消息发送失败等都已添加)


语音未读样式.jpg
图片未读显示.jpg

5.单聊会话页

class ImDetailController: RCConversationViewController {
    
    var userMap: [String: String]? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        initView()
    }
   
    func initView()  {

         // 去掉聊天页右上交自带的设置按钮
        self.navigationItem.rightBarButtonItem = nil

       // 如果用到IQKeyboardManager 必须false 否则键盘和数据框之间会有间距
        IQKeyboardManager.shared().isEnabled = false

       // 更新下历史聊天列表中自己的头像 防止自己更新头像 返回本页面 部分头像显示旧头像问题
        RongHelp.shared.refreshUserInfoCache()

        enableNewComingMessageIcon = true
        enableUnreadMessageIcon = true
     
       //  这段代码很重要 稍后会单独讲到
        if let bean = UserToken.shared.userBean {
            userMap = [
                "id" : "\(bean.userBean.id)",
                "name": bean.profileBean.nickName,
                "avatar" : TSRouter.fileCheckUrl + bean.profileBean.avatar]
        }
    }
    
    // 每次第一次进来的时候 将个人信息带给对方
    override func sendMessage(_ messageContent: RCMessageContent!, pushContent: String!) {
        if let message = messageContent as? RCTextMessage , let map = userMap {
            let data : NSData! = try? JSONEncoder().encode(map) as NSData
            let string = NSString(data:data as Data,encoding: String.Encoding.utf8.rawValue)
            if let newString = string as String? {
                message.extra = newString
                super.sendMessage(message, pushContent: pushContent)
                userMap = nil
                return
            }
        }
        super.sendMessage(messageContent, pushContent: pushContent)
    }
   
}

extension ImDetailController{

    // 点击头像
    override func didTapCellPortrait(_ userId: String!) {
       // TODO
    }
    
   // 消息点击处理事件 
    override func didTapMessageCell(_ model: RCMessageModel!) {
        super.didTapMessageCell(model)
        if conversationType != RCConversationType.ConversationType_SYSTEM {
            return
        }
        if let extra = (model.content as? RCTextMessage)?.extra {
            let messageModel = MessageModel(json: JSON(parseJSON: extra))
            //落地页  可能是项目中的任何一个页面 具体实现与项目无关  不再赘述
            return
        }
    }
    
    // 这里可以单独设置 文字分颜色点击等功能 这里是融云比较坑的地方 文档不明确  
   // 翻了源码才找到这个功能  具体写法参考下RCAttributedLabel 融云这个类 项目暂时没用到 不再赘述
}

至此,融云集成的大部分功能基本实现,按步骤复制集成应该没啥问题, 初学iOS 代码规范不一定合理,欢迎指出不合理地方
PS:下篇文章填坑

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • 眼看着节气一个个过去,霜降、立冬、小雪,不知不觉进入寒冬。 之前不知在哪看到一句话,说“修学根基要正”,当时听后一...
    谢小琨Adam阅读 1,026评论 0 50
  • 一: 婚车装饰:260(包括主婚车车头鲜花装饰,后视镜及车顶鲜花装饰) 手捧花:150/2束 手腕花:4个*15元...
    花青子阅读 75评论 0 0
  • 无关风月只想记录。 你一定也遇到过这样的男生,也许你们根本就没有说过一句话,没有任何交集,但是当你回...
    讲故事的小贾阅读 416评论 0 0
  • 暮雨泠泠寒秋来, 叶渐红黄草渐衰。 依栏凝眸萧瑟处, 寂寞小菊:枉自开。 姜尚八十文王顾, 柳七绝袖黄金台。 曾几...
    化茧成蝶_219a阅读 275评论 0 0
  • 今天看了一篇小短文《最大的麦穗》。原来关于“选择”,2500年前,为了说明这个道理,苏格拉底就发明了一个妙...
    有点意思的喵阅读 1,393评论 0 2