iOS社交项目(使用环信二次开发)

前言

时光如梭,2019转瞬即逝。去年手头上做了公司的两个新项目,一个教育平台类一个社交类,任务有点重但都挺过来了。社交类项目公司原计划春节后测试验收完毕开始投放市场,但今年伊始一场肺炎席卷了整个国家,也打乱了公司的部署。目前公司未安排线上办公,上班也是遥遥无期,看着房贷看着银行存款让我鸭梨山大。今天是2020年2月11日,啰嗦半天,闲来无事记录下社交项目中趟的坑。

集成环信

  • cocoapods管理环信SDK


    截屏2020-02-11下午2.51.11.png

我的项目中主要使用了环信的基本IM还有实时语音功能,但项目是用swift写的,所以自己写了一些供swift里使用的头文件,这里就不做赘述了。

  • 项目中调用
    环信官方demo代码写的很详细,可直接给代码文件copy进自己的项目中,使用很简单。
截屏2020-02-11下午3.17.08.png

图1,项目使用了上图中环信demo里的文件夹文件(IM基类是自己写的)

1.环信管理
以下方法都是封装在IM管理类中,方便调用

class TTIMClass: NSObject {
    
    public static let shared = TTIMClass.init()
    private override init() {}
    
    var isSystem:Bool = false // 是否为系统消息
    
    // 接收新消息回调
    typealias didReceiveBlock = (_ messages:[Any]) ->Void
    var didReceiveCallBack:didReceiveBlock?
    
    // 接收新CMD透传消息回调
    typealias didCMDReceiveBlock = (_ messages:[Any]) ->Void
    var didMDReceiveCallBack:didCMDReceiveBlock?
    
    // 接收会话消息列表变化回调
    typealias conversationListBlock = (_ messages:[Any]) ->Void
    var conversationListCallBack:conversationListBlock?
    
    // 自动登录成功与否
    typealias autoLoginBlock  = () ->Void
    var autoLoginCallBack:autoLoginBlock?

    
    /// 初始化环信
     func initIM(){
        
        let options = EMOptions.init(appkey: EASEMOB_KEY)
        options?.apnsCertName = "推送开发证书"
        EMClient.shared()?.initializeSDK(with: options)
        
        // 账号
        EMClient.shared()?.add(self, delegateQueue: nil)
        
        // 接收消息
        EMClient.shared()?.chatManager.add(self, delegateQueue: nil)
        
        // 实时语音
        TTChatCallManager.shared()
    }
    
    
    /// 登录环信
    /// - Parameters:
    ///   - username: username
    ///   - pwd: pwd
    ///   - resultCallBack: 登录回调 resultCallBack
    func setIMLogin(username:String, pwd:String, resultCallBack: @escaping (_ result:String?, _ emErr:EMError?)->Void){
        
        // 判断是否设置了自动登录
        let isAutoLogin:Bool =  EMClient.shared()?.options.isAutoLogin ?? false
        if !isAutoLogin {
            
            EMClient.shared()?.login(withUsername: username, password: pwd, completion: resultCallBack)
        }else{
            
           printLog(message: "未设置环信自动登录")
        }
        
    }
    
    
    /// 个人详情跳转聊天页
    /// - Parameters:
    ///   - receiveModel: 接收方mdoel
    ///   - sendModel: 发送方mdoel
    ///   - presentController: presentController
    func jumpChatViewController(receiveModel:TTPersonalModel, sendModel:TTPersonalModel, presentController:UIViewController!){
        
        let authIamges = ["", "真人icon", "女神"];
        
        let chatModel = TTChatModel.share()
        chatModel.member_id = receiveModel.member_id
        chatModel.easemob_id = receiveModel.easemob_id
        chatModel.memo_name = receiveModel.memo_name
        chatModel.memo_desc = receiveModel.memo_desc
        chatModel.nickname = receiveModel.nickname
        chatModel.gender = receiveModel.gender
        chatModel.avatar_url = receiveModel.avatar_url
        chatModel.sendAvatar_url = TTDefaultsData.setProfileModel().avatar_url
        chatModel.city = receiveModel.city
        chatModel.age = receiveModel.easemob_id
        chatModel.qq = receiveModel.qq
        chatModel.wechat = receiveModel.wechat
        chatModel.online_status = receiveModel.online_status
        chatModel.like_status = receiveModel.like_status
        chatModel.block_status = receiveModel.block_status
        chatModel.chat_status = receiveModel.chat_status
        chatModel.voice_status = receiveModel.voice_status
        
        chatModel.vip_level = receiveModel.vip_level
        chatModel.vip_name = receiveModel.vip_name
        chatModel.auth_status = receiveModel.auth_status
        chatModel.auth_name = receiveModel.auth_name
        chatModel.authImage = authIamges[Int(receiveModel.auth_status) ?? 0]
        chatModel.complaint_url = TTDefaultsData.setAppInfoModel().linkModel.complaint_url
        
        chatModel.send_member_id = sendModel.member_id
        chatModel.send_username  = TTDefaultsData.setProfileModel().username
        chatModel.send_nickname  = TTDefaultsData.setProfileModel().nickname
        chatModel.send_memoname  = sendModel.memo_name
        chatModel.send_voice_status = sendModel.voice_status
        chatModel.send_block_status = sendModel.block_status

        chatModel.send_avatarUrl = TTDefaultsData.setProfileModel().avatar_url
        chatModel.send_level = TTDefaultsData.setProfileModel().auth_status
        chatModel.send_authImage = authIamges[Int(TTDefaultsData.setProfileModel().auth_status) ?? 0]

        chatModel.account_name = TTDefaultsData.setProfileModel().nickname
        chatModel.account_avatar = TTDefaultsData.setProfileModel().avatar_url
        chatModel.account_auth_status = TTDefaultsData.setProfileModel().auth_status
        chatModel.account_member_id = TTDefaultsData.setProfileModel().user_id

        
        let chatVC = TTChatViewController.init(conversationId: receiveModel.easemob_id,
                                               type: EMConversationTypeChat,
                                               chatModel: chatModel,
                                               createIfNotExist: true)
        presentController.navigationController!.pushViewController(chatVC, animated: true)

    }
    
    
    
