美文网首页
云信IM UIKit实现自定义消息收发

云信IM UIKit实现自定义消息收发

作者: runsuc | 来源:发表于2024-03-25 09:38 被阅读0次

UIKit版本:

9.7.0

场景:

基于UIKit实现业务的自定义消息

实现过程:

实现自定义消息解析器

(1)构建解析器

类似于NIMSDK的自定义消息收发,业务层需要注册自定义消息解析器,用来编解码自定义消息内容。其实就是json->NECustomAttachment和NECustomAttachment->json的过程,sdk通过代理回调分别暴露了encode和decode方法给业务层,由业务层把自定义消息的json内容转成message的attchment和把attachment转成json用来存储和传输。

UIKit本身实现了一个NECustomAttachment类,这个类就是用来编解码自定义消息内容的,业务层可以直接继承这个类来快速实现自定义消息的编解码。

示例

import NEChatUIKit

import NIMSDK

import UIKit

public class CustomAttachment: NECustomAttachment {

  public var goodsName = "name"

  public var goodsURL = "url"

  override public func encode() -> String {

    // 自定义序列化方法之前必须调用父类的序列化方法

    let neContent = super.encode()

    var info: [String: Any] = getDictionaryFromJSONString(neContent) as? [String: Any] ?? [:]

    info["goodsName"] = goodsName

    info["goodsURL"] = goodsURL

    let jsonData = try? JSONSerialization.data(withJSONObject: info, options: [])

    var content = ""

    if let data = jsonData {

      content = String(data: data, encoding: .utf8) ?? ""

    }

    return content

  }

}

public class CustomAttachmentDecoder: NECustomAttachmentDecoder {

  override public func decodeCustomMessage(info: [String: Any]) -> CustomAttachment {

    // 自定义反序列化方法之前必须调用父类的反序列化方法

    let neCustomAttachment = super.decodeCustomMessage(info: info)

    let customAttachment = CustomAttachment(customType: neCustomAttachment.customType,

                                            cellHeight: neCustomAttachment.cellHeight,

                                            data: neCustomAttachment.data)

    if customAttachment.customType == 20 {

      customAttachment.cellHeight = 100

    }

    customAttachment.goodsName = info["goodsName"] as? String ?? ""

    customAttachment.goodsURL = info["goodsURL"] as? String ?? ""

    return customAttachment

  }

}

继承自NECustomAttachment的CustomAttachment,可以自行添加需要的类属性,如

goodsName和goodsURL

在encode里面,把当前类的goodsName和goodsURL等属性值以key["goodsName"]value[self.goodsName]的形式添加到json串里面,return给sdk

在decodeCustomMessage里面,sdk返回自定义消息的json串,业务层根据json串内容,把对应的key value赋值给attachment对象,return给sdk

(2)注册解析器

解析器越早注册越好,因为登录之后就会收到离线和漫游消息,如果有自定义消息就会走解析器。可以在登录成功的回调里面添加如下代码注册:

NIMCustomObject.registerCustomDecoder(CustomAttachmentDecoder())

发消息

发消息的时候,需要构建CustomAttachment对象赋值给NIMCustomObject对象,再赋值给NIMMessage,然后send这个NIMMessage对象就可以了。

所以自定义消息发送的关键是CustomAttachment的构建和赋值,UIKit提供构建方法CustomAttachment(customType: customMessageType, cellHeight: 100, data: data)

需要注意的是,这里的customType业务层自定指定,不要重复。cellHeight决定了最终展示在消息详情页面的时候这个消息的cell高度,data可以传data = ["type": customMessageType],customMessageType值同customType。

然后就是CustomAttachment的业务参数赋值,如goodsName和goodsURL

示例

let data = ["type": customMessageType]

        let attachment = CustomAttachment(customType: customMessageType, cellHeight: 100, data: data)

        attachment.goodsName = NIMSDK.shared().loginManager.currentAccount()

        let message = NIMMessage()

        let object = NIMCustomObject()

        object.attachment = attachment

        message.messageObject = object

        NIMSDK.shared().chatManager.send(message, to: viewmodel.session) { error in

          print("send custom message error : ", error?.localizedDescription as Any)

        }

实现自定义消息cell

以上说的还都是非UI的逻辑处理,真正要自定义绘制消息样式是在自定义消息的cell里面

消息的cell,UIKit已经实现了很多,如文本消息,图片消息等,如果要完全起一个空白cell(不包含任何原有元素,如头像),可以继承NEChatBaseCell实现。

