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:下篇文章填坑

你可能感兴趣的:(iOS 融云集成记录(不含离线推送版))