    /// 消息中心跳转聊天页
    /// - Parameters:
    ///   - conversationModel: 消息实体
    ///   - receiveModel: 接收方mdoel
    ///   - sendModel: 发送方mdoel
    ///   - presentController: presentController
    func jumpMessageChatViewController(conversationModel:EMConversationModel, receiveModel:TTPersonalModel, sendModel:TTPersonalModel, presentController:UIViewController!){
        
        let authIamges = ["", "真人icon", "女神"];
        
        let chatModel = TTChatModel.share()
        chatModel.member_id = receiveModel.member_id
        chatModel.easemob_id = receiveModel.easemob_id
        chatModel.memo_name = receiveModel.memo_name
        chatModel.memo_desc = receiveModel.memo_desc
        chatModel.nickname = receiveModel.nickname
        chatModel.gender = receiveModel.gender
        chatModel.avatar_url = receiveModel.avatar_url
        chatModel.sendAvatar_url = TTDefaultsData.setProfileModel().avatar_url
        chatModel.city = receiveModel.city
        chatModel.age = receiveModel.easemob_id
        chatModel.qq = receiveModel.qq
        chatModel.wechat = receiveModel.wechat
        chatModel.online_status = receiveModel.online_status
        chatModel.like_status = receiveModel.like_status
        chatModel.block_status = receiveModel.block_status
        chatModel.chat_status = receiveModel.chat_status
        chatModel.voice_status = receiveModel.voice_status

        chatModel.vip_level = receiveModel.vip_level
        chatModel.vip_name = receiveModel.vip_name
        chatModel.auth_status = receiveModel.auth_status
        chatModel.auth_name = receiveModel.auth_name
        chatModel.authImage = authIamges[Int(receiveModel.auth_status) ?? 0]
        chatModel.complaint_url = TTDefaultsData.setAppInfoModel().linkModel.complaint_url

        chatModel.send_member_id = sendModel.member_id
        chatModel.send_username  = sendModel.username
        chatModel.send_nickname  = sendModel.nickname
        chatModel.send_memoname  = sendModel.memo_name
        chatModel.send_block_status = sendModel.block_status
        chatModel.send_voice_status = sendModel.voice_status
        chatModel.send_avatarUrl = sendModel.avatar_url
        chatModel.send_level = sendModel.auth_status
        chatModel.send_authImage = authIamges[Int(sendModel.auth_status) ?? 0]

        chatModel.account_name = TTDefaultsData.setProfileModel().nickname
        chatModel.account_avatar = TTDefaultsData.setProfileModel().avatar_url
        chatModel.account_auth_status = TTDefaultsData.setProfileModel().auth_status
        chatModel.account_member_id = TTDefaultsData.setProfileModel().user_id
        
        
        let chatVC = TTChatViewController.init(coversationModel: conversationModel, chatModel: chatModel)
        presentController.navigationController!.pushViewController(chatVC, animated: true)

    }
    
    
    /// 连麦
    /// - Parameter receiveModel: receiveModel 连麦对象
    func setLineMic(receiveModel:TTPersonalModel){
        
        let chatModel = TTChatModel.share()
        chatModel.member_id = receiveModel.member_id
        chatModel.easemob_id = receiveModel.easemob_id
        chatModel.memo_name = receiveModel.memo_name
        chatModel.memo_desc = receiveModel.memo_desc
        chatModel.nickname = receiveModel.nickname
        chatModel.gender = receiveModel.gender
        chatModel.avatar_url = receiveModel.avatar_url
        chatModel.sendAvatar_url = TTDefaultsData.setProfileModel().avatar_url
        chatModel.online_status = receiveModel.online_status
        chatModel.like_status = receiveModel.like_status
        chatModel.block_status = receiveModel.block_status
        chatModel.chat_status = receiveModel.chat_status
        
        chatModel.vip_level = receiveModel.vip_level
        chatModel.vip_name = receiveModel.vip_name
        chatModel.auth_status = receiveModel.auth_status
        chatModel.auth_name = receiveModel.auth_name
        
        chatModel.send_nickname = TTDefaultsData.setProfileModel().nickname
        chatModel.send_avatarUrl = TTDefaultsData.setProfileModel().avatar_url
        chatModel.send_level = TTDefaultsData.setProfileModel().auth_status

        
        
        let conversation = EMClient.shared()?.chatManager.getConversation(receiveModel.easemob_id, type: EMConversationTypeChat, createIfNotExist: true)
        let conversationModel = EMConversationModel.init(emModel: conversation!)
        
        NotificationCenter.post(name: .EMMake1v1Call,
                                object: ["chatter":conversationModel.emModel.conversationId as Any, "type":NSNumber(value: EMCallTypeVoice.rawValue)],
                                userInfo: nil)
    }
   
    
    /// 退出登录
    /// - Parameters:
    ///   - resultCallBack: 退出登录回调
    func setExitIM(resultCallBack: @escaping (_ emErr:EMError?)->Void){
        
        
        EMClient.shared()?.logout(true, completion: resultCallBack)
    }
    
    
    
