美文网首页融云IM
iOS 融云集成记录(不含离线推送版)

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

作者: 呵呵先森 | 来源:发表于2019-10-23 18:49 被阅读0次

    最近学习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 融云集成记录(不含离线推送版)

        本文链接:https://www.haomeiwen.com/subject/uqikvctx.html