1、编写Podfile文件,屏蔽UI库,放开UI源码库的本地依赖。
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'YXDemo' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
#登录组件
pod 'YXLogin', '1.0.0'
#可选UI库
#pod 'NEContactUIKit', '9.3.0'
#pod 'NEQChatUIKit', '9.3.0'
#pod 'NEConversationUIKit', '9.3.1'
#pod 'NEChatUIKit', '9.3.0'
#pod 'NETeamUIKit', '9.3.0'
#可选Kit库(和UIKit对应)
pod 'NEContactKit', '9.3.0'
pod 'NEQChatKit', '9.3.0'
pod 'NEConversationKit', '9.3.0'
pod 'NEChatKit', '9.3.0'
pod 'NETeamKit', '9.3.0'
#基础kit库
pod 'NECommonUIKit', '9.3.0'
pod 'NECommonKit', '9.3.0'
pod 'NECoreIMKit', '9.3.0'
pod 'NECoreKit', '9.3.0'
#扩展库
pod 'NEMapKit', '9.3.1'
# 如果需要查看UI部分源码请注释掉以上在线依赖,打开下面的本地依赖
pod 'NEQChatUIKit', :path => 'NEQChatUIKit/NEQChatUIKit.podspec'
pod 'NEContactUIKit', :path => 'NEContactUIKit/NEContactUIKit.podspec'
pod 'NEConversationUIKit', :path => 'NEConversationUIKit/NEConversationUIKit.podspec'
pod 'NETeamUIKit', :path => 'NETeamUIKit/NETeamUIKit.podspec'
pod 'NEChatUIKit', :path => 'NEChatUIKit/NEChatUIKit.podspec'
#pod 'NEMapKit', :path => 'NEMapKit/NEMapKit.podspec'
end
#fix bug in Xcode 14
post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == 'RSKPlaceholderTextView'
target.build_configurations.each do |config|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
end
2、将官方demo里的UI源码库,拷贝到项目根目录。
3、执行pod update,首次安装可能需要多次install才能成功。
4、进行初始化操作。
@implementation AppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[[IMManager shared] initIM];
ViewController *vc = [[ViewController alloc]init];
UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:vc];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
return YES;
}
static let shared = IMManager()
let account = "10086"
let token = ""
func initIM() {
// init
let option = NIMSDKOption()
option.appKey = ""
IMKitClient.instance.setupCoreKitIM(option)
NEKeyboardManager.shared.enable = true
NEKeyboardManager.shared.shouldResignOnTouchOutside = true
}
5、自定义消息的封装和解析。
方式一(不推荐):注册新建的自定义消息解析器。
1)假设新类型的名称叫recruit,新建Attachment文件,编写recruit类型的消息封装类和反序列解码类,实现NIMCustomAttachment和NIMCustomAttachmentCoding的协议方法。
//
// IMRecruitAttachment.swift
// NEChatUIKit
//
// Created by iss730001004790 on 2023/3/29.
//
import UIKit
import NIMSDK
@objcMembers
open class IMRecruitAttachment: NSObject, NIMCustomAttachment {
public var iconUrl = ""
public var linkUrl = ""
public var receiveId = ""
public var subTitle = ""
public var title = ""
public var price = ""
public func encode() -> String {
let info = ["iconUrl": iconUrl, "linkUrl": linkUrl, "receiveId": receiveId, "subTitle": subTitle, "title": title, "price": price] as [String: Any]
let dic = ["type":2000,"data":info] as [String : Any]
let jsonData = try? JSONSerialization.data(withJSONObject: dic, options: .prettyPrinted)
var content = ""
if let data = jsonData {
content = String(data: data, encoding: .utf8) ?? ""
}
return content
}
}
public class IMRecruitAttachmentDecoder: NSObject, NIMCustomAttachmentCoding {
public func decodeAttachment(_ content: String?) -> NIMCustomAttachment? {
var attachment: NIMCustomAttachment?
let data = content?.data(using: .utf8)
guard let dataInfo = data else {
return attachment
}
let infoDict = try? JSONSerialization.jsonObject(
with: dataInfo,
options: .mutableContainers
)
// let infoResult = infoDict as? [String: Any]
// let type = infoResult?["type"] as? Int
// switch type {
// case 0:
attachment = decodeCustomMessage(info: infoDict as? [String: Any] ?? [String(): String()])
// default:
// print("test")
// }
return attachment
}
func decodeCustomMessage(info: [String: Any]) -> IMRecruitAttachment {
let customAttachment = IMRecruitAttachment()
if let dataInfo = info["data"] as? [String : Any] {
customAttachment.iconUrl = dataInfo["iconUrl"] as? String ?? ""
customAttachment.linkUrl = dataInfo["linkUrl"] as? String ?? ""
customAttachment.receiveId = dataInfo["receiveId"] as? String ?? ""
customAttachment.subTitle = dataInfo["subTitle"] as? String ?? ""
customAttachment.title = dataInfo["title"] as? String ?? ""
customAttachment.price = dataInfo["price"] as? String ?? ""
}
// if let type = info["type"] as? Int {
// customAttachment.type = type
// }
return customAttachment
}
}
2)删除ChatViewController里的自带的注册自定义消息解析器的代码。
3)添加新建的recruit类型消息解析器。
//注册自定义消息的解析器
NIMCustomObject.registerCustomDecoder(IMRecruitAttachmentDecoder())
方式二(推荐):使用SDK的自定义消息解析器。
1)在SDK原有的文件IMCustomAttachment,编写子类型recruit和candidates的消息封装类和反序列解码的代码。
// Copyright (c) 2022 NetEase, Inc. All rights reserved.
// Use of this source code is governed by a MIT license that can be
// found in the LICENSE file.
import UIKit
import NIMSDK
public class CustomAttachment: NSObject, NIMCustomAttachment {
public var type = 0
public var goodsName = "name"
public var goodsURL = "url"
public func encode() -> String {
let info = ["goodsName": goodsName, "goodsURL": goodsURL, "type": type] as [String: Any]
let jsonData = try? JSONSerialization.data(withJSONObject: info, options: .prettyPrinted)
var content = ""
if let data = jsonData {
content = String(data: data, encoding: .utf8) ?? ""
}
return content
}
}
open class IMRecruitAttachment: NSObject, NIMCustomAttachment {
public var type = 0
public var iconUrl = ""
public var linkUrl = ""
public var receiveId = ""
public var subTitle = ""
public var title = ""
public var price = ""
public func encode() -> String {
let info = ["iconUrl": iconUrl, "linkUrl": linkUrl, "receiveId": receiveId, "subTitle": subTitle, "title": title, "price": price] as [String: Any]
//添加子类型
let dic = ["type":2000,"data":info] as [String : Any]
let jsonData = try? JSONSerialization.data(withJSONObject: dic, options: .prettyPrinted)
var content = ""
if let data = jsonData {
content = String(data: data, encoding: .utf8) ?? ""
}
return content
}
}
open class IMCandidatesAttachment: NSObject , NIMCustomAttachment {
public var iconUrl = ""
public var linkUrl = ""
public var receiveId = ""
public var subTitle = ""
public var title = ""
public var price = ""
public func encode() -> String {
let info = ["iconUrl": iconUrl, "linkUrl": linkUrl, "receiveId": receiveId, "subTitle": subTitle, "title": title, "price": price] as [String: Any]
//添加子类型
let dic = ["type":3000,"data":info] as [String : Any]
let jsonData = try? JSONSerialization.data(withJSONObject: dic, options: .prettyPrinted)
var content = ""
if let data = jsonData {
content = String(data: data, encoding: .utf8) ?? ""
}
return content
}
}
public class CustomAttachmentDecoder: NSObject, NIMCustomAttachmentCoding {
public func decodeAttachment(_ content: String?) -> NIMCustomAttachment? {
var attachment: NIMCustomAttachment?
let data = content?.data(using: .utf8)
guard let dataInfo = data else {
return attachment
}
let infoDict = try? JSONSerialization.jsonObject(
with: dataInfo,
options: .mutableContainers
)
let infoResult = infoDict as? [String: Any]
let type = infoResult?["type"] as? Int
switch type {
case 0:
attachment =
decodeCustomMessage(info: infoDict as? [String: Any] ?? [String(): String()])
case 2000:
attachment =
decodeRecruitMessage(info: infoDict as? [String: Any] ?? [String(): String()])
case 3000:
attachment =
decodeCandidatesMessage(info: infoDict as? [String: Any] ?? [String(): String()])
default:
print("test")
}
return attachment
}
func decodeCustomMessage(info: [String: Any]) -> CustomAttachment {
let customAttachment = CustomAttachment()
customAttachment.goodsName = info["goodsName"] as? String ?? ""
customAttachment.goodsURL = info["goodsURL"] as? String ?? ""
if let type = info["type"] as? Int {
customAttachment.type = type
}
return customAttachment
}
func decodeRecruitMessage(info: [String: Any]) -> IMRecruitAttachment {
let customAttachment = IMRecruitAttachment()
if let dataInfo = info["data"] as? [String : Any] {
customAttachment.iconUrl = dataInfo["iconUrl"] as? String ?? ""
customAttachment.linkUrl = dataInfo["linkUrl"] as? String ?? ""
customAttachment.receiveId = dataInfo["receiveId"] as? String ?? ""
customAttachment.subTitle = dataInfo["subTitle"] as? String ?? ""
customAttachment.title = dataInfo["title"] as? String ?? ""
customAttachment.price = dataInfo["price"] as? String ?? ""
}
if let type = info["type"] as? Int {
customAttachment.type = type
}
return customAttachment
}
func decodeCandidatesMessage(info: [String: Any]) -> IMCandidatesAttachment {
let customAttachment = IMCandidatesAttachment()
if let dataInfo = info["data"] as? [String : Any] {
customAttachment.iconUrl = dataInfo["iconUrl"] as? String ?? ""
customAttachment.linkUrl = dataInfo["linkUrl"] as? String ?? ""
customAttachment.receiveId = dataInfo["receiveId"] as? String ?? ""
customAttachment.subTitle = dataInfo["subTitle"] as? String ?? ""
customAttachment.title = dataInfo["title"] as? String ?? ""
customAttachment.price = dataInfo["price"] as? String ?? ""
}
// if let type = info["type"] as? Int {
// customAttachment.type = type
// }
return customAttachment
}
}
2)保留ChatViewController里的注册自定义消息解析器的代码。
// 注册自定义消息的解析器
NIMCustomObject.registerCustomDecoder(CustomAttachmentDecoder())
6、在MessageModel里新增recruit和candidates类型的MessageType。另外,新建MessageRecruitMode文件承担起viewmodel的功能,将NIMMessage数据转为cell可用MessageContentModel。
case recruit
case candidates
//
// MessageRecruitModel.swift
// NEChatUIKit
//
// Created by iss730001004790 on 2023/3/29.
//
import UIKit
import NIMSDK
class MessageRecruitModel: MessageContentModel {
public var imageUrl: String?
public var text: String?
public var price: String?
required init(message: NIMMessage?) {
super.init(message: message)
contentSize = CGSize(width: qChat_content_maxW, height: 300)
height = Float(contentSize.height + qChat_margin) + fullNameHeight
type = .recruit
if let messageObject = message?.messageObject as? NIMCustomObject {
let attachment = messageObject.attachment as NIMCustomAttachment?
if attachment!.isMember(of: IMRecruitAttachment.self){
let imageTextObject = attachment! as! IMRecruitAttachment
print(imageTextObject)
imageUrl = imageTextObject.iconUrl
text = imageTextObject.title
price = imageTextObject.price
}
}
}
}
7、编写recruit类型要展示的cell文件。
8、在ChatViewController里,注册要展示的cell,并在UITableViewDataSource添加它们。
9、在ChatViewModel的modelFromMessage方法里,添加custom类型的消息处理。
如果此处自定义消息有子类型,需添加额外代码,区别处理消息。
1)笔者采取的方式是:添加一个类方法,返回自定义消息的子类型。
//
// MessageCustomTypeModel.swift
// NEChatUIKit
//
// Created by iss730001004790 on 2023/4/6.
//
import NIMSDK
import UIKit
class MessageCustomTypeModel: NSObject {
class func type(message: NIMMessage?) -> MessageType {
if let messageObject = message?.messageObject as? NIMCustomObject, let attachment = messageObject.attachment as NIMCustomAttachment? {
if attachment.isMember(of: IMRecruitAttachment.self) {
return .recruit
} else if attachment.isMember(of: IMCandidatesAttachment.self) {
return .candidates
}
}
return .text
}
}
2)分别使用子类型的viewmodel处理消息。
case .custom:
let customType = MessageCustomTypeModel.type(message: message)
if customType == .recruit {
model = MessageRecruitModel(message: message)
}else if customType == .candidates {
model = MessageCandidatesModel(message: message)
}else {
// 未识别的消息类型,默认为文本消息类型,text为未知消息
message.text = "未知消息"
model = MessageContentModel(message: message)
}
10、运行代码,在MessageRecruitModel打断点,看recruit消息是否解析成对象。
11、最后,在自定义cell上展示解析好的数据。因为新增的ChatRecruitCell继承自ChatBaseRightCell/ChatBaseLeftCell,所以重写func setModel(_ model: MessageContentModel)方法赋值即可(此处model即MessageRecruitModel解析完的对象)。
override func setModel(_ model: MessageContentModel) {
super.setModel(model)
if let m = model as? MessageRecruitModel, let url = m.imageUrl?.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
} else {
}
if let m = model as? MessageRecruitModel, let text = m.text {
} else {
}
if let m = model as? MessageRecruitModel, let text = m.price {
} else {
}
}
自定义消息发送:
再补充一下,自定义消息的发送。将数据组装成自定义的Attachment,注入附件发送出去。
//MARK: 发起自定义会话
func pushCustomeMessageChat(dic: NSDictionary, nav: UINavigationController) {
IMManager.shared.loginIM() {
let attachment = IMRecruitAttachment()
if (dic.isKind(of: NSDictionary.self)) {
attachment.title = "title"
attachment.linkUrl = "linkUrl"
attachment.subTitle = "subTitle"
attachment.iconUrl = "iconUrl"
attachment.price = "priceStr"
attachment.receiveId = "123"
// 构造出具体会话,以发给用户account为例
let session = NIMSession("10010", type: .P2P)
// 构造出具体消息并注入附件
let objct = NIMCustomObject()
objct.attachment = attachment
let message = NIMMessage()
message.messageObject = objct
//发送消息
try? NIMSDK.shared().chatManager.sendForwardMessage(message, to: session)
//跳转会话
let para = ["nav": nav as Any, "session" : session as Any]
Router.shared.use(PushP2pChatVCRouter, parameters: para, closure: nil)
}
}
}