    /// 获取未读信息角标数量
    func loadConversationBadge()->Int{
        
        var unreadCount:Int = 0

//        if TT_USERID != "0" && EMClient.shared()?.isConnected ?? false{
            
            let conversations:Array<EMConversation> = EMClient.shared()?.chatManager.getAllConversations() as! Array<EMConversation>
            for conversation in conversations{

                unreadCount += Int(conversation.unreadMessagesCount)
            }
//        }
        
        return unreadCount
    }
    
    
    /// 消息未读角标
    func setUnreadMessagesCount(count: Int){
            
//        if TT_USERID != "0" && EMClient.shared()?.isConnected ?? false{
            
            let appDelegate = UIApplication.shared.delegate as! AppDelegate
            let tttbc:TTTabBarController = appDelegate.window?.rootViewController as! TTTabBarController
            tttbc.setTabbarUnreadMessagesCount(self.loadConversationBadge() + count)
            EMRemindManager.updateApplicationIconBadgeNumber(self.loadConversationBadge() + count)
//        }
    }
    
    
    
    /// 清空角标
    func setClearUnreadMessagesCount(){
            
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        let tttbc:TTTabBarController = appDelegate.window?.rootViewController as! TTTabBarController
        tttbc.setTabbarUnreadMessagesCount(0)
        EMRemindManager.updateApplicationIconBadgeNumber(0)
        
    }
    
}