import NEChatUIKit

import UIKit

class CustomChatCell: NEChatBaseCell {

  public var testLabel = UILabel()

  override func awakeFromNib() {

    super.awakeFromNib()

    // Initialization code

  }

  override func setSelected(_ selected: Bool, animated: Bool) {

    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state

  }

  required init?(coder: NSCoder) {

    fatalError("init(coder:) has not been implemented")

  }

  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {

    super.init(style: style, reuseIdentifier: reuseIdentifier)

    selectionStyle = .none

    backgroundColor = .clear

    testLabel.translatesAutoresizingMaskIntoConstraints = false

    contentView.addSubview(testLabel)

    NSLayoutConstraint.activate([

      testLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),

      testLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),

    ])

    testLabel.font = UIFont.systemFont(ofSize: 14)

    testLabel.textColor = UIColor.black

  }

  override func setModel(_ model: MessageContentModel, _ isSend: Bool) {

    print("this is custom message")

    testLabel.text = "this is custom message"

  }

}

init方法里面添加自定义view,自定义绘制UI,重写setModel方法,在这个方法里面,sdk会返回消息的model内容MessageContentModel,可以在这里给自己绘制的UI赋值。

如果想要cell包含原有的一些元素(如头像 时间tip),可以继承NormalChatMessageBaseCell或者FunChatMessageBaseCell,前者是基础版UI,后者是通用版UI的消息基础cell。

示例

import NEChatUIKit

import UIKit

import NIMSDK

class CustomChatCell: /*NEChatBaseCell*/NormalChatMessageBaseCell {

    public var backView = UIView()

  public var testLabel = UILabel()

    var rigntConstraint: NSLayoutConstraint?

    var leftConstraint: NSLayoutConstraint?

  override func awakeFromNib() {

    super.awakeFromNib()

    // Initialization code

  }

  override func setSelected(_ selected: Bool, animated: Bool) {

    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state

  }

  required init?(coder: NSCoder) {

    fatalError("init(coder:) has not been implemented")

  }

  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {

    super.init(style: style, reuseIdentifier: reuseIdentifier)

    selectionStyle = .none

    backgroundColor = .clear

      backView.backgroundColor = .orange

      backView.layer.cornerRadius = 10

      backView.translatesAutoresizingMaskIntoConstraints = false

      contentView.addSubview(backView)

      NSLayoutConstraint.activate([

        backView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),

        backView.widthAnchor.constraint(equalToConstant: 140),

        backView.heightAnchor.constraint(equalToConstant: 100),

      ])

      leftConstraint = backView.rightAnchor.constraint(equalTo: contentView.leftAnchor, constant: 200)

      leftConstraint?.isActive = false

      rigntConstraint = backView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -60)

      rigntConstraint?.isActive = true

    testLabel.translatesAutoresizingMaskIntoConstraints = false

    backView.addSubview(testLabel)

    NSLayoutConstraint.activate([

      testLabel.centerXAnchor.constraint(equalTo: backView.centerXAnchor),

      testLabel.centerYAnchor.constraint(equalTo: backView.centerYAnchor),

      testLabel.widthAnchor.constraint(equalToConstant: 100),

      testLabel.heightAnchor.constraint(equalToConstant: 80)

    ])

    testLabel.font = UIFont.systemFont(ofSize: 14)

      testLabel.numberOfLines = 0

    testLabel.textColor = UIColor.black

  }

  override func setModel(_ model: MessageContentModel, _ isSend: Bool) {

      super.setModel(model, isSend)

      if (isSend) {

          rigntConstraint?.isActive = true

          leftConstraint?.isActive = false

      } else {

          rigntConstraint?.isActive = false

          leftConstraint?.isActive = true

      }

    print("this is custom message")

      let object : NIMCustomObject = model.message?.messageObject as! NIMCustomObject

      let attachment : CustomAttachment = object.attachment as! CustomAttachment

      testLabel.text = attachment.goodsName

  }

}

setModel里面需要调用super.setModel(model, isSend)

实现自定义最近会话cell

最近会话cell重写主要是修改最近会话最后一条消息的内容展示的,如最后一条消息是名片消息,需要在最近会话显示【名片消息】。

继承ConversationListCell,重写configData方法,给subTitle重新赋值就可以

示例

import NEConversationUIKit

import UIKit

