JChat-iOS 现在只处理 bug,不再添加新功能,新功能将会在 jchat-swift 上更新。
JChat 是一个聊天 App。
JChat 具有完备的即时通讯功能。主要有:
- 基本的聊天类型:文本、语音、图片;
- 单聊与群聊;
- 用户属性,包括头像;
- 黑名单;
- 好友通讯录;
- 消息同步功能
JChat 的功能基于 JMessage SDK 来开发。它是一个 JMessage SDK 的完备的 Demo,但不仅仅是 Demo。我们的预期与目标是,当你的业务需要一个企业级的聊天 App 时,可以基于这里提供的源代码,更换 Logo 与应用名称,就可以直接用上。
JChat 当前提供 Android 与 iOS 版本。稍后也将提供 Web 版本。
本源代码项目要编译运行跑起来,需要注意以下几个地方。
- 工程使用 cocoapods 管理依赖如果没有安装 pod 需要先行安装 👉 CocoaPods
- 成功安装 cocoapods 后在终端执行如下命令安装依赖(在 Podfile 文件所在目录)
pod install
- 因为这是一个 CocoaPods 项目。打开 .xcodeproj 项目目录将缺少依赖。
- appKey:JPush appKey 是 JMessage SDK 运行的基本参数。请到 JPush 官方网站登录控制台创建应用获取。
- bundle_id:这是一个 iOS 应用的基本属性。你需要登录到 Apple 开发者网站去创建应用。
JMessage 当前版本为 2.0.x。与之前 1.0.x 版本有比较大的变更。
因为变更太大,所以这次变更有点不够友好,大部分 API 有调整,包括对象结构。这会导致集成 JMessage SDK 1.0.x 版本的 App 切换到新版本时,会编译不通过,某些 API 调用需要调整。调整的具体思路,可参考本项目 JChat iOS 源代码,以及 JMessage iOS 相关文档。
主要分为五个功能模块:用户详情 (UserInfo),会话列表 (Conversation List),会话 (Conversation) 登录 (Login) 和 设置 (Setting)。每个功能模块按照 MVC 模式划分,部分模块还有一些 Util 类。
CustomUI 自定义 View
Category 通用 Category
Util 通用辅助类
建议在 AppDelegate didFinishLaunchingWithOptions 方式初始化,如JChat 所示
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self initLogger];
// init third-party SDK
[JMessage addDelegate:self withConversation:nil];// 这句代码放到前面目的是,应用启动是会做数据库是否升级盘点,如果需要升级,则锁定数据库,进行升级
[JMessage setupJMessage:launchOptions
appKey:JMSSAGE_APPKEY
channel:CHANNEL apsForProduction:NO
category:nil];
[JPUSHService registerForRemoteNotificationTypes:(UIUserNotificationTypeBadge |
UIUserNotificationTypeSound |
UIUserNotificationTypeAlert)
categories:nil];
[self registerJPushStatusNotification];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [UIViewController new];
[self.window makeKeyAndVisible];
[self setupMainTabBar];
[self setupRootView];
[JCHATFileManager initWithFilePath];//demo 初始化存储路径
return YES;
}
注册SDK 注册APNS 成功获得APNS token 传入JPUSHService 如下代码所示
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[JPUSHService registerDeviceToken:deviceToken];
}
首次使用JMessage 需要有JMessage 账户,通过如下代码注册一个新用户。JChat 项目在JCHATRegisterViewController 类中执行了注册操作,并且在注册完成回调执行登录操作(登录操作也可以移动到其它地方进行,具体看程序业务)。
- (IBAction)registerBtnClick:(id)sender {
DDLogDebug(@"Action - registerBtnClick");
if ([self.usernameTextField.text isEqualToString:@""]) {
[MBProgressHUD showMessage:@"用户名不能为空" view:self.view];
return;
}
if ([self.passwordTextField.text isEqualToString:@""]) {
[MBProgressHUD showMessage:@"密码不能为空" view:self.view];
return;
}
[self.usernameTextField resignFirstResponder];
[self.passwordTextField resignFirstResponder];
NSString *username = self.usernameTextField.text.stringByTrimingWhitespace;
NSString *password = self.passwordTextField.text.stringByTrimingWhitespace;
if ([self checkValidUsername:username AndPassword:password]) {
[MBProgressHUD showMessage:@"正在注册" view:self.view];
[[JCHATTimeOutManager ins] startTimerWithVC:self];
[JMSGUser registerWithUsername:username
password:password
completionHandler:^(id resultObject, NSError *error) {
[[JCHATTimeOutManager ins] stopTimer];
if (error == nil) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
[MBProgressHUD showMessage:@"注册成功" view:self.view];
[[JCHATTimeOutManager ins] startTimerWithVC:self];
[JMSGUser loginWithUsername:username
password:password
completionHandler:^(id resultObject, NSError *error) {
[[JCHATTimeOutManager ins] stopTimer];
if (error == nil) {
[[NSUserDefaults standardUserDefaults] setObject:username forKey:kuserName];
[[NSUserDefaults standardUserDefaults] setObject:username forKey:klastLoginUserName];
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
JCHATSetDetailViewController *detailVC = [[JCHATSetDetailViewController alloc] init];
[self.navigationController pushViewController:detailVC animated:YES];
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
} else {
DDLogDebug(@"login fail error %@",error);
NSString *alert = [JCHATStringUtils errorAlert:error];
alert = [JCHATStringUtils errorAlert:error];
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
[MBProgressHUD showMessage:alert view:self.view];
DDLogError(alert);
}
}];
} else {
NSString *alert = @"注册失败";
alert = [JCHATStringUtils errorAlert:error];
[MBProgressHUD hideHUDForView:self.view animated:YES];
[MBProgressHUD showMessage:alert view:self.view];
}
}];
}
}
注册完成会回调 handler ,如下代码。如果出现错误会返回的error 部位nil,注意resultOvject 不同接口会返回不同类型的值或者nil,详细信息可以关注 JMessage 官方文档
typedef void (^JMSGCompletionHandler)(id resultObject, NSError *error);
会话是一个用户与用户之间聊天的载体,要有会话用户之间才能收发消息 获得会话有两种方式 1. 创建会话 2. 获取历史会话
如下代码分别创建了 单聊会话,和群聊会话, JChat 在JCHATConversationListViewController类 实现创建会话操作
- (void)skipToSingleChatView :(NSNotification *)notification {
JMSGUser *user = [[notification object] copy];
__block JCHATConversationViewController *sendMessageCtl =[[JCHATConversationViewController alloc] init];
__weak typeof(self)weakSelf = self;
sendMessageCtl.superViewController = self;
[JMSGConversation createSingleConversationWithUsername:user.username completionHandler:^(id resultObject, NSError *error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (error == nil) {
sendMessageCtl.conversation = resultObject;
JPIMMAINTHEAD(^{
sendMessageCtl.hidesBottomBarWhenPushed = YES;
[strongSelf.navigationController pushViewController:sendMessageCtl animated:YES];
});
} else {
DDLogDebug(@"createSingleConversationWithUsername");
}
}];
}
JCHat在JCHATConversationListViewController类中获取所有历史会话的具体代码如下
- (void)getConversationList {
[self.addBgView setHidden:YES];
[JMSGConversation allConversations:^(id resultObject, NSError *error) {
NSLog(@"the result");
JPIMMAINTHEAD(^{
if (error == nil) {
_conversationArr = [self sortConversation:resultObject];
_unreadCount = 0;
for (NSInteger i=0; i < [_conversationArr count]; i++) {
JMSGConversation *conversation = [_conversationArr objectAtIndex:i];
_unreadCount = _unreadCount + [conversation.unreadCount integerValue];
}
[self saveBadge:_unreadCount];
} else {
_conversationArr = nil;
}
[self.chatTableView reloadData];
});
}];
}
若想监听conversation 的消息需要把某个对象设为conversation的delegate(可以是任何对象),比如JChat JCHATConversationViewController类需要监听发送回调,受消息回调则必须先设置代理,具体代码如下
- (void)addDelegate {
[JMessage addDelegate:self withConversation:self.conversation];
}
JMSGMessage 是消息的实体。需要自己创建要发送的消息,JChat JCHATConversationViewController类中发送消息的代码如下
- (void)prepareTextMessage:(NSString *)text {
DDLogDebug(@"Action - prepareTextMessage");
if ([text isEqualToString:@""] || text == nil) {
return;
}
[[JCHATSendMsgManager ins] updateConversation:_conversation withDraft:@""];
JMSGMessage *message = nil;
JMSGTextContent *textContent = [[JMSGTextContent alloc] initWithText:text];
JCHATChatModel *model = [[JCHATChatModel alloc] init];
message = [_conversation createMessageWithContent:textContent];
[_conversation sendMessage:message];// 发送该条消息
[self addmessageShowTimeData:message.timestamp];
[model setChatModelWith:message conversationType:_conversation];
[self addMessage:model];
}
前面已经说了可以给conversation 添加回调delegate,收到消息也是通过回调函数来获取的,JChat JCHATConversationViewController类 收到消息回调方法如下
- (void)onReceiveMessage:(JMSGMessage *)message
error:(NSError *)error {
if (error != nil) {
JCHATChatModel *model = [[JCHATChatModel alloc] init];
[model setErrorMessageChatModelWithError:error];
[self addMessage:model];
return;
}
if (![self.conversation isMessageForThisConversation:message]) {
return;
}
if (message.contentType == kJMSGContentTypeCustom) {
return;
}
DDLogDebug(@"Event - receiveMessageNotification");
JPIMMAINTHEAD((^{
if (!message) {
DDLogWarn(@"get the nil message .");
return;
}
if (_allMessageDic[message.msgId] != nil) {
DDLogDebug(@"该条消息已加载");
return;
}
if (message.contentType == kJMSGContentTypeEventNotification) {
if (((JMSGEventContent *)message.content).eventType == kJMSGEventNotificationRemoveGroupMembers
&& ![((JMSGGroup *)_conversation.target) isMyselfGroupMember]) {
[self setupNavigation];
}
}
if (_conversation.conversationType == kJMSGConversationTypeSingle) {
} else if (![((JMSGGroup *)_conversation.target).gid isEqualToString:((JMSGGroup *)message.target).gid]){
return;
}
JCHATChatModel *model = [[JCHATChatModel alloc] init];
[model setChatModelWith:message conversationType:_conversation];
if (message.contentType == kJMSGContentTypeImage) {
[_imgDataArr addObject:model];
}
model.photoIndex = [_imgDataArr count] -1;
[self addmessageShowTimeData:message.timestamp];
[self addMessage:model];
}));
}