//MARK: ***** 账号异常处理 *****
extension TTIMClass:EMClientDelegate{
    
    // 自动登录返回结果
    func autoLoginDidCompleteWithError(_ aError: EMError!) {
        
        if aError != nil{
            
            var errorDes = "登录失败,请重试"

            switch (aError!.code) {
                case EMErrorUserNotFound:
                    errorDes = "用户ID不存在"
                    break
                case EMErrorNetworkUnavailable:
                    errorDes = "网络未连接"
                    break
                case EMErrorServerNotReachable:
                    errorDes = "无法连接服务器"
                    break
                case EMErrorUserAuthenticationFailed:
                    errorDes = aError!.errorDescription
                    break
                case EMErrorUserLoginTooManyDevices:
                    errorDes = "登录设备数已达上限"
                    break
                case EMErrorUserAlreadyLogin:
                    errorDes = "用户已登录"
                    break
                case EMErrorUserLoginOnAnotherDevice:
                    errorDes = "当前用户在另一台设备上登录"
                    break
                default:
                    break
            }
            
            printLog(message: "环信自动登录失败结果\(errorDes)  code\(aError!.code)")
            
            if !TT_SINGLE_ONCE.isOffline {
                
                TT_SINGLE_ONCE.isOffline = true
                TTUserTool.exitLogin(isActive: false)
                NotificationCenter.post(name: .EMPushCallViewController)

                let aDelegate = UIApplication.shared.delegate as! AppDelegate
                TTAlertController.setAlertController(presentController: aDelegate.window!.rootViewController!, title:nil , messgae: "\(errorDes)!", cancel: nil, define: "我知道了") { (define) in
                    
                    TTUserTool.pushLoginPageAndResetDefault()
                }
            }
            
            
        }else{
            
            printLog(message: "IM自动成功")
            if autoLoginCallBack != nil{
                
                autoLoginCallBack!()
            }
        }
        
    }
    
    
    // 当前登录账号已经被从服务器端删除时会收到该回调
    func userAccountDidRemoveFromServer() {
        
        printLog(message: "当前登录账号已经被从服务器端删除时会收到该回调")
        
        if !TT_SINGLE_ONCE.isOffline {
            
            TT_SINGLE_ONCE.isOffline = true
            TTUserTool.exitLogin(isActive: false)
            NotificationCenter.post(name: .EMPushCallViewController)

            let aDelegate = UIApplication.shared.delegate as! AppDelegate
            TTAlertController.setAlertController(presentController: aDelegate.window!.rootViewController!, title:nil , messgae: "账号已经在其他设备上登录,该设备账号已下线!", cancel: nil, define: "我知道了") { (define) in
                
                TTUserTool.pushLoginPageAndResetDefault()
            }
        }
    }
    
    // 当前登录账号在其它设备登录时会接收到该回调
    func userAccountDidLoginFromOtherDevice() {
        
        printLog(message: "当前登录账号在其它设备登录时会接收到该回调")

        
        if !TT_SINGLE_ONCE.isOffline {
            
            TT_SINGLE_ONCE.isOffline = true
            TTUserTool.exitLogin(isActive: false)
            NotificationCenter.post(name: .EMPushCallViewController)

            let aDelegate = UIApplication.shared.delegate as! AppDelegate
            TTAlertController.setAlertController(presentController: aDelegate.window!.rootViewController!, title:nil , messgae: "账号已经在其他设备上登录,该设备账号已下线!", cancel: nil, define: "我知道了") { (define) in
                
                TTUserTool.pushLoginPageAndResetDefault()
            }
            
        }
        
    }
    
    
    
}
 


//MARK: ***** 接收消息 *****
extension TTIMClass:EMChatManagerDelegate{
    
