/jchat-ios

JChat iOS app, a real app based on JMessage SDK.

Primary LanguageObjective-CMIT LicenseMIT

JChat iOS

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
打开项目文件 JChat.xcworkspace
  • 因为这是一个 CocoaPods 项目。打开 .xcodeproj 项目目录将缺少依赖。
配置运行的基本属性
  • appKey:JPush appKey 是 JMessage SDK 运行的基本参数。请到 JPush 官方网站登录控制台创建应用获取。
  • bundle_id:这是一个 iOS 应用的基本属性。你需要登录到 Apple 开发者网站去创建应用。

JMessage 文档

JMessage 升级

JMessage 当前版本为 2.0.x。与之前 1.0.x 版本有比较大的变更。

因为变更太大,所以这次变更有点不够友好,大部分 API 有调整,包括对象结构。这会导致集成 JMessage SDK 1.0.x 版本的 App 切换到新版本时,会编译不通过,某些 API 调用需要调整。调整的具体思路,可参考本项目 JChat iOS 源代码,以及 JMessage iOS 相关文档。

JChat 介绍

JChat 工程结构

如图

JChat 代码结构

主要分为五个功能模块:用户详情 (UserInfo),会话列表 (Conversation List),会话 (Conversation) 登录 (Login) 和 设置 (Setting)。每个功能模块按照 MVC 模式划分,部分模块还有一些 Util 类。

CustomUI 自定义 View

Category 通用 Category

Util 通用辅助类

主要功能索引

JMessage 初始化代码

建议在 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);

会话 (Conversation)

会话是一个用户与用户之间聊天的载体,要有会话用户之间才能收发消息 获得会话有两种方式 1. 创建会话 2. 获取历史会话

1.创建会话

如下代码分别创建了 单聊会话,和群聊会话, 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");
    }
  }];
}

2. 获取历史会话

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];
    });
  }];
}

3. 添加代理

若想监听conversation 的消息需要把某个对象设为conversation的delegate(可以是任何对象),比如JChat JCHATConversationViewController类需要监听发送回调,受消息回调则必须先设置代理,具体代码如下

- (void)addDelegate {
  [JMessage addDelegate:self withConversation:self.conversation];
}

4. 发送消息

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];
}

5. 接收消息

前面已经说了可以给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];
  }));
}