open class CustomConversationListCell: ConversationListCell {

  // 新增 UI 元素,用于展示在线状态

  private lazy var onlineView: UIImageView = {

    let notify = UIImageView()

    notify.translatesAutoresizingMaskIntoConstraints = false

    notify.image = UIImage(named: "about_yunxin")

    return notify

  }()

  override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {

    super.init(style: style, reuseIdentifier: reuseIdentifier)

    // 头像右下角

    contentView.addSubview(onlineView)

    NSLayoutConstraint.activate([

      onlineView.rightAnchor.constraint(equalTo: headImge.rightAnchor),

      onlineView.bottomAnchor.constraint(equalTo: headImge.bottomAnchor),

      onlineView.widthAnchor.constraint(equalToConstant: 12),

      onlineView.heightAnchor.constraint(equalToConstant: 12),

    ])

  }

  public required init?(coder: NSCoder) {

    fatalError("init(coder:) has not been implemented")

  }

  // 此方法用于数据和 UI 的绑定,可在此处在数据展示前对数据进行处理

  override open func configData(sessionModel: ConversationListModel?) {

    super.configData(sessionModel: sessionModel)

    subTitle.text = "[名片消息]"

  }

}

会话详情页面展示消息

上面准备好了自定义消息的cell:

CustomChatCell

怎么把这个cell注入到UIKit里面呢,以单聊为例

(1)需要继承会话详情页面

在这个页面把自定义cell注入到消息列表中,示例

class ChatViewController: P2PChatViewController {

    let customMessageType = 20

    override func viewDidLoad() {

        NEChatUIKitClient.instance.regsiterCustomCell(["\(customMessageType)": CustomChatCell.self])

        super.viewDidLoad()

        // Do any additional setup after loading the view.

    }

}

需要在super.viewDidLoad()之前注入

这个customMessageType是自己定义的自定义消息的cell类型,可以同发消息时候的消息类型customMessageType。通过regsiterCustomCell方法注入cell。

(2)修改默认单聊消息详情页面为自定义详情页面

Router.shared.register(PushP2pChatVCRouter) { param in

          print("param:\(param)")

          let nav = param["nav"] as? UINavigationController

          guard let session = param["session"] as? NIMSession else {

            return

          }

          let anchor = param["anchor"] as? NIMMessage

          let p2pChatVC = ChatViewController(session: session, anchor: anchor)

          for (i, vc) in (nav?.viewControllers ?? []).enumerated() {

            if vc.isKind(of: ChatViewController.self) {

              nav?.viewControllers[i] = p2pChatVC

              nav?.popToViewController(p2pChatVC, animated: true)

              return

            }

          }

            if let remove = param["removeUserVC"] as? Bool, remove {

                nav?.viewControllers.removeLast()

            }

          nav?.pushViewController(p2pChatVC, animated: true)

        }

在登录成功之后,重注册PushP2pChatVCRouter路由,这样从会话列表页面跳转就跳转到自定义的

ChatViewController页面了,从而使注入cell的代码生效。

会话列表展示消息类型

同样的最近会话列表的自定义cell注入也需要继承会话列表页面ConversationController,通过cellRegisterDic[customMessageType] = CustomConversationListCell.self注入自定义的CustomConversationListCell。

示例

import UIKit

import NIMSDK

import NEConversationUIKit

class ViewController: ConversationController, NEBaseConversationControllerDelegate {

    let customMessageType = 20

    override func viewDidLoad() {

        delegate = self

      // 自定义cell, [ConversationListModel.customType: 需要注册的自定义cell]

      cellRegisterDic[customMessageType] = CustomConversationListCell.self

        super.viewDidLoad()

        // Do any additional setup after loading the view.

    }

    public func onDataLoaded() {

      guard let conversationList = viewModel.conversationListArray else { return

      }

      conversationList.forEach { model in

          let message = model.recentSession?.lastMessage

          if (message?.messageType == .custom) {

              model.customType = customMessageType

          }

      }

      tableView.reloadData()

    }

}

需要在super.viewDidLoad()之前注入

需要注意的是,这里也需要指定一个类型customMessageType。并且需要实现

NEBaseConversationControllerDelegate,在onDataLoaded方法里面绑定会话列表数据model的

customType。也就是cell的type和model的type一致,这样sdk加载数据的时候才知道使用哪个cell展示。

相关文章

网友评论

      本文标题:云信IM UIKit实现自定义消息收发

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