    // 解析普通消息
    func messagesDidReceive(_ aMessages: [Any]!) {
        
        isSystem = false
        let messageArray:Array<EMMessage> = aMessages as! Array<EMMessage>
        printLog(message: "接收普通消息\(aMessages ?? Array<Any>())")
        
        for messages:EMMessage in messageArray {
            
            if messages.ext != nil {
                           
                let ext:Dictionary  = messages.ext
                printLog(message: "接收普通扩展属性消息 \(ext) id\(String(describing: messages.conversationId)) to\(String(describing: messages.to)) from\(String(describing: messages.from))")
                
                if ext.keys.contains("type"){ // 系统消息,字典包含type为系统消息,走系统消息推送逻辑
                    
                    isSystem = true
                    printLog(message: "系统消息 \(ext)")
                    
                    let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 0.01, repeats: false)
                    let content = UNMutableNotificationContent.init()
                    content.sound = UNNotificationSound.default
                    content.title = ext.keys.contains("title") ? (ext["title"] as! String) : ""
                    content.body = ext.keys.contains("content") ? (ext["content"] as! String) : "你有一条新消息"
                    content.userInfo = ["f":"admin"] // value = admin为系统消息,其他为IM消息,详见AppDelegate 点击通知回调方法
                    let request = UNNotificationRequest.init(identifier: messages.conversationId, content: content, trigger: trigger)
                    UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)

                    // 收到钱包通知,请求个人资金信息
                    if "\(ext["type"] ?? "")" == "4"{
                        
                        TTNetworkBackgroundRequest.requestAccountData()
                    }
                    
                    break
                }
            }
            
            /* 本地通知IM消息
               在长连接还存在的时候,通过环信收消息的回调接收到消息,之后判断当前App的状态,
               如果是在后台的情况下,就可以`通过代码主动弹出一个通知`,来起到通知用户的作用
            */
            EMRemindManager.remind(messages)
        }
    
        if !isSystem{
            
            self.setUnreadMessagesCount(count: Defaults[.unread_system_count])
            NotificationCenter.post(name: .SYSTEM_MSG_UNREADNUM, object: ["message_un_read_num": "\(self.loadConversationBadge())"], userInfo: nil)

            if didReceiveCallBack != nil{

                didReceiveCallBack!(aMessages)
            }
        }
        
        
    }
    
    // 会话列表发生变化
    func conversationListDidUpdate(_ aConversationList: [Any]!) {
        
        self.setUnreadMessagesCount(count: Defaults[.unread_system_count])

        if conversationListCallBack != nil{
            
            conversationListCallBack!(aConversationList)
        }
    }
    
    
    // 透传消息
    func cmdMessagesDidReceive(_ aCmdMessages: [Any]!) {
        
        if didMDReceiveCallBack != nil{
            
            didMDReceiveCallBack!(aCmdMessages)
        }
        
        let messageArray:Array<EMMessage> = aCmdMessages as! Array<EMMessage>
        printLog(message: "-----接收透传消息\(aCmdMessages ?? Array<Any>())")

        // 解析消息扩展属性
        for messages:EMMessage in messageArray {
            
            // cmd消息中的扩展属性
            if messages.ext != nil {
                
                let ext:Dictionary  = messages.ext
                printLog(message: "接收透传扩展属性消息 \(ext)")
            }
        }
        
        
    }
    
    
    
}
  1. TTIMClass里写的isSystem变量,在APP活跃状态下区分消息是即时通信还是服务器推送消息。
    TTIMClass页面跳转方法是根据自身项目的业务逻辑封装,里面所传model是为了在聊天页显示用户的头像、用户名和一些基本信息,因为环信的服务器不会记录集成方服务器的头像和用户名还有个人信息,所以给一个model来记录个人的一些信息,方便在聊天页和消息页使用。
    如在聊天页界面使用,必须在聊天的信息里添加扩展信息,用来在即时消息里传递用户的信息。

如下图这样:
图2

243D9A3673A566477DCD1996E37D7D09.png

图3

E830C3BC1D3F347B170A0BE8F1E800EE.png

代码如下:
图2这里使用的是环信demo里的ChatViewController,自己做了修改

/// 发送扩展消息
@property (strong, nonatomic) NSMutableDictionary *sendExt;
 self.sendExt = [@{@"member_id":self.chatModel.send_member_id,             // 发送人member id
                   @"nick":self.chatModel.send_nickname,                    // 发送人昵称
                   @"avatar":self.chatModel.send_avatarUrl,                 // 发送人头像
                   @"level":self.chatModel.send_level,                      // 发送人认证状态
                   @"memoname":self.chatModel.send_memoname,                // 发送人备注名
                   @"blockstatus":self.chatModel.send_block_status,         // 发送人是否被拉黑
                   @"voice_status":self.chatModel.send_voice_status,        // 发送人是否允许连麦

                   @"recive_member_id":self.chatModel.member_id,            // 接收人member id
                   @"recive_nick":self.chatModel.nickname,                  // 接收人昵称
                   @"recive_avatar":self.chatModel.avatar_url,              // 接收人头像
                   @"recive_level":self.chatModel.auth_status,              // 接收人认证状态
                   @"recive_memoname":self.chatModel.memo_name,             // 接收人备注名
                   @"recive_block_status":self.chatModel.block_status,      // 接收人是否被拉黑
                   @"recive_voice_status":self.chatModel.voice_status,      // 接收人是否允许连麦
                   @"complaint_url":self.chatModel.complaint_url            // 举报URL
                  }mutableCopy];

发送消息时使用

#pragma mark - Send Message
/// 文本消息
- (void)_sendTextAction:(NSString *)aText ext:(NSDictionary *)aExt{
    
    [self.chatBar clearInputViewText];
    if ([aText length] == 0) {
        return;
    }
 
    //TODO: 处理表情
    //    NSString *sendText = [EaseConvertToCommonEmoticonsHelper convertToCommonEmoticons:aText];
        EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithText:aText];
    [self _sendMessageWithBody:body ext:aExt isUpload:NO];
    
    if (self.enableTyping) {
        [self _sendEndTyping];
    }
}
/// 图片消息
- (void)_sendImageDataAction:(NSData *)aImageData{
   
    EMImageMessageBody *body = [[EMImageMessageBody alloc] initWithData:aImageData displayName:@"image"];
    [self _sendMessageWithBody:body ext:self.sendExt isUpload:YES];
}
/// 位置消息
- (void)_sendLocationAction:(CLLocationCoordinate2D)aCoord address:(NSString *)aAddress{
    EMLocationMessageBody *body = [[EMLocationMessageBody alloc] initWithLatitude:aCoord.latitude longitude:aCoord.longitude address:aAddress];
    [self _sendMessageWithBody:body ext:self.sendExt isUpload:NO];
}
/// 语音消息
- (void)_sendVideoAction:(NSURL *)aUrl{
    EMVideoMessageBody *body = [[EMVideoMessageBody alloc] initWithLocalPath:[aUrl path] displayName:@"video.mp4"];
    [self _sendMessageWithBody:body ext:self.sendExt isUpload:YES];
}

图3消息页面里点UITableView跳转进入聊天页,再把收到的消息里的扩展传给聊天页。因为用户在作为接收方的场景下,在收到新消息,只能用发送方那边消息的扩展,所以在跳转进入聊天页时需要把会话的用户信息传入。

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        let model:EMConversationModel = datas[indexPath.row] as! EMConversationModel
        let conversation:EMConversation = model.emModel
        let lastMessage:EMMessage = conversation.latestMessage
        let ext = lastMessage.ext
        let sendModel = TTPersonalModel()
        let receiveModel = TTPersonalModel()
        
        if ext != nil{
            // 发送方
            sendModel.nickname = ext != nil ? (ext?.keys.contains("nick") ?? false ? (ext!["nick"] as! String) : "") : ""
            sendModel.avatar_url = ext != nil ? (ext?.keys.contains("avatar") ?? false ? (ext!["avatar"] as! String) : "") : ""
            sendModel.memo_name = ext != nil ? (ext?.keys.contains("memoname") ?? false ? (ext!["memoname"] as! String) : "") : ""
            sendModel.member_id = ext != nil ? (ext?.keys.contains("member_id") ?? false ? (ext!["member_id"] as! String) : "") : ""
            sendModel.block_status = ext != nil ? (ext?.keys.contains("blockstatus") ?? false ? (ext!["blockstatus"] as! String) : "") : ""
            sendModel.auth_status = ext != nil ? (ext?.keys.contains("level") ?? false ? (ext!["level"] as! String) : "") : ""
            sendModel.voice_status = ext != nil ? (ext?.keys.contains("voice_status") ?? false ? (ext!["voice_status"] as! String) : "") : ""

            // 接收方
            receiveModel.easemob_id = conversation.conversationId
            receiveModel.member_id = ext != nil ? (ext?.keys.contains("recive_member_id") ?? false ? (ext!["recive_member_id"] as! String) : "") : ""
            receiveModel.nickname = ext != nil ? (ext?.keys.contains("recive_nick") ?? false ? (ext!["recive_nick"] as! String) : "") : ""
            receiveModel.avatar_url = ext != nil ? (ext?.keys.contains("recive_avatar") ?? false ? (ext!["recive_avatar"] as! String) : "") : ""
            receiveModel.memo_name = ext != nil ? (ext?.keys.contains("recive_memoname") ?? false ? (ext!["recive_memoname"] as! String) : "") : ""
            receiveModel.block_status = ext != nil ? (ext?.keys.contains("recive_block_status") ?? false ? (ext!["recive_block_status"] as! String) : "") : ""
            receiveModel.voice_status = ext != nil ? (ext?.keys.contains("voice_status") ?? false ? (ext!["voice_status"] as! String) : "") : ""
            receiveModel.auth_status = ext != nil ? (ext?.keys.contains("recive_level") ?? false ? (ext!["recive_level"] as! String) : "") : ""
            receiveModel.complaint_url = ext != nil ? (ext?.keys.contains("complaint_url") ?? false ? (ext!["complaint_url"] as! String) : "") : ""
        }
        
        TTIMClass.shared.jumpMessageChatViewController(conversationModel: model, receiveModel: receiveModel, sendModel: sendModel, presentController: self)
        
    }

推送

项目统一使用环信做推送,有IM消息推送、服务器根据用户个人自定义推送、系统推送,不同的推送业务逻辑处理也不一样,所以一定要区别对待。

AppDelegate:

    /// 已经进入后台
    /// - Parameter application: application
    func applicationDidEnterBackground(_ application: UIApplication) {
            // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
            // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    
        EMClient.shared()?.applicationDidEnterBackground(application)
    }

    
    /// 即将进入前台
    /// - Parameter application: application
    func applicationWillEnterForeground(_ application: UIApplication) {
            // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
        
        EMClient.shared()?.applicationWillEnterForeground(application)
    }
//MARK: ***** 推送 *****
extension AppDelegate:UNUserNotificationCenterDelegate{
    
    /// 注册远程通知
    func setRegisterRemoteNotification(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?){
           
        application.applicationIconBadgeNumber = 0
        let center = UNUserNotificationCenter.current()
        center.delegate = self
        center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
              
            if granted{  // 点击允许推送
                   
                printLog(message: "点击允许推送,成功")
                center.getNotificationSettings { (settings) in
                       
                    DispatchQueue.main.async {
                        
                        application.registerForRemoteNotifications()
                    }
                    
                    printLog(message: "点击允许推送,成功++++\(settings)")
                }
            }else{ // 点击不允许推送
                   
                printLog(message: "点击不允许推送,注册失败")
            }
        }

    }
    
    
    
    /// 获取deviceToken成功 将得到的deviceToken传给环信SDK
    /// - Parameters:
    ///   - application: application
    ///   - deviceToken: deviceToken
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        
        
        printLog(message:"获取deviceToke成功")

        DispatchQueue.global(qos: .default).async {
        
            EMClient.shared()?.registerForRemoteNotifications(withDeviceToken: deviceToken, completion: { (error) in
                
                if error != nil{

                    var errorDes = "上传失败,请重试"
                    switch (error!.code) {
                    case EMErrorUserNotFound:
                        errorDes = "用户ID不存在"
                        break
                    case EMErrorNetworkUnavailable:
                        errorDes = "网络未连接"
                        break
                    case EMErrorServerNotReachable:
                        errorDes = "无法连接服务器"
                        break
                    case EMErrorUserAuthenticationFailed:
                        errorDes = error!.errorDescription
                        break
                    case EMErrorUserLoginTooManyDevices:
                        errorDes = "登录设备数已达上限"
                        break
                    default:
                        break
                    }

                    printLog(message: "推送注册失败结果\(errorDes)  code\(error!.code)")
                }else{

                    printLog(message: "推送注册成功")
                }
            })
        }
    }
    
    
    
    /// 获取deviceToke 失败
    /// - Parameters:
    ///   - application: application
    ///   - error: error
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { // 可选
        printLog(message:"获取deviceToke 失败 Error: \(error)")
    }
    
    
    /// APP在前台得到通知
    /// - Parameters:
    ///   - center: center
    ///   - notification: notification
    ///   - completionHandler: completionHandler
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void){
        
        let userInfo = notification.request.content.userInfo
        printLog(message: "APP在前台得到推送\(userInfo)")
        completionHandler(.alert);
        EMClient.shared()?.application(UIApplication.shared, didReceiveRemoteNotification: userInfo)
        
        
        printLog(message: "保存条数\(Defaults[.unread_system_count])")
        let count = Defaults[.unread_system_count] + 1
        Defaults[.unread_system_count] = count
        TTIMClass.shared.setUnreadMessagesCount(count: Defaults[.unread_system_count])
        NotificationCenter.post(name: .SYSTEM_MSG_UNREADNUM, object: ["system_un_read_num":"\(Defaults[.unread_system_count])"], userInfo: nil)
        
        // 收到钱包通知,请求个人资金信息
        if "\(userInfo["f"] ?? "")" == "wallet"{
            
            TTNetworkBackgroundRequest.requestAccountData()
        }
    }

    
    /// 点击通知回调
    /// - Parameters:
    ///   - center: center
    ///   - response: response
    ///   - completionHandler: completionHandler
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void){
        
        let userInfo:Dictionary = response.notification.request.content.userInfo
        printLog(message: "userInfo\(userInfo))")
        

        /*   点击通知
             ApnsNickName: 发送方设置的用户名(即环信管理后台中看到的用户昵称)
             xxxx: 消息内容(发送方发的什么,就显示什么)
             badge: 角标,表示离线消息数
             sound: 收到 APNs 时的提示音
             f: 消息发送方的环信 ID
             t: 消息接收方的环信 ID
             m: 消息 ID
         */
        if userInfo.keys.contains("f") {

            printLog(message: "点击通知")

            if "\(userInfo["f"] ?? "")" == "admin" || "\(userInfo["f"] ?? "")" == "wallet" { // 系统消息

                TT_SINGLE_ONCE.pushMessageIndex = 1
                NotificationCenter.post(name: .PUSHNOTIFICATION, object: "1", userInfo: nil)
                
                if "\(userInfo["f"] ?? "")" == "wallet"{
                    
                    TTNetworkBackgroundRequest.requestAccountData()
                }
            }else{ // IM消息

                TT_SINGLE_ONCE.pushMessageIndex = 0
                NotificationCenter.post(name: .PUSHNOTIFICATION, object: "0", userInfo: nil)
            }

       
        //需要执行这个方法
        completionHandler()
    }

APP在前台时收到了消息使用本地推送,本地推送显示的逻辑在TTIMClass类中已封装过。

吐槽一下环信的推送,不是很及时在开发和测试过程中会产生很多的困扰。

结尾

项目中还使用了高德地图、阿里的人脸识别,自己都进行过二次封装,整个项目使用的MVVM模式,写之前参考了很多的设计思路,也遇到一些坑。以后有时间再写一篇记录下。

最后:愿2020年疫情尽快结束,愿我中华国运昌盛,也愿自己少一些浮躁多一分沉淀,愿自己的能力和精神世界更加强大!

end

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